复合索引设计与统计信息与基数

共 27 题
📑 题目列表 27 题
#
★★★

1. 冗余索引(Redundant Index)的检测,idx_a 与 idx_a_b 的覆盖关系?

如何检测数据库中的冗余索引?以 idx_a 与 idx_a_b 为例说明二者的覆盖关系,以及冗余索引会带来哪些危害?

  • 基于最左前缀原则判断索引之间的覆盖关系
  • 冗余索引的检测工具与 SQL(MySQL sys.schema_redundant_indexes、pt-duplicate-key-checker)
  • 冗余索引的危害:写放大、空间占用与优化器负担

冗余索引指某索引的键列被另一索引的键列完全覆盖(按最左前缀方向),删除后不影响任何查询。判断覆盖关系的核心是最左前缀:若索引 B 的键列序列以索引 A 的键列序列为前缀(如 idx_a_b(a,b) 以 a 开头,完全包含 idx_a(a)),且列序一致、类型一致,则 A 冗余。反之不成立:idx_a_b 不能被 idx_a 覆盖。

需要注意边界:若 idx_a 带有唯一约束(提供唯一性保证)或存在"仅用 a 列即可覆盖的查询"而 idx_a_b 因额外列更宽导致扫描成本不同,需按真实负载评估后再删。冗余索引的危害:每次 DML 多维护一棵 B-Tree(写放大)、占用磁盘与缓冲池内存、统计与优化器枚举空间增大,还可能让优化器在相似索引间犹豫导致计划抖动。检测可借助 MySQL 的 sys.schema_redundant_indexes 视图或 pt-duplicate-key-checker 工具,结合慢日志中从未被使用的索引(index_usage 统计)一起治理。

本题考查"覆盖关系"的精确判定:必须以最左前缀为准绳,同时保留"唯一约束、覆盖查询、列序类型一致"三个例外,防止误删。回答时先给判定法则,再给检测工具与危害,体现从原理到运维的完整认知。

#
★★★

2. 复合索引与 OR 条件的兼容性,OR 是否破坏最左前缀?

复合索引与 OR 条件是否兼容?OR 是否会破坏最左前缀原则,优化器在什么情况下仍能利用索引?

  • OR 分支彼此独立、无法像 AND 那样延续复合索引前缀的原理
  • Index Merge(Union)与 BitmapOr 的适用条件
  • OR 与非索引列组合时退化为全表扫描的判断

OR 条件各分支是"并集"语义,彼此独立,无法像 AND 条件那样沿复合索引的键列序列逐列延续定位,因此从"单棵 B-Tree 范围延续"的角度看,OR 确实破坏最左前缀的连续性。但优化器仍有两种方式利用索引:一是 Index Merge(MySQL 的 index_merge=union,EXPLAIN 显示 type=index_merge)或位图合并(PostgreSQL 的 BitmapOr)——分别对每个分支扫描其可用索引得到位图后求并集再回表;二是把 OR 改写为等价形式(如 a=1 OR a=2 改写为 a IN (1,2) 走单索引范围)。

前提是"每个 OR 分支都能独立走索引":只要有一个分支涉及非索引列、函数包裹列或前导模糊匹配,优化器通常放弃索引合并、整条语句退化为全表扫描。因此生产排查中,OR 条件的第一问是"每个分支是否都有可用索引",而不是简单地认为 OR 一定失效。

本题的关键是区分"索引定位"与"索引合并"两个层次:OR 不能延续前缀定位,但可以借索引合并分别命中各分支。回答时点明退化条件(任一分支无索引)与改写手段(IN 合并、UNION ALL),即覆盖了面试官想考察的完整知识点。

#
★★★

3. 复合索引与查询模式(WHERE、ORDER BY、GROUP BY)的列序协同?

复合索引的列序如何与 WHERE、ORDER BY、GROUP BY 协同?如何设计列序让一条查询同时避免 filesort 与临时表?

  • 等值列在前、排序/分组列次之、范围列最后的列序原则
  • ORDER BY 免排序的条件:与索引前缀一致、方向一致、中间无范围间断
  • GROUP BY 走索引避免临时表与排序的条件

复合索引列序与查询模式的协同原则是"先过滤、再排序、后兜底":① WHERE 中的等值条件列放在最前(等值条件不破坏前缀连续性,可任意顺序,优先放常用且区分度高的);② ORDER BY/GROUP BY 列紧接其后——当排序列与已消费的等值列构成连续前缀且方向与索引一致时,MySQL 免 filesort、PG 免 Sort 节点;③ 范围条件(>、<、BETWEEN)列放最后,因为范围条件之后索引无法继续定位或保证排序。

要同时服务 WHERE 与 ORDER BY,需满足:WHERE 等值列 + ORDER BY 列恰好是索引前缀的连续子集,且 WHERE 中不能存在"截断"该连续性的范围条件(如 WHERE a=1 AND b>2 ORDER BY c,b 的范围截断后 c 无法保序)。GROUP BY 同理:分组列与索引前缀一致时可用"松散扫描/索引有序扫描"避免临时表与排序。设计时把高频查询的"等值列→排序/分组列→范围列"清单列出来合并设计,并用 EXPLAIN 的 Extra(Using filesort/Using temporary 是否消失)验证。

本题考察列序设计的方法论而非单一结论:等值列、排序列、范围列的先后次序决定了索引能否"一索引多用"。回答时给出原则加验证手段,并强调范围条件会截断后续列的排序能力这一关键约束。

#
★★★

4. 复合索引的写入代价,每多一列,写入路径多一次 B-Tree 维护?

