如何通过Analyze Table更新采样优化MySQL多列复合索引的基数统计?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1225个文字,预计阅读时间需要5分钟。
MySQL的优化器依赖于列的基数(cardinality)来选择索引。复合索引的基数统计不是针对每个组合值单独计数,而是基于采样估算的——默认情况下,仅采样10个数据页(由innodb_stats_sample_pages控制),仅对前缀列做统计。如果表很大、数据倾斜严重,或复合索引列的顺序与查询条件不匹配,那么ANALYZE TABLE可能无法准确更新你关心的列组合的统计值。
实操建议:
- 先查当前统计是否真的被更新:
SHOW INDEX FROM your_table;关注
Cardinality列,对比ANALYZE TABLE前后变化 - 确认采样页数是否足够:运行
SELECT @@innodb_stats_sample_pages;,小表可保持默认,大表(千万级以上)建议设为
100或200(需在配置文件或会话中设置并重启生效) -
ANALYZE TABLE不触发即时重算——InnoDB 实际使用的是持久化统计信息(innodb_stats_persistent = ON默认开启),它只在表首次打开、DDL 后或显式调用时刷新,但不会自动重采样所有列组合
复合索引的基数到底统计哪几列
MySQL 对复合索引 (a, b, c) 的基数统计是分层的:只统计 a、(a,b)、(a,b,c) 这三个前缀组合,**不会统计 b 单独、(b,c) 或 c 的基数**。这意味着如果你的查询是 WHERE b = ? AND c = ?,优化器根本看不到 (b,c) 的区分度,只能按全表扫描或退化到其他索引处理。
实操建议:
- 检查实际查询条件与索引列顺序是否对齐;若常用
WHERE b = ? AND c = ?,应建索引为(b, c)或(b, c, a),而非依赖(a, b, c)的中间前缀 - 用
SELECT seq_in_index, column_name, collation, cardinality FROM information_schema.STATISTICS WHERE table_name = 'your_table' AND index_name = 'your_index' ORDER BY seq_in_index; 确认哪些前缀组合有基数记录
- 避免“一索引打天下”思维——复合索引不是万能覆盖,它的统计粒度天然受限于前缀结构
什么时候 ANALYZE TABLE 会失效或不准
常见失效场景包括:表刚批量插入大量新数据但未 ANALYZE;使用 LOAD DATA INFILE 或 INSERT ... SELECT 后未手动触发;行格式为 COMPRESSED 且采样页包含压缩失败块;以及最隐蔽的一种:启用了 innodb_stats_auto_recalc = OFF,导致 DDL 后也不自动更新统计。
实操建议:
- 批量写入后必须显式执行
ANALYZE TABLE your_table;,不能依赖自动机制
- 检查自动重算是否开启:
SELECT table_name, innodb_stats_auto_recalc FROM information_schema.INNODB_TABLES WHERE table_name = 'your_table';
- 若发现
Cardinality长期为NULL或明显偏低(比如WHERE status = 'active'返回 95% 行数,但基数却显示 100),大概率是采样不足或数据分布极端偏斜,此时可考虑用直方图(MySQL 8.0+)补充:ANALYZE TABLE your_table UPDATE HISTOGRAM ON status;
直方图能替代 ANALYZE TABLE 吗
不能。直方图(UPDATE HISTOGRAM)是对单列值分布的精细刻画,用于优化等值/范围查询的选择率估算;而 ANALYZE TABLE 更新的是索引前缀的基数,影响索引选择逻辑。二者解决的问题维度不同,且直方图不作用于复合索引列组合。
实操建议:
- 对高区分度但查询频繁的单列(如
user_id、order_no),优先建直方图;对低区分度列(如status、is_deleted),直方图效果有限,更应靠调整索引顺序或覆盖索引 - 直方图需要额外存储空间,且
ANALYZE TABLE ... UPDATE HISTOGRAM是阻塞操作,大表慎用;生产环境建议在低峰期执行,并监控information_schema.COLUMN_STATISTICS - 复合索引的“真实基数”问题,目前没有 MySQL 原生方案能精确统计任意列组合——这是引擎层设计限制,不是调参能绕过的
EXPLAIN 里就永远看不到你期待的 key。本文共计1225个文字,预计阅读时间需要5分钟。
MySQL的优化器依赖于列的基数(cardinality)来选择索引。复合索引的基数统计不是针对每个组合值单独计数,而是基于采样估算的——默认情况下,仅采样10个数据页(由innodb_stats_sample_pages控制),仅对前缀列做统计。如果表很大、数据倾斜严重,或复合索引列的顺序与查询条件不匹配,那么ANALYZE TABLE可能无法准确更新你关心的列组合的统计值。
实操建议:
- 先查当前统计是否真的被更新:
SHOW INDEX FROM your_table;关注
Cardinality列,对比ANALYZE TABLE前后变化 - 确认采样页数是否足够:运行
SELECT @@innodb_stats_sample_pages;,小表可保持默认,大表(千万级以上)建议设为
100或200(需在配置文件或会话中设置并重启生效) -
ANALYZE TABLE不触发即时重算——InnoDB 实际使用的是持久化统计信息(innodb_stats_persistent = ON默认开启),它只在表首次打开、DDL 后或显式调用时刷新,但不会自动重采样所有列组合
复合索引的基数到底统计哪几列
MySQL 对复合索引 (a, b, c) 的基数统计是分层的:只统计 a、(a,b)、(a,b,c) 这三个前缀组合,**不会统计 b 单独、(b,c) 或 c 的基数**。这意味着如果你的查询是 WHERE b = ? AND c = ?,优化器根本看不到 (b,c) 的区分度,只能按全表扫描或退化到其他索引处理。
实操建议:
- 检查实际查询条件与索引列顺序是否对齐;若常用
WHERE b = ? AND c = ?,应建索引为(b, c)或(b, c, a),而非依赖(a, b, c)的中间前缀 - 用
SELECT seq_in_index, column_name, collation, cardinality FROM information_schema.STATISTICS WHERE table_name = 'your_table' AND index_name = 'your_index' ORDER BY seq_in_index; 确认哪些前缀组合有基数记录
- 避免“一索引打天下”思维——复合索引不是万能覆盖,它的统计粒度天然受限于前缀结构
什么时候 ANALYZE TABLE 会失效或不准
常见失效场景包括:表刚批量插入大量新数据但未 ANALYZE;使用 LOAD DATA INFILE 或 INSERT ... SELECT 后未手动触发;行格式为 COMPRESSED 且采样页包含压缩失败块;以及最隐蔽的一种:启用了 innodb_stats_auto_recalc = OFF,导致 DDL 后也不自动更新统计。
实操建议:
- 批量写入后必须显式执行
ANALYZE TABLE your_table;,不能依赖自动机制
- 检查自动重算是否开启:
SELECT table_name, innodb_stats_auto_recalc FROM information_schema.INNODB_TABLES WHERE table_name = 'your_table';
- 若发现
Cardinality长期为NULL或明显偏低(比如WHERE status = 'active'返回 95% 行数,但基数却显示 100),大概率是采样不足或数据分布极端偏斜,此时可考虑用直方图(MySQL 8.0+)补充:ANALYZE TABLE your_table UPDATE HISTOGRAM ON status;
直方图能替代 ANALYZE TABLE 吗
不能。直方图(UPDATE HISTOGRAM)是对单列值分布的精细刻画,用于优化等值/范围查询的选择率估算;而 ANALYZE TABLE 更新的是索引前缀的基数,影响索引选择逻辑。二者解决的问题维度不同,且直方图不作用于复合索引列组合。
实操建议:
- 对高区分度但查询频繁的单列(如
user_id、order_no),优先建直方图;对低区分度列(如status、is_deleted),直方图效果有限,更应靠调整索引顺序或覆盖索引 - 直方图需要额外存储空间,且
ANALYZE TABLE ... UPDATE HISTOGRAM是阻塞操作,大表慎用;生产环境建议在低峰期执行,并监控information_schema.COLUMN_STATISTICS - 复合索引的“真实基数”问题,目前没有 MySQL 原生方案能精确统计任意列组合——这是引擎层设计限制,不是调参能绕过的
EXPLAIN 里就永远看不到你期待的 key。
