索引膨胀、直方图与采样

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

1. MySQL 中 information_schema 与 OPTIMIZE TABLE 的膨胀检测?

如何用 information_schema 检测 MySQL 表/索引膨胀?OPTIMIZE TABLE 如何回收膨胀空间?

  • information_schema.tables 的 DATA_LENGTH、INDEX_LENGTH 与 DATA_FREE 估算碎片率
  • OPTIMIZE TABLE 重建表回收碎片与更新统计
  • 大表执行的额外空间与低峰窗口

检测:information_schema.tables 提供 DATA_LENGTH(聚簇索引页占用)、INDEX_LENGTH(二级索引页占用)与 DATA_FREE(表中空闲/碎片空间),碎片率 ≈ DATA_FREE / (DATA_LENGTH + INDEX_LENGTH),超过 20%~30% 或 DATA_FREE 绝对量很大时值得整理;也可对比 DATA_LENGTH 与统计行数×行大小估算膨胀程度。

回收:OPTIMIZE TABLE 对 InnoDB 执行"表重建 + 统计刷新"——重写聚簇索引与全部二级索引,合并碎片页、消除空洞、压缩文件大小;8.0 下走在线 DDL(允许并发 DML,构建期间记录变更并重放),但仍需额外临时空间(重建文件与日志)且耗时较长。注意事项:大表执行前确认磁盘余量、选择低峰窗口;对 MyISAM 则是整表重写并加锁;碎片率低时执行纯属浪费。

本题考察膨胀检测与回收的完整链路:information_schema 的字段计算碎片率,OPTIMIZE TABLE 的机制与成本,以及执行时机判断。

#
★★★

2. REINDEX CONCURRENTLY 的工作原理,避免长时间锁表?

REINDEX CONCURRENTLY 如何在不长时间阻塞读写的情况下重建索引?其原理与限制?

  • 两阶段构建:先按快照扫描建索引,再合并构建期间的新增变更
  • 期间允许 DML,只短暂需要验证锁
  • 限制:额外磁盘/CPU、失败留下无效索引、锁排队

REINDEX CONCURRENTLY 的原理是不持有排他锁、分两阶段构建新索引:第一阶段以当前快照扫描表数据构建新索引(在目录中注册为"构建中"索引),此时并发 DML 照常进行,变更通过 WAL 记录;第二阶段重新扫描构建期间发生变更的元组(基于第一阶段快照边界做增量对账),把新增/更新/删除合并进新索引,处理与并发写竞争的死元组;完成后短暂验证并替换旧索引。整个过程读写基本不阻塞,适合大表在线重建。

限制:① 需要额外磁盘与 CPU(两阶段扫描,耗时约为普通 REINDEX 的两倍);② 若中断(崩溃、进程终止),会留下 INVALID 状态的无用索引,需手工 DROP INDEX 清理;③ CREATE INDEX CONCURRENTLY 不能在事务块内使用(REINDEX CONCURRENTLY 从 PG 12 起允许在事务块内);④ 与其他 DDL 在锁队列中互等,可能长时间等待。

本题考察在线重建索引的机制与边界:两阶段构建与增量合并原理、允许 DML 的锁模型、失败残留与事务块限制。

#
★★★

3. VACUUM FULL 与 REINDEX 的取舍,表膨胀 vs 索引膨胀?

VACUUM FULL 与 REINDEX 各自解决什么问题?表膨胀与索引膨胀如何取舍?

  • 普通 VACUUM 只标记可复用、不归还空间;VACUUM FULL 重写表彻底回收
  • REINDEX 只重建索引消除索引膨胀
  • 取舍:按膨胀来源选择,优先 autovacuum 预防

普通 VACUUM 只把死元组标记为可复用空间,不把空间归还操作系统,表文件大小不变;VACUUM FULL 用新表文件重写整表(把有效行搬进紧凑的新文件),彻底消除表级膨胀并归还磁盘空间,代价是需要排他锁与约两倍临时空间,重写表的同时其索引也被重建。REINDEX 只针对索引:重建索引文件消除索引膨胀(死索引项与页分裂遗留的碎片),可用 CONCURRENTLY 在线执行。

取舍原则:先定位膨胀来源——若表主体 dead 比例高(pgstattuple 显示),用 VACUUM FULL(或分区轮换);若表主体紧凑但索引页利用率低(pgstatindex 显示 avg_leaf_density 偏低),用 REINDEX(优先 CONCURRENTLY);两者都严重时可先 VACUUM FULL 顺带重建索引。生产首选预防:autovacuum 及时回收、fillfactor 预留页内空间,应急再 FULL。

