这是《MySQL 8.0 源码与内核实战》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 优化器用代价模型把 IO、CPU、内存和网络估算统一为可比较的成本。代价不是精确时间,而是相对排序依据。
11.1 代价来源
| 成本 | 示例 |
|---|---|
| IO | 页读取、回表、临时表 |
| CPU | 表达式计算、比较、聚合 |
| memory | join buffer、sort buffer |
| engine estimate | 引擎返回的扫描代价 |
| first row | LIMIT 场景 |
估算输入:
- 表行数;
- 索引基数;
- range 区间数;
- 记录长度;
- 页大小;
- 缓存命中率假设;
- 条件选择率;
- 连接顺序。
11.2 系统表
代价常量存储在引擎代价表中:
SELECT * FROM mysql.engine_cost;
SELECT * FROM mysql.server_cost;
常见项:
| 表 | 成本 |
|---|---|
| engine_cost | io block read |
| server_cost | row evaluate |
| server_cost | memory tmp table create |
| server_cost | disk tmp table create |
修改示例:
UPDATE mysql.engine_cost
SET cost_value=2.0
WHERE cost_name='io_block_read_cost';
FLUSH OPTIMIZER_COSTS;
生产环境修改必须有基线、回滚和评测,不应作为常规调优手段。
11.3 选择率
等值条件常见思路:
selectivity = 1 / distinct_values
范围条件依赖:
- 列类型;
- min/max;
- 直方图;
- 索引统计;
- 条件形式;
- NULL 比例;
- 字符集。
查看直方图:
ANALYZE TABLE orders UPDATE HISTOGRAM ON status, user_id;
SELECT * FROM information_schema.column_statistics\G
直方图适合列相关性弱但数据倾斜明显的场景。
11.4 单表代价
scan cost
= estimated pages * io cost
+ estimated rows * row evaluate cost
index lookup cost
= range count * io cost
+ matching rows * row evaluate cost
+ back to clustered index cost
影响判断:
- 预估返回行数;
- 是否覆盖索引;
- 回表页分散程度;
- LIMIT;
- ORDER BY;
- 统计准确性。
11.5 连接代价
Nested Loop:
cost = outer cost
+ outer rows * inner lookup cost
Hash Join:
cost = build input scan + hash build
+ probe input scan + hash probe
优化器会依据估算行数、可用索引和内存代价选择。无索引等值大表连接常更容易走 hash join。
11.6 覆盖索引
SELECT user_id, COUNT(*)
FROM orders
WHERE status=1
GROUP BY user_id;
若存在:
KEY idx_status_user(status,user_id)
二级索引可能覆盖查询,避免聚簇索引回表。覆盖索引收益取决于:
- 返回列;
- 索引列顺序;
- 行宽;
- 范围条件;
- 排序分组需求。
11.7 LIMIT 与 first row
SELECT * FROM orders ORDER BY created_at DESC LIMIT 10;
如果有 idx_created(created_at),按索引反向读取前 10 行可能代价很低。无索引时则必须扫描并排序更多行。
优化器会区分:
- 全量执行代价;
- 找到第一批行的代价;
- 是否可提前停止。
11.8 代价模型局限
- 统计是估算;
- 缓存效果难以精确建模;
- 相关列分布难处理;
- 远程存储延迟差异大;
- 运行时并发影响 IO;
- UDF 和复杂函数代价难估计;
- 参数化值不同导致计划差异。
因此执行计划可能随数据分布变化。
11.9 调优流程
慢 SQL
-> EXPLAIN ANALYZE
-> compare estimated and actual rows
-> check statistics / histogram
-> analyze access path
-> rewrite SQL or index
-> regression test
常见修复:
- 补充合适索引;
- 收集统计;
- 增加直方图;
- 改写条件;
- 消除隐式转换;
- 减少返回列;
- 拆分复杂 SQL;
- 局部使用 hint。
本章小结
代价模型把页 IO、行计算、临时表和连接成本折算为相对值,用于选择访问路径和连接顺序。理解统计、选择率、覆盖索引、first row 和模型局限,才能正确解读执行计划。
思考题
- 代价单位和执行时间是否等价?
- 为什么等值条件常用基数倒数估算选择率?
- 直方图适合解决什么统计问题?
- 覆盖索引如何改变代价?
EXPLAIN ANALYZE中估算行数和实际行数差异说明什么?