SQL执行计划的“成本模型”:读懂cost,理解优化器为什么选这个计划

📅 2026/8/4 15:57:58
SQL执行计划的“成本模型”:读懂cost,理解优化器为什么选这个计划
大家好我是小耶写功课只是为了我踩过的坑你们别再踩了做了这么多年SQL优化你有没有发现一个奇怪的现象同一套业务数据同一个版本的数据库只是在不同的环境里跑执行计划可能完全不一样——有时候走索引有时候全表扫描。开发环境跑得好好的SQL一上生产就慢了。排除了数据量差异、硬件差异之后真正的原因指向了一个地方优化器在不同的环境里“算”出来的成本不一样。优化器不是凭感觉选执行计划的它有一套完整的成本模型Cost Model。它会把每种可能的执行方式换算成一个数字——cost然后选cost最小的那个。问题在于这个cost是算出来的不是测出来的。而算的依据是统计信息。统计信息可以理解成优化器手里的“参考数据”——表有多少行、每列有多少个不同值、数据分布如何。成本模型则是优化器用来算账的“计算器”——读一次磁盘算多少分、处理一行数据算多少分。两者配合优化器才能算出每个执行计划的cost。如果统计信息不准或者成本模型的计算逻辑跟你预想的不一样优化器的判断就会“跑偏”。成本模型是怎么“算账”的优化器的决策逻辑是对每个可能的执行计划走哪个索引、用什么JOIN顺序用成本模型估算出一个cost然后对比所有候选计划的cost选择最小的那个。这个“代价”主要由三部分构成成本类型含义说明IO_cost读写数据页的成本从磁盘读取数据页到内存的代价通常是成本的大头CPU_cost处理行数的成本在内存中处理行数据的代价比如比较、聚合、排序memory_cost临时内存使用成本使用临时表或排序缓冲区的代价简单说优化器会把每个可能的执行计划换算成一个数字——cost然后选最小的那个。举个例子WHERE user_id 12345 AND order_date 2026-01-01。优化器会估算两种方案的成本——走(user_id, order_date)复合索引或者全表扫描。如果走索引的cost比全表扫描小优化器就选索引。如果统计信息过旧走索引的成本估算可能严重偏高优化器就会“算错账”。关键认知优化器不是“跑一遍测试”来比较哪个计划快而是“算一遍账”来估算哪个计划成本低。这个“账”算得准不准完全取决于统计信息的准确度。如果统计信息过旧优化器可能基于错误的数据做出“看起来最优”的决策——实际上它是“看错了”不是“算错了”。那怎么看到优化器算的这笔账传统的EXPLAIN只告诉你结果不告诉你“账是怎么算的”。但MySQL 8.0提供了EXPLAIN FORMATJSON能把优化器的成本估算完整地展示出来。EXPLAIN FORMATJSON SELECT * FROM orders WHERE user_id 12345 AND order_date 2026-01-01;输出的JSON中最核心的部分是cost_infocost_info: { read_cost: 1500.25, eval_cost: 500.00, prefix_cost: 2000.25, data_read_per_join: 10M }字段含义read_cost读取数据的IO成本eval_cost评估和处理行的CPU成本prefix_cost当前表在JOIN顺序中的累计成本data_read_per_join预估读取的数据量大小注意cost是优化器基于统计信息估算的相对代价单位是“等价随机I/O次数”不是真实执行耗时。但它的相对大小告诉我们优化器为什么选了A没选B。更进一步OPTIMIZER_TRACE——看优化器的完整思考过程EXPLAIN FORMATJSON只展示了最终选中的计划的成本但看不到优化器放弃了哪些计划、为什么放弃。这就是OPTIMIZER_TRACE的价值——它能完整记录优化器从接收SQL到选定执行计划的每一步决策候选计划评估、规则应用、索引选择、成本对比全部以JSON格式呈现。SET SESSION optimizer_trace enabledon; SELECT * FROM orders WHERE user_id 12345 AND order_date 2026-01-01; SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE\G SET SESSION optimizer_trace enabledoff;在OPTIMIZER_TRACE的输出中重点关注rows_estimation部分——它会列出优化器为每个候选索引计算的cost和rows以及最终为什么选了某个计划。一个真实案例某电商系统订单表1000万行(user_id, order_date)上有复合索引。某天查询突然变慢执行计划显示全表扫描。用OPTIMIZER_TRACE查看后发现优化器估算user_id12345会返回50万行实际只有200行。因为统计信息过旧优化器认为“走索引回表50万次的IO成本大于全表扫描1000万行的顺序读成本”所以选了全表扫描。执行ANALYZE TABLE orders更新统计信息后优化器重新估算只返回200行走复合索引的cost远低于全表扫描查询从5秒降到了0.05秒。关键认知优化器不是“笨”是“看错了”——它基于错误的统计信息做出了在当时看起来最优的决策。总结工具能看到什么EXPLAIN知道“选了谁”EXPLAIN FORMATJSON知道“选了谁、花了多少钱”OPTIMIZER_TRACE知道“为什么选它、为什么没选另一个”执行计划是优化器的“决策结果”统计信息是优化器的“决策依据”成本模型是优化器的“决策算法”。学会用EXPLAIN FORMATJSON和OPTIMIZER_TRACE看到优化器的“账本”和“思考过程”你就能从“看懂EXPLAIN”升级到“理解优化器为什么这么选”。小耶在手SQL 不愁还有什么想了解的欢迎留言小耶一定知无不言言无不尽……我们下次见~