本题考察两类膨胀的处置差异:VACUUM FULL 解决表膨胀(重写+锁),REINDEX 解决索引膨胀(可在线),并按膨胀来源取舍。

#
★★★

4. 索引膨胀(Index Bloat)的原因,长事务、删除不回收、MVCC 版本?

索引膨胀的根因有哪些?长事务、删除操作与 MVCC 版本如何共同导致索引膨胀?

  • MVCC 下更新/删除产生死元组与死索引项
  • 长事务卡住回收 horizon,死版本无法清理
  • VACUUM 回收后页不收缩,膨胀不可逆需 REINDEX

索引膨胀的根因链条:MVCC 下 UPDATE/DELETE 不物理删除行,而是留下死元组,对应的索引项也保留为死项;VACUUM 清理死元组并回收索引死项空间,但索引页回收后不会"收缩合并"——文件大小不回缩,只留出可复用空洞,因此索引文件只会增长或持平。

三个放大器:① 长事务/长查询——只要存在最老活跃事务,其快照之前的死版本都不能回收(回收 horizon 被卡住),膨胀持续累积;② 高频 UPDATE(尤其改索引列)——死索引项产生速率远大于回收速率;③ autovacuum 配置保守(阈值大、延迟长)导致回收滞后。此外页分裂与随机插入也会降低页利用率。处理:先解决长事务与回收配置,再用 REINDEX CONCURRENTLY 压缩索引文件;根治需控制写放大与事务时长。

本题考察膨胀的形成机制:MVCC 死项、回收 horizon 卡顿与页不收缩三重因素,以及"先治理事务再 REINDEX"的处理顺序。

#
★★★

5. 索引重建的方法,REINDEX(PG)、ALTER TABLE ... REBUILD(Oracle/MySQL)?

PostgreSQL 的 REINDEX 与 MySQL/Oracle 的索引重建方法各是什么?如何选择?

  • PG:REINDEX(默认持锁)与 REINDEX CONCURRENTLY(在线)
  • MySQL:DROP+ADD INDEX(8.0 INPLACE 在线)或 OPTIMIZE TABLE 重建整表
  • Oracle:ALTER INDEX ... REBUILD [ONLINE]

PostgreSQL:REINDEX INDEX/TABLE/DATABASE 重建索引,默认持排他锁一次性重建,生产用 REINDEX INDEX ... CONCURRENTLY 在线重建(分两阶段执行),可带 TABLESPACE 迁移。MySQL:没有 REINDEX 语句,重建索引常用两种方式——ALTER TABLE ... DROP INDEX + ADD INDEX(8.0 下二级索引增删支持 ALGORITHM=INPLACE 在线执行,不阻塞 DML),或 OPTIMIZE TABLE(重建整表含所有索引并回收碎片);注意 ALTER TABLE 期间需要元数据锁。Oracle:ALTER INDEX idx REBUILD 重建索引,REBUILD ONLINE 允许 DML(需额外空间与内部日志),只重排索引数据不重建表。

选择:仅索引膨胀 → PG 用 REINDEX CONCURRENTLY、MySQL 用 DROP+ADD(INPLACE)、Oracle 用 REBUILD ONLINE;表级碎片 → MySQL 用 OPTIMIZE、PG 用 VACUUM FULL;大表必须评估在线性、临时空间与低峰窗口。

本题考察三库索引重建的语法与在线性:PG 的 CONCURRENTLY、MySQL 的 INPLACE、Oracle 的 ONLINE,并按场景选择。

#
★★★

6. 索引膨胀的预防,autovacuum、fillfactor、统计信息?

如何预防索引膨胀?autovacuum 配置、fillfactor 与统计信息各起什么作用?

  • autovacuum 及时回收死元组,防滞后累积
  • fillfactor 预留页内空间减少页分裂(索引默认 90)
  • 监控统计(n_dead_tup、页密度)驱动预防性 REINDEX 计划

预防索引膨胀三道防线:① autovacuum 调优——确保自动回收跟得上死元组产生速度:调整 autovacuum_vacuum_scale_factor/阈值与 naptime 匹配写负载,监控 pg_stat_user_tables 的 n_dead_tup 与 last_vacuum,防止"回收滞后累积";② fillfactor——PG 索引默认 fillfactor=90,页内预留 10% 空位,使页内更新不立即触发页分裂,显著降低碎片与死项率;表 fillfactor 默认 100,热更新表可降到 70~80 配合 HOT 更新;代价是每页容量下降、索引略大;③ 监控与预案——用 pgstatindex/pgstattuple 定期查页利用率,结合索引大小增长曲线制定周期性 REINDEX CONCURRENTLY 计划(如每周/每月低峰),并排查长事务(xact_start 极老的会话)这一膨胀放大器。