复合索引的写入代价如何衡量?"每多一列,写入路径就多一次 B-Tree 维护"的说法是否准确?

  • 复合索引是一棵 B+Tree 而非多棵,增加列不增加树的数量
  • 宽索引键导致叶节点变宽、页利用率下降与写入放大
  • 独立索引与复合索引在写入代价上的本质区别

该说法不准确:复合索引在物理上是一棵 B+Tree,索引键由多列拼接而成,增加一列并不会增加索引树的数量,因此不存在"每多一列就多一次 B-Tree 维护"。

但复合索引变宽仍有真实代价:键更长使每个叶节点能容纳的键数量减少、页分裂概率上升、插入/更新时需写入的字节数与 WAL 日志量增大,索引体积与缓冲池占用上升——写入放大与空间成本是存在的,只是来源是"键宽度"而非"树数量"。真正"每增加一个索引就多维护一棵树"的是独立索引。因此工程判断是:能用复合索引合并的列尽量合并(少树、宽键),能用覆盖收益抵消宽键成本的合理保留,而单独评估每个索引的写放大。

本题考察概念精确性:先纠正"多一列=多一棵树"的错误直觉,再指出宽键带来的真实写入代价(页容量、页分裂、日志),最后落到"合并索引 vs 加宽索引"的工程权衡,逻辑链完整。

#
★★★

5. 复合索引的选择性(Selectivity)评估,基数(Cardinality)与分布?

如何评估复合索引各列的选择性?基数(Cardinality)与数据分布如何共同影响索引的取舍?

  • 选择性的定义:去重值数/总行数,越接近 1 越好
  • 低基数列放在复合索引前部导致前缀键大量重复
  • 数据倾斜比平均基数更重要:热点值分布决定实际扫描行数

选择性指某列不同取值占全部行的比例(去重值数/总行数),越接近 1 说明该列越能区分行,索引扫描返回的行越少。复合索引中靠前列的选择性至关重要:若前缀列基数极低(如性别只有两个值),索引树中会产生大量重复前缀键,等值过滤后仍需扫描并回表大量行,可能不如直接全表扫描。

更关键的是分布而非平均基数:若 90% 的数据集中在少数热点值(如状态列 99% 为 normal),即使平均基数可观,对热点值的查询经索引扫描后仍命中绝大多数行,优化器会根据直方图/MCV 判断选择率而放弃索引;而对冷门值则可能走索引。优化器依据统计信息(MySQL 的 n_diff_pfx 系列、PG 的 n_distinct/MCV/直方图)做此判断,因此评估复合索引时应同时看"基数"与"分布形态",低基数但分布均匀的列与高基数但严重倾斜的列都可能价值有限。

本题考查选择性评估的完整维度:基数只是平均值,分布(倾斜、热点)才是决定实际扫描量的关键。回答时把"基数→分布→优化器如何用统计判断"串起来,并落到工程取舍。

#
★★★

6. 覆盖复合索引(Covering Index)的设计,包含 SELECT、WHERE、ORDER BY 列?

什么是覆盖索引?如何设计覆盖复合索引使其同时覆盖 SELECT、WHERE 与 ORDER BY 列,从而避免回表与排序?

  • 覆盖索引的定义:查询所需列全部位于索引中,免回表(EXPLAIN 显示 Using index)
  • InnoDB 二级索引叶节点隐含主键列,SELECT 主键无需显式加入
  • 设计顺序:WHERE 等值列 → ORDER BY 列 → SELECT 兜底列,并权衡写放大

覆盖索引指查询需要的所有列(WHERE、SELECT、ORDER BY 中引用的列)都包含在索引键中,使索引扫描本身即可产出结果,无需回表读取聚簇索引行,EXPLAIN 中表现为 Extra 的 Using index。InnoDB 二级索引的叶节点隐含存放主键值,因此 SELECT 主键列无需显式加入索引即可免回表,这是设计时容易忽略的"免费列"。

设计步骤:① 确定 WHERE 等值列(放最前,兼顾最左前缀);② 追加 ORDER BY 列(保证方向一致、与等值列构成连续前缀,消除 filesort);③ 最后把 SELECT 中剩余的列追加为"冗余负载列"——它们只用来覆盖输出,不参与定位,但会让索引变宽、增大写放大,需权衡是否值得(行宽大、回表昂贵时值得)。典型收益场景:高频点查、统计类聚合(COUNT/SUM 全索引扫描)、宽表(回表随机 I/O 昂贵)。

本题考察覆盖索引的设计顺序与代价意识:先用等值与排序列保证"能用",再追加负载列保证"覆盖",最后用写放大约束贪心。点明 InnoDB 二级索引隐含主键列是加分细节。

#
★★★

7. 复合索引的 EXPLAIN 解读?

如何解读 EXPLAIN 中复合索引的使用情况?key、key_len、rows、ref 与 Extra(Using index、Using index condition、Using filesort)各表示什么?

  • key 表示实际使用的索引,key_len 反映消耗了多少索引前缀字节
  • ref 显示等值匹配的来源列,rows 为预估扫描行数
  • Extra 中 Using index(覆盖)、Using index condition(ICP)、Using filesort(排序未用索引)的判读

EXPLAIN 解读复合索引使用情况的关键字段:① key:优化器实际选择的索引;② key_len:索引中实际被消费的键字节长度——与索引定义对比可精确判断"最左前缀用了多少列",这是判断复合索引是否被充分利用的最直接证据;③ ref:参与等值探测的列或常量(const),ref 越多说明前缀列被完整等值匹配;④ rows:预估扫描行数,与实际行数对比可发现统计失真。

