MySQL代价模型由mysql.server_cost和mysql.engine_cost两张可配置系统表驱动,优化器据此结合统计信息动态计算执行计划成本;修改表后需执行FLUSH OPTIMIZER_COSTS生效。

MySQL代价模型依赖两个系统表:server_cost 和 engine_cost
代价模型不是硬编码的固定公式,而是由 mysql.server_cost 和 mysql.engine_cost 两张系统表驱动的可配置机制。优化器在生成执行计划前,会查这两张表获取基础成本常量,再结合统计信息(如 Cardinality、Rows)动态计算总代价。
常见错误是直接修改参数却没刷新缓存——MySQL 5.7+ 启动后会将这些值加载进内存,后续对表的 UPDATE 不会实时生效,必须执行 FLUSH OPTIMIZER_COSTS 才能重载。
-
server_cost控制通用操作开销:比如key_compare_cost(默认 0.05)影响索引扫描时的比较次数估值;row_evaluate_cost(默认 0.1)决定每行 WHERE 条件评估成本 -
engine_cost按存储引擎区分:InnoDB 行有io_block_read_cost(默认 1.0),MyISAM 则用另一套值;同一张表切换引擎后,代价估算可能突变 - 不建议全局调低
disk_temptable_create_cost(默认 20.0)来“骗”优化器走内存临时表——若实际内存不足,仍会落盘,且copying to tmp table on disk状态会更频繁
为什么 ANALYZE TABLE 后执行计划突然变了
因为代价模型严重依赖统计信息,而 ANALYZE TABLE 会更新 information_schema.STATISTICS 中的 Cardinality 值和 SHOW TABLE STATUS 中的 Rows 估算。优化器用这些值预估“走索引能过滤掉多少行”,一旦 Cardinality 失真(例如字段大量重复但统计显示高区分度),就会高估索引效率,误选索引扫描而非全表扫描。
典型场景:某时间字段加了索引,但业务只写近 7 天数据,旧数据占表 95% 却长期不清理。ANALYZE TABLE 可能仍按全量分布采样,导致优化器认为该索引选择性差,放弃使用。
- 验证方式:执行
SHOW INDEX FROM table_name查看Cardinality是否合理;对比SELECT COUNT(DISTINCT col) FROM table_name的实际结果 - 临时缓解:用
FORCE INDEX绕过代价判断,但只是掩盖问题 - 根本解法:对冷热分离明显的表,考虑分区(PARTITION BY RANGE),让
ANALYZE在每个分区上独立统计
JOIN 顺序不是按 SQL 书写顺序决定的
优化器会穷举所有合法 JOIN 排列(n! 种),对每种组合分别估算成本:rows_before_join × io_block_read_cost + rows_after_join × row_evaluate_cost。最终选总代价最小的顺序,与你写的 FROM a JOIN b JOIN c 无关。
容易被忽略的一点:如果某张表有 WHERE 条件且能大幅过滤(比如 WHERE status = 'done'),优化器大概率把它排在 JOIN 链最前面——不是因为它“重要”,而是它输出的中间结果集最小,后续连接成本自然降低。
- 查看实际顺序:用
EXPLAIN FORMAT=TREE(8.0+)或EXPLAIN的id和select_type列推断 - 强制指定顺序风险大:用
STRAIGHT_JOIN会跳过代价计算,若数据分布变化(如某表突然膨胀 10 倍),性能可能断崖下跌 - 小表驱动大表仍是经验法则,但仅当统计信息准确时成立;若小表的
Cardinality被低估,优化器可能反向选择
临时表成本常量直接影响 GROUP BY / ORDER BY 是否走磁盘
当查询含 GROUP BY 或 ORDER BY 且无法利用索引排序时,MySQL 必须建临时表。此时优化器会对比内存临时表与磁盘临时表的总成本:memory_temptable_create_cost + N × memory_temptable_row_cost vs disk_temptable_create_cost + N × disk_temptable_row_cost。其中 N 是预估行数,来自 Rows 统计。
问题常出在 N 严重高估:比如 SELECT COUNT(*) FROM t WHERE create_time > '2026-05-01',但 create_time 索引的 Cardinality 过低,优化器以为要扫 100 万行,就倾向选磁盘临时表,哪怕实际只返回几百行。
- 检查方式:观察
EXPLAIN的Extra列是否含Using temporary,再结合SHOW PROFILE看Copying to tmp table耗时占比 - 调优方向:优先确保
GROUP BY字段有高Cardinality索引;若不可行,再考虑调高tmp_table_size和max_heap_table_size,但这治标不治本 - 注意:
memory_temptable_row_cost默认 0.1,比row_evaluate_cost(0.1)还低——优化器天然偏好内存临时表,所以统计不准时更容易误判
代价模型本身不难理解,难的是统计信息如何影响它的输入。很多“优化器不走索引”的问题,根源不在模型算错,而在 Cardinality 或 Rows 这两个数字已经脱离实际好几个数量级了。


