本题考察膨胀预防的体系化手段:回收速度(autovacuum)、页内缓冲(fillfactor)、监测预案(统计与页密度),三层配合。

#
★★★

7. MySQL ALGORITHM=INPLACE 的 REBUILD?

MySQL 的 ALGORITHM=INPLACE 重建(REBUILD)如何工作?与 COPY 相比有何优势与限制?

  • INPLACE 在原表上构建新结构,期间并发 DML 由 row log 重放
  • 与 COPY(阻塞 DML、近两倍空间)的对比
  • 限制:部分操作退化为 COPY、临时空间需求、可强制报错防降级

ALGORITHM=INPLACE 让 MySQL 在不复制整表数据文件的情况下完成结构变更:对索引重建(DROP/ADD INDEX),引擎扫描聚簇索引行生成新二级索引,期间允许并发 DML——在线 DDL 把变更写入内存/磁盘的 row log(变更缓冲),构建完成后重放增量使新结构与数据一致;整个过程不阻塞读写(8.0 默认),代价是额外临时空间与更长构建时间。对比 ALGORITHM=COPY:COPY 创建新表复制所有行,期间 DML 被阻塞,磁盘占用近两倍。

限制:并非所有操作都支持 INPLACE(如 FULLTEXT 索引、部分压缩页操作、8.0 修改主键仍走 COPY),不支持时自动降级为 COPY——可显式写 ALGORITHM=INPLACE 让不支持时报错而不是静默降级;临时空间不足会失败;大表构建前评估磁盘余量与低峰窗口。

本题考察在线 DDL 的实现与边界:INPLACE 的构建+重放机制、与 COPY 的对比、以及强制防降级的技巧。

#
★★★

8. MySQL ANLAYZE TABLE 与 REBUILD?

MySQL 的 ANALYZE TABLE 与重建(REBUILD)有什么关系?何时需要两者配合?

  • ANALYZE 只刷新统计,REBUILD 重写数据与索引
  • 先 REBUILD 再 ANALYZE,让统计反映整理后的物理布局
  • OPTIMIZE 附带更新统计,显式 ANALYZE 可控制采样质量

ANALYZE TABLE 与 REBUILD 是两回事:ANALYZE 只重新采样并刷新统计信息(行数、基数、页数)写入持久化系统表,不触碰数据文件与索引文件;REBUILD(OPTIMIZE TABLE 或 ALTER TABLE 重建)重写数据页与索引页,回收碎片、压缩空间。

二者配合场景:大表长期更新后既存在物理碎片又统计陈旧——先 REBUILD(物理整理),再 ANALYZE(让统计基于整理后的物理布局,页数与行数估算更准确);OPTIMIZE TABLE 内部会附带更新统计,但显式 ANALYZE 可控制采样质量(如临时调大 innodb_stats_persistent_sample_pages 再分析)。顺序与窗口:REBUILD 开销大(磁盘、锁、时间),ANALYZE 便宜,低峰先重后分。

本题考察两类维护动作的分工与顺序:ANALYZE 是"统计层面"、REBUILD 是"物理层面",先整理后统计保证数据一致。

#
★★★

9. MySQL OPTIMIZE TABLE?

MySQL 的 OPTIMIZE TABLE 具体做什么?适用场景、锁行为与注意事项?

  • 对 InnoDB 执行表重建(FORCE)+ 统计刷新
  • 8.0 走在线 DDL,但需临时空间与低峰窗口
  • 碎片率低时无收益,分区表与 MyISAM 的差异

OPTIMIZE TABLE 对 InnoDB 执行"表重建(ALTER TABLE ... FORCE)+ 统计刷新":重写聚簇索引与全部二级索引,合并碎片页、消除 DATA_FREE 空洞、压缩文件大小,同时重算统计信息。执行行为:8.0 下走在线 DDL(允许并发 DML,构建期间记录变更并重放),但需要额外临时空间(重建文件与变更日志),大表耗时较长;对 MyISAM 则是整表重写并加锁。

注意事项:① 碎片率高才值得执行(DATA_FREE 占比大),小表/无碎片执行纯属浪费;② 执行窗口选低峰,预留磁盘余量;③ 8.0 对分区表的 OPTIMIZE 受限(需逐分区处理);④ 若只想整理索引,DROP+ADD INDEX 更轻量;⑤ 完成后观察 information_schema 的 DATA_FREE 是否下降验证效果。

本题考察 OPTIMIZE 的完整语义:做什么(重建+统计)、锁与资源行为、适用条件与验证方法。

#
★★★

10. MySQL innodb_index_stats?