Extra 关键标志:Using index 表示覆盖扫描(未回表);Using index condition 表示启用了索引下推(ICP,部分条件下推到存储引擎过滤);Using where 表示部分条件在服务层过滤;Using filesort/Using temporary 表示排序或分组未用索引。综合判读:key 非空但 key_len 远小于索引定义长度,说明"部分使用索引"(如跳过中间列);rows 大而 ref 为空,说明索引只做了粗粒度过滤。

本题考察 EXPLAIN 的精细读法:key_len 与 ref 回答"用了多少列",Extra 回答"用了之后还做了什么"。回答时给出"key_len 对比索引定义"的方法,这是复合索引调优最实用的技巧。

EXPLAIN SELECT id, name FROM t WHERE a = 1 AND c > 2;
-- key: idx_a_b_c, key_len: 4 (仅 a), ref: const, Extra: Using where
#
★★★

8. 复合索引对 NULL 的处理,PostgreSQL 中 NULL 的排序与索引行为(NULLS FIRST/LAST),NULLS NOT DISTINCT 唯一约束与部分索引如何配合?

PostgreSQL 中 NULL 在 B-Tree 索引中的排序与扫描行为是怎样的?为什么 IS NULL 查询仍可能不走索引?NULLS NOT DISTINCT 唯一约束与部分索引分别在什么场景下发挥作用?

  • PostgreSQL B-Tree 索引存储 NULL 且默认把 NULL 视为最大值(ASC 排最后、DESC 排最前)
  • IS NULL/IS NOT NULL 的估算与扫描路径(部分索引)
  • NULLS NOT DISTINCT(PG 15+)让唯一约束视 NULL 为相等值

PostgreSQL 的 B-Tree 索引会存储 NULL 值键项,且 NULL 在排序语义中视为"最大":默认 ASC 排序下 NULL 排在最后(NULLS LAST)、DESC 下排在最前(NULLS FIRST),因此 WHERE col IS NULL 可以使用普通 B-Tree 索引定位(PG 8.3 起)。与 MySQL InnoDB(二级索引同样存储 NULL,但 NULL 按最小值排序、位于键序最左)的差异主要在"NULL 的排列位置"而非"是否存储"。需要注意的是:优化器最终是否走索引仍取决于成本估算——NULL 占比很高时,索引扫描命中大量行、逐行回表代价可能超过全表扫描,此时仍会放弃索引。

弥补手段有二:① 部分索引——CREATE INDEX ... ON t(col) WHERE col IS NULL(或 IS NOT NULL),只索引满足谓词的行,NULL 查询可精确命中;② NULLS NOT DISTINCT——PG 15 起可在唯一约束/唯一索引上声明 NULLS NOT DISTINCT,使多个 NULL 被视为重复值,从而对"含 NULL 的列"强制唯一性(默认 NULLS DISTINCT 下唯一约束允许任意多个 NULL)。此外排序时 NULLS FIRST/LAST 的默认行为(NULL 最大)也会影响使用索引排序的查询。

本题考查跨引擎的 NULL 语义:先讲清 PG"索引存储 NULL 且 NULL 排最大"的正确模型及与 MySQL(NULL 排最左)的差异,再给出 NULLS NOT DISTINCT(唯一性)与部分索引(查询加速)两种官方手段,并顺带说明 NULLS FIRST/LAST 对索引排序的影响。

#
★★★

9. MySQL InnoDB 统计信息的持久化(innodb_stats_persistent)与采样(INNODB_STATS_PERSISTENT_SAMPLE_PAGES)?

MySQL InnoDB 统计信息的持久化机制是什么?innodb_stats_persistent 与 innodb_stats_persistent_sample_pages 参数各起什么作用?

  • 持久化统计存于 mysql.innodb_table_stats / innodb_index_stats 系统表
  • 非持久化统计(旧行为)每次打开表随机采样导致计划抖动
  • sample_pages 控制采样页数:越大越准但 ANALYZE 越慢

持久化统计(innodb_stats_persistent=ON,8.0 默认)把表级与索引级统计(行数、索引基数、页数)写入 mysql.innodb_table_stats 与 mysql.innodb_index_stats 系统表,由 ANALYZE TABLE 或自动重算显式更新,避免每次打开表时重新采样——这是消除"同一查询计划随机漂移"的关键改进。旧的非持久化行为在每次表首次打开时做随机采样,结果随采样波动,容易引发计划抖动。

innodb_stats_persistent_sample_pages(默认 20)控制持久化统计每次采样读取的随机页数:页数越多,基数与行数估算越接近真实(尤其对超大索引块),但 ANALYZE 耗时与开销越大。默认 20 页在数据量很大的表上可能显著低估索引基数,导致优化器误判选择性;对关键大表可调大(如 100)后重新 ANALYZE,再评估计划是否改善。

本题考察统计持久化与采样精度的权衡:持久化解决"稳定性",采样页数解决"准确性"。回答时讲清两个参数的作用、默认值(20 页)与调优方向,并点出"统计波动→计划抖动"这一生产痛点。

#
★★★

10. MySQL 统计信息的二次采样与自动更新?

什么是 MySQL 统计信息的"二次采样"?InnoDB 在什么条件下自动更新统计信息?

  • 二级索引两阶段随机采样的过程
  • 自动重算触发条件:修改行数超过约 1/16 表行数或发生 DDL
  • innodb_stats_auto_recalc 与持久化统计的关系

二次采样指 InnoDB 对索引做统计时采用的两阶段随机采样:先从索引根节点沿随机分支下潜到叶层随机选取若干页(每次采样从根出发随机选子节点),再统计这些页内的行数与键分布,外推出整棵索引的基数——相比全索引扫描,开销极小,但样本页之外的大索引块可能被低估。

