多模数据库选型指南:从“多库拼装”到“一库多能”的架构演进
大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!
前几周我们花了大量时间讲EXPLAIN——type怎么读、rows怎么理解、filtered怎么看、Extra里有什么。但有一个问题始终没回答:优化器凭什么这么选?
你肯定遇到过这种情况:明明有合适的索引,优化器却选了全表扫描;明明A计划更快,优化器却选了B计划。这时候你可能会骂优化器“笨”,但其实它只是做了一个“基于现有信息的最优判断”——而它依赖的“现有信息”,就是统计信息和成本模型(Cost Model) 。
如果统计信息不准,或者成本模型的计算逻辑跟你预想的不一样,优化器的判断就会“跑偏”。今天我们把优化器的“算账逻辑”彻底拆开讲一遍。
一、成本模型是什么?——优化器不是“猜”,是“算”
数据库优化器是基于代价(Cost-Based) 的。它不会凭感觉选执行计划,而是会计算每种可能的执行方式的“代价”,然后选代价最小的那个。
这个“代价”不是时间,而是抽象的“资源开销单位”。主要由三部分构成:
| 成本类型 | 含义 | 说明 |
|---|---|---|
| IO_cost | 读写数据页的成本 | 从磁盘读取数据页到内存的代价,通常是成本的大头 |
| CPU_cost | 处理行数的成本 | 在内存中处理行数据的代价,比如比较、聚合、排序 |
| memory_cost | 临时内存使用成本 | 使用临时表或排序缓冲区的代价 |
简单说:优化器会把每个可能的执行计划换算成一个数字——cost,然后选cost最小的那个。
那优化器是怎么算出这些cost的?靠统计信息。表有多少行、每列有多少个不同值、数据分布如何——这些信息决定了优化器估算“要读多少页、要处理多少行”。所以,统计信息是优化器的“眼睛” ,眼睛花了,算得再准也白搭。
二、怎么看到优化器的“账本”?——EXPLAIN FORMAT=JSON
传统的EXPLAIN只告诉你结果,不告诉你“账是怎么算的”。但MySQL 8.0提供了EXPLAIN FORMAT=JSON,能把优化器的成本估算完整地展示出来。
EXPLAIN FORMAT=JSON
SELECT * FROM orders WHERE user_id = 12345 AND order_date > '2026-01-01';
输出的JSON中,最核心的部分是cost_info:
"cost_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次数”,不是真实执行耗时。它不反映CPU、内存、锁等待等运行时开销。但它的相对大小告诉我们优化器为什么选了A没选B。
三、更进一步:OPTIMIZER_TRACE——看优化器的“思考过程”
EXPLAIN FORMAT=JSON只展示了最终选中的计划的成本,但看不到优化器放弃了哪些计划、为什么放弃。
这就是OPTIMIZER_TRACE的价值——它能完整记录优化器从接收SQL到选定执行计划的每一步决策:候选计划评估、规则应用、索引选择、成本对比,全部以JSON格式呈现。
使用方法:
-- 1. 开启跟踪
SET SESSION optimizer_trace = "enabled=on";
SET end_markers_in_json = ON;
-- 2. 执行要分析的SQL
SELECT * FROM orders WHERE user_id = 12345 AND order_date > '2026-01-01';
-- 3. 查看跟踪结果
SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE\G
-- 4. 关闭
SET SESSION optimizer_trace = "enabled=off";
在OPTIMIZER_TRACE的输出中,重点关注rows_estimation部分——它会列出优化器为每个候选索引计算的cost和rows,以及最终为什么选了某个计划。如果优化器选了一个看起来不合理的计划,这里通常会告诉你原因:可能是统计信息过旧导致rows估算偏差太大,可能是某个系统变量影响了成本权重计算。
四、真实案例:优化器“算错账”的根因排查
某电商系统,订单表orders有1000万行,(user_id, order_date)上有复合索引。业务SQL:
SELECT * FROM orders WHERE user_id = 12345 AND order_date > '2026-01-01';
正常情况下走复合索引,扫描几百行就返回。但某天突然变慢,执行计划显示type=ALL全表扫描。
用OPTIMIZER_TRACE查看后发现,优化器估算user_id=12345会返回50万行(实际只有200行)。因为统计信息过旧,优化器认为“走索引回表50万次的IO成本,大于全表扫描1000万行的顺序读成本”,所以选了全表扫描。
执行ANALYZE TABLE orders更新统计信息后,优化器重新估算user_id=12345只返回200行,走复合索引的cost远低于全表扫描,查询从5秒降到了0.05秒。
关键认知:优化器不是“笨”,是“看错了”——它基于错误的统计信息做出了在当时看起来最优的决策。
五、成本模型的局限与注意事项
- cost是估算值,不是实际值:
EXPLAIN FORMAT=JSON中的cost是基于统计信息的估算,不是真实执行成本。验证优化效果应该用EXPLAIN ANALYZE获取实际执行数据。 - 统计信息的质量决定cost的准确度:如果统计信息过旧,cost估算可能偏差巨大。定期执行
ANALYZE TABLE是保证优化器决策质量的基础。 - 系统变量会影响成本计算:
eq_range_index_dive_limit、range_optimizer_max_mem_size等参数会直接影响优化器的成本估算逻辑。 - IO_cost通常占大头:在磁盘表场景下,IO_cost通常是成本的主要组成部分。这就是为什么减少数据页读取(走索引、缩小扫描范围)是优化SQL最有效的手段。
六、总结
| 工具 | 能看到什么 | 解决什么问题 |
|---|---|---|
EXPLAIN |
最终选的执行计划 | 知道“选了谁” |
EXPLAIN FORMAT=JSON |
最终计划的成本估算 | 知道“选了谁、花了多少钱” |
OPTIMIZER_TRACE |
完整的决策过程 | 知道“为什么选它、为什么没选另一个” |
执行计划是优化器的“决策结果”,统计信息是优化器的“决策依据”,成本模型是优化器的“决策算法”。这三者形成一个完整的决策链:
统计信息 → 成本估算 → 执行计划
如果执行计划看起来不合理,不要急着骂优化器“笨”。先检查统计信息是否准确——大概率是优化器“看错了”,而不是“算错了”。学会用EXPLAIN FORMAT=JSON和OPTIMIZER_TRACE看到优化器的“账本”和“思考过程”,你就能从“看懂EXPLAIN”升级到“理解优化器为什么这么选”。
小耶在手,SQL 不愁
还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽……我们下次见~
- 点赞
- 收藏
- 关注作者
评论(0)