MySQL 的 mysql.innodb_index_stats 表的作用与内容?如何用于排查索引统计问题?

  • 每索引多条记录:n_diff_pfx 系列前缀基数、n_leaf_pages、size、sample_size、last_update
  • 前缀基数是优化器估算复合索引选择率的依据
  • 排查:统计陈旧、采样不足、区分度判断

mysql.innodb_index_stats 是 InnoDB 持久化索引统计的系统表:每个索引含多条记录,stat_name 字段包括 n_diff_pfx01(索引第 1 列不同值数)、n_diff_pfx02(前两列组合不同值数)等前缀基数系列——直接决定优化器对复合索引选择率的估算;还有 n_leaf_pages(叶子页数)、size(索引总页数);stat_value 为对应值,sample_size 为采样页数,last_update 为更新时间。

排查价值:① 对比 n_diff_pfx01 与表行数(n_rows)判断列区分度,解释"为何优化器不选该索引"(前缀基数过低);② 检查 last_update 是否过旧(统计陈旧)或 sample_size 过小(采样不足);③ 多列基数序列可验证复合索引最左前缀的选择率。修复:调大 innodb_stats_persistent_sample_pages 后重新 ANALYZE。

本题考察索引统计的物理落点:n_diff_pfx 系列如何支撑选择率估算,以及用 last_update/sample_size 排查统计质量。

#
★★★

11. PostgreSQL CONCURRENTLY 的限制?

PostgreSQL 的 CONCURRENTLY(CREATE/REINDEX INDEX CONCURRENTLY)有哪些限制与注意点?

  • 事务块限制:CREATE INDEX CONCURRENTLY 不能在事务块内
  • 执行成本:两阶段扫描、额外磁盘与 CPU
  • 失败残留 INVALID 索引与 DDL 锁排队

CONCURRENTLY 系列的限制:① 事务块限制——CREATE INDEX CONCURRENTLY 不能在事务块中执行(它内部提交多个事务),REINDEX CONCURRENTLY 从 PG 12 起允许在事务块内使用;② 执行成本更高——两阶段全表扫描(构建 + 增量对账),耗时约为普通索引的两倍,消耗更多 CPU 与磁盘;③ 失败残留——进程中断或冲突会留下 INVALID 状态的无用索引(占用空间、拖慢后续 DDL),需手工 DROP INDEX;④ 锁交互——构建期间允许 DML,但与其他需要排他锁的 DDL 在锁队列中互等,可能长时间等待;⑤ 对象限制——对分区索引等对象的支持随版本演进(旧版本不支持部分场景),需查阅版本文档。

生产使用:执行前检查磁盘余量、避免人工中断、监控日志,失败后清理无效索引再重试。

本题考察 CONCURRENTLY 的边界条件:事务块限制、成本翻倍、失败残留与锁排队,覆盖使用前必须知晓的注意点。

#
★★★

12. PostgreSQL REINDEX 失败回滚?

PostgreSQL 的 REINDEX(含 CONCURRENTLY)失败时如何回滚?对数据与索引有何影响?

  • 普通 REINDEX 事务内原子回滚,原索引完好
  • REINDEX CONCURRENTLY 分阶段提交,失败留下 INVALID 新索引
  • 常见失败原因:磁盘空间不足、中断

普通 REINDEX 是单事务操作:重建期间原索引被锁,若中途失败(磁盘满、崩溃),事务回滚,原索引保持可用且完整——这是它安全但锁表的原因。REINDEX CONCURRENTLY 则分多个内部阶段提交,失败不整体回滚:已注册的新索引可能停留在 INVALID 状态(原索引不受影响、查询照常走原索引),需要 DROP INDEX 清理后再重新执行;若失败发生在替换阶段之后,新索引已生效。

常见失败原因与应对:磁盘空间不足(CONCURRENTLY 需要约两倍索引空间,执行前必须检查);人工中断(客户端断开)后遗留的 INVALID 索引需排查清理(查询 pg_class 的 relispopulated/索引状态);避免在低磁盘余量或高负载时执行。

本题考察 REINDEX 的失败语义差异:普通版事务原子回滚、CONCURRENTLY 分阶段残留,以及磁盘与中断两类典型风险。

#
★★★

13. PostgreSQL pgstattuple 扩展?

PostgreSQL 的 pgstattuple 扩展提供哪些功能?如何用它检测表/索引膨胀?

  • pgstattuple:表级死元组占比与空间统计;pgstattuple_approx 快速近似
  • pgstatindex:索引页利用率、avg_leaf_density、leaf_fragmentation
  • 使用注意:需 CREATE EXTENSION、成本高需低峰执行