自动更新:InnoDB 检测到表被修改的行数累计超过表估算行数的 1/16(约 6.25%)时,异步触发统计重算;发生 DDL(如新增/删除索引)后也会自动重算;这些行为由 innodb_stats_auto_recalc(默认 ON,仅对持久化统计生效)控制,非持久化统计则在表首次打开时重算。理解触发条件有助于解释"为什么没有手动 ANALYZE 计划也变了",以及为何高频 DML 下统计仍可能滞后(1/16 阈值对超大表意味着巨大的绝对修改量)。

本题考察自动统计更新的内部机制:两阶段采样说明"估算如何产生",1/16 触发阈值说明"何时更新"。回答时点出超大表自动更新滞后的工程含义,与手动 ANALYZE 的配合策略。

#
★★★

11. PostgreSQL 中 ANALYZE 命令的作用与频率,自动 ANALYZE 触发条件?

PostgreSQL 中 ANALYZE 命令的作用是什么?autovacuum 在什么条件下自动触发 ANALYZE?生产上如何把握 ANALYZE 频率?

  • ANALYZE 采集列级统计写入 pg_statistic,经 pg_stats 可查
  • 自动触发公式:修改行数 > autovacuum_analyze_threshold + scale_factor × 行数
  • 批量导入后手动 ANALYZE 的必要性

ANALYZE 对表做随机块采样,把列级统计(n_distinct、最频值 MCV、直方图边界、NULL 占比、相关性等)写入 pg_statistic 系统表,优化器规划时据此估算选择率;它与 VACUUM 无关(VACUUM 回收死元组),是纯统计动作。自动触发由 autovacuum 进程完成:当被修改行数超过 autovacuum_analyze_threshold(默认 50)加上 autovacuum_analyze_scale_factor(默认 0.1)× 当前估算行数时触发 ANALYZE,阈值参数与 VACUUM 相互独立。

频率把握:该公式对小表敏感(改 60 行即触发)、对大表宽松(百万行表需改约 10 万行),因此批量导入、ETL 灌数后自动触发往往滞后,建议显式 ANALYZE 让统计立即生效;对高频写入表可调整 autovacuum_analyze_scale_factor 或设置表级存储参数,并用 pg_stat_user_tables 的 n_mod_since_analyze/last_analyze 监控滞后。

本题考察 ANALYZE 的语义(只采集统计、与 VACUUM 分离)与自动触发公式。回答时给出公式含义、大小表差异与手动补采场景,体现对自动机制滞后性的工程认知。

#
★★★

12. 基数(Cardinality)估算的误差,均匀分布假设的失效场景?

优化器估算基数时通常做哪些假设?均匀分布假设在哪些场景下失效,导致哪些估算误差?

  • 均匀分布、列独立、固定 NULL 占比等默认假设
  • 失效场景:数据倾斜、多列相关、区间分布不均
  • 误差传导:选择率误估 → join 顺序/扫描方式误选,以及扩展统计等对策

优化器在缺乏分布细节时默认假设:列值均匀分布、各列相互独立、NULL 比例固定。这些假设在三种典型场景失效:① 单列数据倾斜——少数热点值占绝大多数行(如状态列 99% 为 normal),按平均分布估算会严重低估热点值选择率或高估冷门值;② 多列相关——WHERE country='CN' AND language='中文' 时两列强相关,独立假设让选择率连乘而系统性低估行数;③ 区间非均匀——长尾分布下直方图桶内插值仍可能失真。

误差沿计划树传导:低估行数会让优化器选择本应失败的 Nested Loop、低估 join 中间结果导致连接顺序倒置、高估选择率则错误放弃索引。对策:PG 用 CREATE STATISTICS 扩展统计(dependencies/ndistinct/mcv)修正相关与组合基数,MySQL 8.0 用直方图补充无索引列的分布信息,并保证统计及时刷新。

本题考察对估算假设及其失效模式的理解:先列假设,再给三类失效场景,最后落到误差传导与对策,形成"假设→失效→修复"的完整链条。

#
★★★

13. 直方图(Histogram)的应用,频率直方图、等高直方图(PG 12+)?

直方图如何帮助优化器估算选择率?频率直方图与等高直方图的区别是什么?PostgreSQL 12+ 如何结合使用?

  • 频率直方图:每个(高频)值记录频率,适合少量离散值
  • 等高直方图:每桶行数近似相等、边界为真实值,适合大量不同值
  • PG 12+ 用 MCV 覆盖高频值、等高直方图覆盖其余值的组合策略

直方图把列取值域划分成若干桶,为范围与等值条件提供分布信息。频率直方图(frequency/singleton histogram)为每个(或每个高频)值记录其频率,等值估算可直接命中频率,适合取值个数少的离散列(枚举状态、类型码);等高直方图(height-balanced histogram)把取值域切成行数近似相等的桶,只记录边界(数据中真实出现的值),适合取值众多、分布倾斜的连续列(价格、时间、年龄),范围条件按桶内比例插值估算。

PostgreSQL 12+ 的策略是二者结合:先统计最频值列表(MCV,最多 default_statistics_target 个高频值及其频率),把高频部分从直方图中剔除,剩余值再放入等高直方图(histogram_bounds)——MCV 兜高频、直方图兜中低频,在倾斜分布下显著提升等值与范围估算精度;等值估算优先查 MCV,未命中再用剩余行数与 distinct 数推算。

本题考察直方图的类型学与应用:频率型精确但仅适用少量离散值,等高型粗糙但可覆盖大量值。点出 PG 12+ 的"MCV+等高"组合是本题的核心增量知识点。

#
★★★

14. 优化器如何用索引统计信息估算 range 扫描行数(index dive vs 统计估算)

优化器如何估算索引 range 扫描的行数?MySQL 的 index dive 与统计估算两种方式各自的原理、适用场景与缺陷?

  • index dive:沿 B-Tree 下潜实测边界间不同值数,精确但成本高
  • eq_range_index_dive_limit(默认 200):等值条件超限后改用统计估算
  • 统计估算依赖基数的精度与陈旧风险

MySQL 对范围/等值列表条件的行数估算有两种方式:index dive 是优化器沿索引 B-Tree 从根下潜,实测两个边界(或每个等值)之间实际存在的不同键值数(rec per key),精度极高,但每个条件都需要一次树遍历,条件数量多时成本可观;统计估算则是读取持久化统计(innodb_index_stats 的基数)按比例推算,速度快但精度依赖统计新鲜度。

两者的切换由 eq_range_index_dive_limit(默认 200)控制:当某索引的等值条件数量超过该阈值(典型如超大 IN 列表)时,MySQL 从 index dive 退化为统计估算,避免 dive 开销爆炸。缺陷:统计估算在数据倾斜或统计陈旧时误差大(如 IN 列表中的热点值被低估);而 index dive 对超大列表反而成为性能瓶颈。生产上可通过调整该参数在"精确性"与"规划成本"间取舍。

本题考察 range 估算的双轨机制:dive 是"实测"、统计是"推算",阈值参数管理二者切换。回答时给出机制、切换阈值(默认 200)与各自缺陷,体现对规划成本的理解。

#
★★

15. 统计信息陈旧(Stale Statistics)的危害,误估行数导致错误计划?

统计信息陈旧会带来哪些危害?如何发现并修复因统计陈旧导致的错误执行计划?

  • 陈旧统计 → 行数误估 → 扫描方式与 join 策略误选
  • 发现手段:EXPLAIN ANALYZE 对比估算与实际行数
  • 修复手段:ANALYZE、直方图、采样调参与自动阈值

统计陈旧的危害链:数据大量变更后统计未刷新,优化器仍按旧的行数、基数与分布规划——可能为已近空表选择全表扫描加 Hash Join,或为千万行表选择 Nested Loop;典型症状是"同一 SQL 突然变慢"或"计划与数据规模明显不匹配",且往往在批量写入/大批删除之后出现。

发现手段:EXPLAIN ANALYZE 对比每个节点的估算 rows 与实际 rows,差距达数量级即统计失真;PG 查 pg_stat_user_tables 的 last_analyze、n_mod_since_analyze(PG 14+)判断滞后;MySQL 对比 ANALYZE TABLE 前后的计划。修复:立即执行 ANALYZE(PG)/ANALYZE TABLE(MySQL);长期调整自动统计阈值(PG autovacuum_analyze_*、MySQL 自动重算与采样页),对倾斜列补直方图/扩展统计,并在 ETL 流程末尾显式刷新统计。

本题考察"统计生命周期管理":先描述危害链条与典型症状,再给 EXPLAIN ANALYZE 对比法这一标准诊断手段,最后落到修复与预防的闭环。

#
★★

16. 统计信息(Statistics)的类型,表级(pg_class)、列级(pg_stats)、直方图(pg_statistic)?

PostgreSQL 的统计信息分为哪些层级?pg_class、pg_statistic 与 pg_stats 各自存储什么内容?

  • 表级统计:pg_class.reltuples/relpages,由 VACUUM/ANALYZE 更新
  • 列级统计:pg_statistic 每列一行主记录加统计槽位(MCV/直方图/n_distinct 等)
  • pg_stats 是 pg_statistic 的安全可读视图

PostgreSQL 统计分两级:表级存于 pg_class——reltuples(估算行数)与 relpages(估算页数)由 VACUUM/ANALYZE 更新,是表扫描与全表代价估算的输入;列级存于 pg_statistic 系统表——每列一行主记录,通过 stakind1~5 等槽位存放多种统计:MCV 最频值及其频率(stakind=1)、等高直方图边界(stakind=2)、物理相关性 correlation(stakind=3)、以及 n_distinct、null_frac 等基础字段,此外还有 attstattarget 控制每列的统计深度。

pg_stats 是基于 pg_statistic 的可读视图:自动隐藏当前用户无权限列的取值,字段名更友好(n_distinct、most_common_vals、most_common_freqs、histogram_bounds、correlation、null_frac、avg_width),是 DBA 日常查看统计的入口;pg_statistic 本身不建议直接读写。

本题考察统计信息的物理分布:表级在 pg_class、列级在 pg_statistic、可读入口在 pg_stats。回答时把三个对象的职责讲清,并说明 pg_stats 的权限过滤特性。

#
★★

17. NULL 比例的统计,优化器对 NULL 分布的估算?

优化器如何统计与利用 NULL 比例信息?NULL 比例如何影响选择率估算?

  • pg_stats.null_frac 记录 NULL 行占比
  • 等值条件按 (1 - null_frac) 折减,IS NULL 以 null_frac 为选择率
  • null_frac 陈旧对估算的连锁影响

PostgreSQL 在 pg_stats 中记录每列的 null_frac(NULL 行占比)。优化器估算 WHERE col = v 的选择率时,先以 (1 - null_frac) 折减总行数,再按直方图/均匀分布推算非 NULL 部分的命中;估算 IS NULL 条件时直接以 null_frac 作为选择率。因此 NULL 占比高的列,等值条件估算行数会被显著压低,IS NULL 查询的估算则直接取决于该比例。