pgstattuple 扩展提供物理层面分析:pgstattuple(表名) 返回 tuple_count、dead_tuple_count、tuple_percent(有效元组占比)、free_percent、表大小等,可精确判断表膨胀(dead 比例高说明 VACUUM 滞后);对超大表可用 pgstattuple_approx 以采样方式快速近似。pgstatindex(索引名) 返回索引页利用信息:avg_leaf_density(叶子页平均密度,接近 90% 正常、明显偏低说明膨胀/碎片)、leaf_fragmentation(碎片率)、dead_items 等,是诊断索引膨胀最直接的依据;另有 pgstathashindex 用于哈希索引。

使用注意:pgstattuple 系函数需要扫描表/索引,成本高,应在低峰对目标对象执行;扩展需 CREATE EXTENSION pgstattuple 安装;函数对无权限用户隐藏(需表属主或超级用户)。

本题考察膨胀诊断工具:pgstattuple 测表、pgstatindex 测索引、近似函数与成本控制,以及安装与权限注意点。

#
★★

14. VACUUM 与 REINDEX 的差异?

PostgreSQL 的 VACUUM 与 REINDEX 有何区别?各自解决什么问题,能否互相替代?

  • VACUUM:回收死元组空间供复用,不压缩文件、不合并索引页
  • REINDEX:重建索引文件、压缩索引消除膨胀
  • VACUUM FULL 合体:重写表并重建索引,需排他锁

VACUUM 与 REINDEX 目标不同:VACUUM(普通)清理死元组与死索引项,把它们占用的空间标记为可复用(供新插入/更新使用),但表与索引文件的大小不缩小——空间只"内部流转",不归还操作系统;REINDEX 重建索引文件,把稀疏的索引页合并压缩,文件物理缩小,消除索引膨胀与碎片。二者不可互相替代:VACUUM 解决"死元组堆积"与事务回卷(xid 冻结),REINDEX 解决"索引页利用率低";VACUUM 清理索引死项后索引文件仍保持原有大小(空洞可复用),长期看需 REINDEX 压缩。

VACUUM FULL 是两者合体:重写整表(消除表膨胀)并重建全部索引(消除索引膨胀),代价是排他锁与两倍空间。选择:死元组多但页密度尚可 → VACUUM;索引页密度低 → REINDEX CONCURRENTLY;表主体膨胀 → VACUUM FULL 或分区轮换。

本题考察两个维护动作的语义边界:回收 vs 压缩、不可替代性、以及 VACUUM FULL 的合体定位。

#
★★

15. autovacuum 与索引?

autovacuum 如何处理索引?它对索引膨胀的回收能力与局限?

  • autovacuum 清理索引死项并标记空间可复用,不压缩文件
  • 触发基于表死元组数,对索引膨胀无感知
  • 索引膨胀需独立监控与 REINDEX 计划

autovacuum 对索引的处理限于"清理":每次自动 VACUUM 会遍历索引并删除死索引项(死元组对应的键),回收的页内空间标记为可复用供后续插入使用;它不会压缩索引文件、不合并稀疏页,因此"索引文件大小"只会增长或持平,不会因 autovacuum 缩小——autovacuum 无法替代 REINDEX。

且 autovacuum 的触发基于表级死元组统计(n_dead_tup 超过阈值),对索引膨胀无直接感知:即使索引已严重膨胀,只要表死元组不多,autovacuum 也不会频繁运行。因此生产上需要独立的索引膨胀监控(pgstatindex 页密度、索引大小/表大小比)与定期 REINDEX CONCURRENTLY 计划;autovacuum 调优(阈值、cost limit)保证清理跟得上产生速度,是防止"死项持续累积"的基础。

本题考察 autovacuum 的索引职责边界:只清理不压缩、触发与索引膨胀脱钩,引出独立监控的必要性。

#
★★

16. pgstat 索引膨胀查询?

如何用 pg_stat_* 视图查询与监控索引膨胀?

  • pg_stat_user_indexes 的 idx_scan/idx_tup_read/idx_tup_fetch 使用率统计
  • pg_stat_user_tables 的 n_dead_tup 与 last_vacuum 回收情况
  • 与 pgstatindex 页密度结合形成监控闭环

基于 pg_stat 视图的膨胀监控思路:① 回收情况——pg_stat_user_tables 的 n_dead_tup(当前死元组数)与 last_autovacuum/last_vacuum:n_dead_tup 长期高说明回收滞后;② 索引使用率——pg_stat_user_indexes 的 idx_scan(扫描次数)、idx_tup_read、idx_tup_fetch(回表次数):长期 idx_scan=0 的索引大概率冗余,回表比例高(idx_tup_fetch/idx_tup_read 大)提示覆盖不足或列序不佳;③ 膨胀估算——把索引大小(pg_relation_size)与使用频率、页密度结合:定期采集 pg_stat_user_indexes 快照 + 对疑似对象周期性执行 pgstatindex 看 avg_leaf_density,或对比"索引大小增长曲线"与写入量。