null_frac 由 ANALYZE 刷新,若数据分布变化(如大量行随后被置为 NULL)而统计未更新,等值与 IS NULL 估算都会失真。MySQL 侧 InnoDB 不维护独立的 NULL 比例统计(依赖采样与直方图,且直方图不含 NULL),这是两库在 NULL 估算上的差异。工程上对"可空列 + 大量 NULL"的查询,可结合部分索引(PG)与统计刷新保证估算准确。

本题考察 NULL 在统计中的角色:null_frac 既是等值估算的折减因子,也是 IS NULL 的选择率来源。回答时给出公式化行为,并点出陈旧风险与两库差异。

#
★★

18. MySQL mysql.innodb_table_stats?

MySQL 的 mysql.innodb_table_stats 表的作用是什么?其内容与更新机制如何影响优化器?

  • 持久化统计的存储位置与字段(n_rows、clustered_index_size、sum_of_other_index_sizes)
  • 更新时机:ANALYZE TABLE、自动重算、DDL
  • 与 innodb_index_stats(索引基数)的分工

mysql.innodb_table_stats 是 InnoDB 持久化统计的系统表:每张 InnoDB 表一行,记录 database_name、table_name、last_update(统计更新时间)、n_rows(估算行数)、clustered_index_size(聚簇索引页数)、sum_of_other_index_sizes(其余索引总页数)。优化器规划时读取该表得到表级规模信息,配合 mysql.innodb_index_stats 中的索引前缀基数(n_diff_pfx 系列)估算选择率,而不是每次现算。

更新时机:ANALYZE TABLE 显式刷新、DDL(如新增索引)触发、以及修改行数超过约 1/16 表行数时的自动重算(innodb_stats_auto_recalc=ON)。该表损坏或长期陈旧会使计划劣化(如按错误的页数/行数选择全表扫描);排查看 last_update 是否落后于数据变更节奏,必要时调大采样页数后重新 ANALYZE。

本题考察持久化统计的落点:innodb_table_stats 存表级规模,innodb_index_stats 存索引级基数。回答时给出字段含义、更新时机与陈旧排查方法。

#
★★

19. MySQL 的 ANALYZE TABLE?

MySQL 的 ANALYZE TABLE 命令做什么?使用上有哪些注意点(锁、采样、分区表)?

  • 重新计算统计并写入持久化系统表(含 FULLTEXT 计数)
  • InnoDB 实现为随机采样而非全表扫描,受 sample_pages 影响
  • 执行期间持元数据锁(MDL),分区表默认全部分区

ANALYZE TABLE 让 InnoDB 重新计算表与各索引的统计信息(估算行数、索引基数、页数)并写入持久化系统表,供优化器规划使用;同时更新 FULLTEXT 索引的文档计数。InnoDB 的实现是随机采样(受 innodb_stats_persistent_sample_pages 控制),不是全表扫描,因此大表上也是可接受的轻量操作,但采样页过少会降低精度。

注意点:① 锁行为——执行期间需要持有表级元数据锁(MDL),并发 DML 可能短暂等待,业务高峰慎用;② 采样参数——对超大表可临时调大 sample_pages 再 ANALYZE 以提高准确性;③ 分区表——默认分析全部分区(旧版本对分区支持有限),耗时随分区数增加;④ 频次——数据分布剧烈变化(批量写入、归档删除)后应及时执行,并配合监控统计更新时间。

本题考察 ANALYZE TABLE 的完整语义:做什么(统计)、怎么做(采样)、注意什么(MDL 锁、采样页、分区)。回答覆盖执行行为与工程注意点即可。

ANALYZE TABLE orders;
SHOW WARNINGS; -- 查看统计更新是否发生
#
★★

20. MySQL 8.0 直方图的原理与应用,直方图如何改善非索引列的基数估计,采样桶数与更新策略对执行计划的影响?

MySQL 8.0 直方图的原理是什么?它如何改善非索引列的基数估计?桶数与更新策略如何影响执行计划?

  • ANALYZE TABLE ... UPDATE HISTOGRAM ON 列 生成,存于数据字典
  • 对无索引列、JOIN 列与倾斜数据的选择率改善
  • 直方图不随 DML 自动更新,需手动刷新

MySQL 8.0 的直方图通过 ANALYZE TABLE t UPDATE HISTOGRAM ON col WITH N BUCKETS 生成,以 JSON 形式存于数据字典,可从 information_schema.COLUMN_STATISTICS 查看(含 bucket 数组、histogram-type、sampling-rate)。它让优化器在"没有索引的列"上也拥有分布信息:例如 status 列虽无索引,直方图仍能准确估算等值/范围/IN 的选择率,避免均匀假设下的误估;对 JOIN 列与严重倾斜的列同样有效,配合索引统计综合使用。

桶数与更新策略的影响:桶数(默认 100、最大 1024)越多,分布刻画越细,但构建与存储成本越高;直方图不随 DML 自动更新——数据分布变化后必须手动重新生成(可纳入定时任务),否则保留的是过期分布,反而让估算失真。使用要点:先确认收益列(无索引、倾斜、JOIN 频繁),再设定合理桶数并建立刷新机制。

本题考察直方图的"补盲"价值与维护成本:收益在无索引列与倾斜列,代价是手动更新。回答时给出语法、存储位置、桶数权衡与刷新策略四个要点。

ANALYZE TABLE orders UPDATE HISTOGRAM ON status WITH 100 BUCKETS;
SELECT * FROM information_schema.COLUMN_STATISTICS WHERE TABLE_NAME = 'orders';
#
★★