注意:pg_stat 只提供统计不提供物理页信息,最终确认仍需 pgstattuple/pgstatindex(低峰执行);综合"低使用 + 大体积"或"高写入 + 密度低"可生成 REINDEX 候选清单。

本题考察视图级监控方法:使用率、回收状态与物理密度三路信号,形成"疑似→确认→处置"的闭环。

#
★★

17. MySQL 8.0 直方图(SAMPLING HISTOGRAM)的语法与使用?

MySQL 8.0 直方图(SAMPLING HISTOGRAM)的语法是什么?如何创建、查看与删除?

  • 创建/更新:ANALYZE TABLE ... UPDATE HISTOGRAM ON 列 WITH N BUCKETS
  • 查看:information_schema.COLUMN_STATISTICS 的 JSON 字段
  • 删除:DROP HISTOGRAM;单列、不随 DML 自动更新

创建/更新:ANALYZE TABLE t UPDATE HISTOGRAM ON 列1, 列2 WITH 100 BUCKETS(N 省略默认 100,最大 1024);删除:ANALYZE TABLE t DROP HISTOGRAM ON 列1;查看:SELECT * FROM information_schema.COLUMN_STATISTICS WHERE TABLE_NAME='t'——HISTOGRAM 字段为 JSON(含 bucket 数组、histogram-type、sampling-rate、last-updated)。

注意点:① 只能建在单列上(不支持多列直方图);② 数据分布变化后需手动重新 UPDATE(不随 DML 自动更新,可结合定时任务);③ 适合无索引列的等值/范围/IN 估算、JOIN 列与倾斜数据;④ 有索引的列优化器优先用索引统计,直方图主要补非索引列;⑤ 每列最多一个直方图,重复 UPDATE 即覆盖。

本题考察直方图的三类 SQL 语法:UPDATE/DROP HISTOGRAM 与 COLUMN_STATISTICS 查看,以及"单列、手动刷新"两个关键约束。

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

18. PostgreSQL 中 default_statistics_target 参数控制直方图桶数?

default_statistics_target 如何控制直方图桶数与采样?设置过大过小的影响?

  • 桶数与 MCV 条目数上限约等于 target(默认 100)
  • 采样行数约 300 × target
  • 列级 ALTER TABLE ... ALTER COLUMN SET STATISTICS 精准调优

default_statistics_target(默认 100)同时约束:① 每列直方图桶数的上限(约 target 个桶)与 MCV 列表长度(约 target 项);② ANALYZE 的采样规模(约 300 × target 行),两者共同决定统计精度。调大(如 1000):直方图更细、MCV 覆盖更多高频值,倾斜列与范围条件的估算明显改善;代价是 ANALYZE 变慢、pg_statistic 体积增大、统计更敏感(计划可能更易波动)。调小则相反,适合超宽表/超大库降低 ANALYZE 成本。

生产建议:全局保持默认或适度调整,对关键倾斜列单独提升——ALTER TABLE t ALTER COLUMN c SET STATISTICS 1000,再 ANALYZE——既精准又避免全局成本放大;该列级设置同样影响该列的采样行数与桶数。

本题考察 target 的双重控制(桶数+采样)与精度成本权衡,以及列级覆盖的精准调优技巧。

#
★★

19. 直方图(Histogram)的应用,频率直方图(Frequency Histogram)与等高直方图(Height-Balanced Histogram)?

频率直方图与等高直方图分别适用什么场景?优化器如何用它们估算?

  • 频率直方图:少量离散值、等值估算精确(MySQL singleton、PG MCV)
  • 等高直方图:大量不同值的范围估算(桶内插值)
  • 两者互补:PG 用 MCV 兜高频、直方图兜中低频

频率直方图把每个(或每个高频)不同值及其频率单独记录:MySQL 8.0 的 singleton 类型、PG 的 MCV 列表都属于这类,适合取值个数少(如性别、状态码)的列,等值条件可精确命中频率、估算几乎零误差。等高直方图把取值域切成行数大致相等的桶,只记录边界(真实出现过的值):MySQL 8.0 的 equi-height 类型、PG 的 histogram_bounds 属于这类,适合大量不同值(价格、年龄、时间)的列,范围条件(BETWEEN、>、<)按"落在桶内的比例"插值估算,倾斜数据下仍比均匀假设准。

两者互补:PG 先用 MCV 覆盖高频值,其余值落入等高直方图;MySQL 由 HISTOGRAM 的 histogram-type 字段标识类型。优化器按条件类型选择:等值优先 MCV/singleton,范围用等高桶插值;桶数越多估算越细,但存储与更新成本上升。

本题考察两类直方图的适用学:频率型精于等值、等高型精于范围,以及"MCV 兜高频"的互补设计。

#
★★

20. 等高直方图的边界(Boundary)选择与最频值(Most Frequent Values)?

等高直方图的边界如何选择?最频值(MCV)与直方图的关系?

  • 边界为真实数据值,每桶行数近似相等
  • 高频值单独放 MCV 列表避免占满桶
  • 估算时先查 MCV 命中、未命中再按直方图插值

等高直方图的构建目标:把排序后的取值域切成"每桶包含近似相等行数"的桶,桶边界必须是数据中真实存在的值(如 PG 的 histogram_bounds 数组,相邻边界间为一桶)。由于等高原则,条件落在某桶内时按该桶行数×位置比例插值估算。最频值(MCV)与直方图的分工:极高频的值若放入直方图会占用大量桶,因此单独放入 MCV 列表(记录值与频率),直方图只承载"普通频率"的值——PG 的 ANALYZE 先统计 MCV(约 target 个高频值),剩余值再切等高桶;估算时先查 MCV 命中,未命中再按直方图插值。

MySQL 8.0 的 equi-height 直方图也把高频值计入桶结构。理解"MCV 兜高频、直方图兜中低频"的分工,即可解释为何倾斜列在 PG 中的估算远准于均匀假设,也解释了为何 target 越大 MCV 覆盖越全。

本题考察等高直方图的构建细节:真实值边界、等高原则、MCV 与直方图的分工与估算顺序。

#
★★

21. MySQL ANALYZE TABLE ... UPDATE HISTOGRAM?

MySQL 的 ANALYZE TABLE ... UPDATE HISTOGRAM 语法细节有哪些?采样、桶数与注意事项?

  • WITH N BUCKETS:默认 100、上限 1024
  • 采样构建(sampling-rate),NULL 不入直方图
  • 单列、手动刷新、非索引列收益最大

语法:ANALYZE TABLE t UPDATE HISTOGRAM ON 列名 [WITH n BUCKETS];n 默认 100、上限 1024,桶数越大分布刻画越细、构建与存储成本越高。实现:MySQL 对列值采样(直方图 JSON 中记录 sampling-rate),生成 singleton 或 equi-height 类型直方图存入数据字典,可从 information_schema.COLUMN_STATISTICS 查看。

注意事项:① NULL 值不进入直方图(NULL 语义由其他机制处理);② 只支持单列,多列组合需依赖索引统计或预估;③ 数据分布变化后必须手动重新执行(DML 不触发更新),可结合定时任务;④ 对已建索引的列收益有限(优化器优先索引统计),直方图主要服务非索引列、JOIN 列与倾斜场景;⑤ 每列最多一个直方图,重复 UPDATE 覆盖。

本题考察直方图维护的细节参数:桶数默认值与上限、采样与 NULL 语义、手动刷新与适用边界。

ANALYZE TABLE orders UPDATE HISTOGRAM ON status WITH 64 BUCKETS;
SELECT JSON_EXTRACT(HISTOGRAM, '$.sampling-rate') FROM information_schema.COLUMN_STATISTICS
WHERE TABLE_NAME = 'orders';
#
★★

22. MySQL 直方图的 SQL 语法?

MySQL 8.0 直方图相关的完整 SQL 语法有哪些?(创建、查看、删除)?

  • 创建/更新:UPDATE HISTOGRAM;删除:DROP HISTOGRAM
  • 查看:information_schema.COLUMN_STATISTICS(JSON 只读)
  • 权限与只读约束

MySQL 8.0 直方图的三类 SQL:① 创建/更新——ANALYZE TABLE 表名 UPDATE HISTOGRAM ON 列1 [, 列2 ...] [WITH n BUCKETS],一次可更新多列、各自带桶数;② 删除——ANALYZE TABLE 表名 DROP HISTOGRAM ON 列1 [, 列2 ...];③ 查看——SELECT * FROM information_schema.COLUMN_STATISTICS WHERE SCHEMA_NAME='库名' AND TABLE_NAME='表名',HISTOGRAM 列为 JSON(含 bucket 数组、histogram-type、sampling-rate、last-updated)。