21. PostgreSQL pg_statistic 系统表?

PostgreSQL 的 pg_statistic 系统表存储什么?其统计条目(stakind 槽位)如何组织?

  • 每列一行主记录加最多 5 组统计槽位(stakind/staop/stanumbers/stavalues)
  • stakind 语义:MCV、直方图、相关性、数组元素统计等
  • 普通用户经 pg_stats 视图访问,不直接改表

pg_statistic 是 PostgreSQL 存储列级统计的系统表,结构为"每列一行主记录 + 最多 5 组统计槽位":主记录含 starelid(表)、staattnum(列号)、stattuple? 主要字段包括 stakind1~5(槽位类型)、staop1~5(槽位操作符)、stanumbers1~5(数值数组,如频率)、stavalues1~5(值数组,如 MCV 列表与直方图边界)。stakind 枚举常见值:1 为 MCV(最频值及其频率)、2 为等高直方图边界、3 为相关性(物理顺序与逻辑顺序的相关系数)、4/5 用于数组列的公共元素统计。

该表由 ANALYZE 写入,优化器读取以估算选择率;普通用户无权限直接读取原始 stavalues(可能包含敏感数据),应通过 pg_stats 视图查看过滤后的结果。手工修改 pg_statistic 不被支持,且会被下一次 ANALYZE 覆盖,属反模式。

本题考察统计的物理存储格式:槽位化设计(stakind 区分统计类型)是核心,视图与权限隔离是安全细节。回答时讲清"一列多槽位、槽位按类型复用"即可。

#
★★

22. PostgreSQL pg_stats 视图?

PostgreSQL 的 pg_stats 视图与 pg_statistic 有什么关系?它提供哪些常用字段?

  • pg_stats 是 pg_statistic 的安全可读视图,隐藏未授权列
  • 常用字段:null_frac、n_distinct、most_common_vals/freqs、histogram_bounds、correlation
  • 使用限制:NULL 值无法区分"无统计"与"值为 NULL",视图只读

pg_stats 是基于 pg_statistic 的视图,只展示当前用户有权限读取的表列统计,自动隐藏未授权列的取值,是 DBA 日常查看统计的入口。常用字段:null_frac(NULL 行占比)、n_distinct(估算去重数,正数为绝对值、负数为相对行数比例)、most_common_vals 与 most_common_freqs(MCV 列表及其频率)、histogram_bounds(等高直方图边界数组)、correlation(物理存储顺序与值顺序的相关性,接近 1 时范围扫描的代价估算会被降低)、avg_width(平均字节宽度)、stadistinct 等。

使用限制:① 该视图只读,修改统计需通过 ANALYZE;② 字段值为 NULL 时无法区分"统计未采集"与"真实值就是 NULL";③ 高基数列的 n_distinct 是采样外推值,精度有限。排查计划问题时,常把 pg_stats 的直方图/MCV 与 EXPLAIN 的估算行数对照,手工复核优化器的选择率计算。

本题考察 pg_stats 的定位与字段语义:视图=安全入口,字段=估算输入。回答时给出常用字段含义与"只读、NULL 二义性"两个注意点。

#
★★

23. PostgreSQL 扩展统计与 n_distinct?

PostgreSQL 的 n_distinct 如何估算与表示?扩展统计(CREATE STATISTICS)与 n_distinct 有什么关系?

  • n_distinct 正数为绝对值、负数为相对行数比例(-1 表示不同值数等于行数)
  • 高基数列采样外推的不可靠性
  • CREATE STATISTICS 的 ndistinct 选项统计多列组合基数

pg_stats.n_distinct 用正数表示估算的不同值个数,用负数表示相对比例(如 -1 表示不同值数等于表行数),该值由 ANALYZE 从样本外推:对低基数列较准,对高基数列(UUID、时间戳、随机字符串)样本中很难覆盖全部取值,外推误差很大,常被低估为比例形式,进而高估等值条件的选择率。

扩展统计(CREATE STATISTICS)支持 ndistinct 统计类型:对指定的多列组合(如 (a,b))直接统计组合去重数,存入扩展统计对象。它对两类场景价值显著:① GROUP BY a, b 查询——优化器按单列 distinct 相乘会严重高估组合基数(低估选择率),ndistinct 统计给出真实组合基数;② 等值 JOIN 两侧多列——a=b 的 join 基数估算更准。启用后优化器在查询列组与统计定义匹配时自动使用,无需改写 SQL。

本题考察 n_distinct 的语义(正负号)与扩展统计的互补价值:单列 distinct 解决不了多列组合基数,CREATE STATISTICS 的 ndistinct 正是为此而生。

#
★★

24. 扩展统计(Extended Statistics),PG 的 CREATE STATISTICS 处理多列相关?

PostgreSQL 扩展统计如何解决多列相关性导致的估算问题?CREATE STATISTICS 的三种统计类型各解决什么问题?

  • ndistinct:多列组合基数,改善 GROUP BY/JOIN 估算
  • dependencies:函数依赖强度,纠正多列条件连乘低估
  • mcv:多列最频组合及其频率,覆盖高相关倾斜组合

标准统计假设列独立,WHERE a AND b 的选择率会被连乘而系统性低估(列相关时尤其严重)。CREATE STATISTICS 提供三种统计:① ndistinct——统计多列组合的去重数,改善 GROUP BY 多列与多列 JOIN 的基数估算;② dependencies——测量列间函数依赖强度(如 b 几乎由 a 决定),此时 P(a∧b)≈P(a),优化器用它直接纠正连乘误差;③ mcv——对高相关的多列组合直接列出"最频组合值及其频率",在组合倾斜且相关时提供最精确的等值估算。

用法:CREATE STATISTICS s ON a, b FROM t 默认同时构建 ndistinct 与 dependencies,指定 WITH 选项可加 mcv(如 WITH (mcv = true)? 语法:CREATE STATISTICS s (mcv) ON a, b FROM t)。优化器在查询中的列组合与统计定义匹配时自动使用,无需改写 SQL。典型适用:业务中强相关的列对(region/city、user_type/status),配合倾斜数据效果最明显。

本题考察扩展统计的三件套:dependencies 修"相关性连乘"、ndistinct 修"组合基数"、mcv 修"组合倾斜"。回答时按类型给出适用问题,并说明自动生效机制。

CREATE STATISTICS s_region_city (dependencies, ndistinct) ON region, city FROM users;
ANALYZE users;
#
★★

25. 采样率(Sample Rate)对统计的影响,PG 的 default_statistics_target?

采样率如何影响统计准确性?PostgreSQL 的 default_statistics_target 参数如何控制采样与直方图桶数?

  • default_statistics_target 默认 100,决定直方图桶数与 MCV 条目数上限
  • ANALYZE 采样行数约 300 × target
  • 列级 ALTER TABLE ... ALTER COLUMN SET STATISTICS 精准调优

default_statistics_target(默认 100)同时决定两件事:① 每列统计的"深度"——直方图桶数上限与 MCV 条目数上限都约为 target 个;② ANALYZE 的采样规模——采样行数约为 300 × target 行。两者共同决定统计精度:target 越大,直方图分桶越细、MCV 覆盖的高频值越多、样本越大,倾斜列与范围条件的估算越准;代价是 ANALYZE 更慢、pg_statistic 体积更大、统计对数据变化更敏感(计划可能更易波动)。

调小 target 则相反:ANALYZE 更快、统计更粗,适合海量表控制维护成本。生产建议:全局保持默认或适度调整,对关键倾斜列单独提升——ALTER TABLE t ALTER COLUMN c SET STATISTICS 1000,再 ANALYZE,既精准又避免全局开销放大。

本题考察采样与存储精度的权衡:target 是"统计深度旋钮",采样行数(300×target)与桶数/MCV 数(≈target)是它的两个输出。回答时给出默认值、调大调小影响与列级覆盖技巧。

#
★★

26. 统计信息波动导致的计划抖动(plan instability)与稳定化手段

统计信息波动如何导致执行计划抖动?有哪些计划稳定化手段?

  • 统计微变引发计划在临界点来回切换(扫描方式、join 方法)
  • 参数化查询 generic/custom plan 切换引发的抖动(PG)
  • 稳定化手段:校准统计、固定计划(SPM/Query Store/提示)、阈值调参

计划抖动指同一 SQL 因统计信息微小变化而在多个差异巨大的计划间来回切换(如 Nested Loop ↔ Hash Join、索引扫描 ↔ 全表扫描),常见于:① 数据分布处于两种策略的代价临界点,统计微调即翻转选择;② 参数化查询不同参数值导致 custom plan 与 generic plan 反复切换(PG 在"generic 与 custom 代价相差 10%"阈值附近尤其频繁);③ 统计刷新节奏不规律,autovacuum ANALYZE 触发时计划突变。

稳定化手段分三层:① 校准统计源头——合理设置 default_statistics_target/采样页、建立扩展统计与直方图,让估算贴近真实、减少突变源;② 固定计划——SQL Server 用 Query Store 强制计划、Oracle 用 SPM/SQL Profile、MySQL 用 FORCE INDEX/optimizer_switch、PG 用 pg_hint_plan 或 plan_cache_mode=force_generic_plan 锁定形态;③ 运维治理——控制批量变更节奏、监控计划分布(pg_stat_statements、Query Store 计划版本)并在回退时人工介入。

本题考察计划稳定性工程:先讲抖动机制(临界点切换与 generic/custom 切换),再按"统计源头→计划固定→运维监控"三层给出手段,覆盖诊断与治理全链路。

#

27. 低区分度列参与复合索引是否有价值(选择性 vs 覆盖收益)

低区分度(低基数)列加入复合索引是否有价值?如何权衡选择性与覆盖收益?

  • 低基数列作前缀:过滤 + 去重扫描,但树中大量重复键
  • 低基数列作后缀:提供覆盖收益,避免回表读载荷
  • 价值判断依赖查询负载与写放大,而非单纯选择性

有价值,但价值取决于"位置"与"负载":低基数列放复合索引前部,等值过滤后仍会命中大量行,主要收益是"把全表扫描降级为索引范围扫描 + 回表过滤",若后续高选择性列继续收窄且回表代价可控,仍优于全表;放后部,主要收益是覆盖——该列已在索引键中,SELECT 该列时免回表。单纯按选择性否定低基数列是片面的:对"90% 查询都按该列过滤"的负载,低基数前缀 + 高选择性后缀的组合通常优于任何单列高选择性索引。

代价与取舍:低基数前缀使索引树出现大量重复键,扫描范围大、页利用分散;每加入一列都有写放大与空间成本。实践判断法:用 EXPLAIN 对比"索引方案 vs 全表方案"的实际耗时与行数,观察覆盖收益(Using index)是否兑现,再决定保留前缀过滤还是后缀覆盖。

本题考察选择性之外的索引价值维度:过滤收益与覆盖收益。回答时区分"前缀位置"与"后缀位置"两种价值形态,并用负载与实测判断收尾。