注意事项:直方图数据只读——由 ANALYZE 生成,不能直接 UPDATE 该 JSON;UPDATE 与 DROP 需要表的 ANALYZE 权限;一次 UPDATE HISTOGRAM ON 多列与逐列执行等效;删除后优化器对该列回退到均匀假设估算,可能引起计划变化,删除前先评估影响。

本题考察直方图管理的完整 SQL 面:创建/更新、删除、查看三入口,以及 JSON 只读与权限约束。

#
★★

23. PostgreSQL pg_stats.histogram_bounds?

pg_stats.histogram_bounds 字段的含义与用途?如何解读?

  • 升序真实值边界数组,长度 = 桶数 + 1
  • 桶行数近似相等(剔除 NULL 与 MCV 命中部分后均分)
  • 解读:范围条件插值估算、与 MCV 配合人工复算

pg_stats.histogram_bounds 是等高直方图的边界数组:元素按值升序排列,都是列中真实出现过的值,数组长度 = 桶数 + 1,相邻两元素之间为一个桶。桶的语义:所有桶包含(约)相等的行数——总行数剔除 NULL 与 MCV 命中部分后均分到各桶,因此估算范围条件(如 col BETWEEN x AND y)时,优化器找出 x、y 落在的桶,按桶内位置比例插值得到行数。

解读技巧:把 histogram_bounds[0] 与最后一个元素看作"非 MCV 值的分布范围";若边界数量很少或缺失(target 过小或列几乎都是高频值),说明该列统计较粗;结合 most_common_vals/most_common_freqs 可手工复算一条查询的估算行数,用于验证优化器行为与定位统计失真。

本题考察直方图边界的物理语义:真实值、桶数+1、等高均分,以及手工复算的排查技巧。

#
★★

24. PostgreSQL 直方图与优化器?

PostgreSQL 优化器如何使用直方图估算?哪些场景直方图不起作用?

  • 范围条件插值估算,等值优先 MCV 再按剩余 distinct 推算
  • 失效场景:值超出边界、多列相关、JOIN 选择率、函数包裹列
  • 对策:扩展统计、表达式索引

PG 优化器用直方图的方式:范围条件(>、<、BETWEEN)按 histogram_bounds 插值——确定边界落在哪些桶,按桶内位置比例×每桶行数估算;等值条件不直接查直方图桶频次,而是先查 MCV(命中即用其频率),未命中则用"剩余行数/剩余 distinct 估算"推算(直方图不存储逐值频度)。

直方图无效或失效的场景:① 查询值落在直方图最小/最大边界之外(超出采样范围),按"边界外无行"或边界桶比例处理,可能高估/低估;② 多列组合条件(WHERE a AND b)——直方图是单列的,相关列需 CREATE STATISTICS 扩展统计;③ JOIN 选择率——PG 默认不用直方图估算 join 结果基数(用 MCV 与均匀分布),误差大时可建扩展统计;④ 函数/表达式包裹列(WHERE func(col)=x)——无表达式直方图,除非建立表达式索引并 ANALYZE 其统计。

本题考察直方图的适用边界:范围插值、等值走 MCV,以及四类失效场景与对应补救手段。

#

25. ANALYZE 的随机块采样机制与 default_statistics_target 如何影响统计准确性?采样过少会引发什么误估?

ANALYZE 的随机块采样机制是什么?default_statistics_target 如何影响准确性?采样过少会引发哪些误估?

  • 块级随机采样:随机选页、读整页统计,物理相关可能引入偏差
  • 采样行数约 300×target,target 越大越准
  • 采样过少的后果:漏高频值、直方图粗糙、n_distinct 低估、相关性偏差

PostgreSQL 的 ANALYZE 采用块级随机采样:从表的所有数据页中随机选取一部分页(页数由目标采样行数推算),读取选中页内的全部行做统计——相比逐行随机采样,页级采样读取 I/O 更高效,但同一页内的行在物理分布上相关(如按时间插入的数据集中页内),可能引入偏差。采样规模由 default_statistics_target 控制:采样行数约 300 × target(默认 100 → 约 3 万行),target 越大样本越大、统计越准。

采样过少的后果:① 低占比但关键的高频值可能没被采到,MCV 漏项,等值估算按均匀假设严重失准;② 直方图边界粗糙,范围估算误差放大;③ n_distinct 对高基数列系统性低估(样本中不同值比例外推不足),导致选择率高估、优化器偏向全表扫描;④ correlation 估算偏差影响索引扫描代价。缓解:列级调大 STATISTICS、对倾斜列建扩展统计、确保 ANALYZE 频率匹配数据变化速率。

本题考察采样机制与精度的关系:块级采样的原理与偏差来源、300×target 的规模控制、采样不足的四类误估后果。