type
status
date
slug
summary
tags
category
icon
password
catalog
sort
前言:为什么有的SQL“明明该走索引却全表扫”?
上一篇咱们聊了超图优化器这个“智能大脑”,它能找出最优的连接顺序,但你有没有想过:它凭什么判断“这个计划比那个计划好”?答案就藏在今天要聊的“成本模型”里——这是优化器的“算盘”,所有决策都靠它算出来。
我当年做开发时,遇到过一个经典问题:一张100万行的表,加了索引的字段
status
,查询WHERE status = 1
却走了全表扫描。当时我百思不得其解,直到后来深入研究成本模型才明白:优化器算出来“全表扫描成本更低”。因为status=1
的行占了80%,走索引需要回表查数据,成本反而比直接扫全表高。这一章咱们就把MySQL的成本模型扒个底掉:从它怎么算IO成本、CPU成本,到统计信息如何影响估算,再到怎么调参让它算得更准。全程配源码、案例和调优技巧,保证让你看完就懂“优化器为什么这么选”,从此告别“索引加了没用”的困惑。
4.1 成本模型的本质:优化器的“价值尺度”
成本模型(Cost Model)是优化器的“决策依据”,它把执行计划的各种操作(扫描、连接、排序等)换算成统一的“成本值”,然后选成本最低的计划。简单说,就是用数字衡量“哪个方案更划算”。
4.1.1 为什么需要成本模型?
没有成本模型的优化器就像“凭感觉点菜的食客”,只能靠经验(规则)选,而有了成本模型,就变成“看价格点菜的理性消费者”,每一分钱都花在刀刃上。
举个例子:查询
SELECT * FROM users WHERE age > 30
,有两个选项:- 选项A:全表扫描,读100万行,IO成本高但不用回表;
- 选项B:走
age
索引,读20万行索引+回表查20万行数据,IO次数多但扫描行数少。
优化器必须算出A和B的成本,才能决定选哪个。没有成本模型,它可能盲目选索引(规则优化),结果反而更慢。
4.1.2 成本模型的核心目标:“算得准”比“算得快”更重要
成本模型的核心目标是“准确估算每个操作的成本”,因为一旦算不准,优化器就会做出错误决策。比如:
- 实际需要扫描10万行,成本模型算成1万行,可能选错索引;
- 实际IO成本很低(SSD),模型却按机械硬盘算,可能放弃索引扫描。
所以MySQL的成本模型一直在迭代:从5.0版本的简单行数乘法,到5.7的条件过滤,再到8.0的直方图和并行成本,都是为了“算得更准”。
4.1.3 成本模型的“三大支柱”:IO、CPU、内存
MySQL的成本模型把成本分成三类,就像生活中的“衣食住行”,各有各的计算方式:
成本类型 | 对应操作 | 核心影响因素 | 默认权重 | 场景优先级 |
IO成本 | 读数据页、索引页 | 扫描的页数 | 1.0 | OLAP(大表扫描)> OLTP |
CPU成本 | 过滤、计算、排序 | 处理的行数 | 0.2 | OLTP(高频小查询)> OLAP |
内存成本 | 临时表、排序缓存 | 使用的内存量 | 0.001 | 内存紧张时优先级升高 |
这三类成本加起来就是“总成本”,优化器选总成本最低的计划。比如全表扫描可能IO成本高但CPU成本低,索引扫描可能IO成本低但CPU成本高(回表计算),最终谁赢全看“总和”。
4.2 成本模型核心公式:手把手教你算“优化器的账”
光说概念太空泛,咱们直接上公式。MySQL的成本计算逻辑藏在源码里,但核心公式可以简化成这样,哪怕你不会编程也能看懂。
4.2.1 IO成本:“读多少页,花多少钱”
IO成本是最基础的成本,因为磁盘IO是数据库最慢的操作之一。计算公式为:
关键参数:io_block_read_cost
(默认1.0)
这个参数代表读一个数据页的IO成本,默认1.0。可以根据硬件调整:
- 机械硬盘(IO慢):保持默认1.0,甚至调高;
- SSD硬盘(IO快):调低到0.2-0.5,让优化器更愿意用索引(因为IO成本低了)。
示例:一张表有1000个数据页,全表扫描的IO成本是
1000 × 1.0 = 1000
。怎么算“扫描的页数”?
- 全表扫描:表的数据页数 =
DATA_LENGTH / innodb_page_size
(InnoDB页默认16KB);
- 索引扫描:索引的页数 =
INDEX_LENGTH / innodb_page_size
;
- 范围扫描:估算扫描的索引页数(根据范围大小和索引密度,索引越密集,相同范围扫的页数越少)。
比如
users
表DATA_LENGTH=16MB
,innodb_page_size=16KB
,数据页数=16MB/16KB=1024页,全表扫描IO成本=1024×1.0=1024。4.2.2 CPU成本:“处理多少行,花多少算力”
CPU成本针对数据处理操作(过滤、计算、连接等),公式为:
关键参数:row_evaluate_cost
(默认0.2)
代表处理一行数据的CPU成本,默认0.2。CPU性能强的服务器可以调低(比如0.1),让优化器更愿意处理多行数据。
示例:全表扫描100万行,CPU成本=100万 × 0.2 = 20万;索引扫描20万行+回表20万行,CPU成本=40万 × 0.2 = 8万。
“处理的行数”怎么算?
- 全表扫描:表的总行数(
table_rows
);
- 索引扫描:索引的基数 × 范围比例(比如
age>30
占总数据的20%,就是基数 × 0.2
);
- 带过滤条件:还要乘以“过滤效果”(
filtering_effect
),比如status=1
过滤掉90%的行,就再×0.1。
- 并行查询影响:MySQL 8.0支持并行扫描,CPU成本会按并行线程数分摊(实际成本=计算值/线程数)。
4.2.3 内存成本:“临时表/排序用多少内存,花多少钱”
内存成本针对需要临时表或排序的操作,公式为:
关键参数:memory_block_cost
(默认0.001)
代表使用一页内存的成本,默认很低(0.001),因为内存比磁盘快得多。但当内存不够用(需要写磁盘临时表)时,成本会飙升(加上IO成本)。
示例:排序用了100页内存,内存成本=100 × 0.001 = 0.1;如果内存不够,写了50页磁盘,成本=100×0.001 + 50×1.0=50.1(瞬间变高)。
4.2.4 总成本:“三笔账加起来,谁低选谁”
最终总成本是三部分相加,公式为:
咱们用开头的例子算一算:查询
WHERE status = 1
,表100万行,status=1
占80%(80万行),有idx_status
索引。选项A:全表扫描
- IO成本:1000页 × 1.0 = 1000
- CPU成本:100万行 × 0.2 = 20万
- 内存成本:0(不需要临时表/排序)
- 总成本:1000 + 200000 + 0 = 201000
选项B:走idx_status
索引
- 索引扫描IO成本:200页(索引页)× 1.0 = 200
- 回表IO成本:80万行 × 平均每行占0.1页(假设)× 1.0 = 80000
- CPU成本:(200页索引行 + 80万回表行)× 0.2 = 800200 × 0.2 = 160040
- 内存成本:0
- 总成本:200 + 80000 + 160040 = 240240
优化器怎么选?
成本类型 | 全表扫描成本 | 索引扫描成本(含回表) |
IO成本(×1000) | 1 | 80.2 |
CPU成本(×1000) | 200 | 160 |
内存成本 | 0 | 0 |
总成本(×1000) | 201 | 240.2 |
注:IO成本含索引页+回表页(索引扫描)
选项A总成本201000,选项B240240,所以优化器选全表扫描——这就是“明明有索引却不走”的真相!因为数据分布导致索引成本更高。
4.3 统计信息:成本模型的“算盘珠子”
成本模型算得准不准,全靠统计信息(Statistics)这个“算盘珠子”。统计信息就是表和索引的“基本情况表”,比如“有多少行”“索引有多少不同值”,没有它,优化器就是“瞎算账”。
4.3.1 表级统计:表的“整体情况”
表级统计记录整个表的基本信息,优化器用它算全表扫描成本。关键统计项有:
统计项 | 含义 | 来源 | 对成本的影响 | 存储位置 |
table_rows | 估算总行数 | SHOW TABLE STATUS | 直接影响CPU成本(行数×0.2) | 持久化统计存在 mysql.innodb_table_stats |
data_length | 数据总大小(字节) | SHOW TABLE STATUS | 算数据页数(data_length/页大小),影响IO成本 | 同上 |
avg_row_length | 平均行长度 | 数据总大小/行数 | 算回表时的IO成本(行数×平均行长度/页大小) | 同上 |
怎么看表级统计?
用
SHOW TABLE STATUS
:比如
Data_length=100MB
,innodb_page_size=16KB
,数据页数=100MB/16KB≈6250页,全表扫描IO成本=6250×1.0=6250。4.3.2 索引级统计:索引的“详细简历”
索引级统计记录每个索引的情况,优化器用它算索引扫描成本。关键统计项:
统计项 | 含义 | 来源 | 对成本的影响 | 存储位置 |
Cardinality | 索引基数(不重复值数量) | SHOW INDEX | 算索引选择性(基数/行数),影响扫描行数估算 | 持久化统计存在 mysql.innodb_index_stats |
Seq_in_index | 索引列顺序 | SHOW INDEX | 决定多列索引是否能被有效利用 | 同上 |
Index_type | 索引类型(B-tree/Hash) | SHOW INDEX | 影响索引扫描方式(范围扫描只支持B-tree) | 表元数据 |
怎么看索引级统计?
用
SHOW INDEX
:比如
Cardinality=5
,表行数100万,索引选择性=5/100万=0.000005,说明status
列重复值很多(适合全表扫描)。4.3.3 直方图:8.0新增的“数据分布图”
MySQL 8.0以前,成本模型估算非等值条件(
>
, <
, BETWEEN
)时经常不准,因为只靠基数,不知道数据分布。比如age
列基数100,但数据集中在18-30岁,age>30
可能只占10%,但优化器可能按50%估算。8.0引入直方图(Histogram)解决这个问题,它记录数据的分布情况,就像“数据的身高体重分布图”,让优化器能精准估算范围条件的行数。
直方图结构示意图:
年龄区间 | 占比(%) | 说明 |
18-25 | 30 | 最大占比区间,年轻人为主 |
26-30 | 25 | 次大占比区间 |
31-35 | 15 | 中等占比 |
36-40 | 10 | 占比逐步下降 |
41-50 | 12 | 中老年用户占比 |
51+ | 8 | 最小占比区间 |
怎么生成和查看直方图?
- 生成直方图:
ANALYZE TABLE 表名 UPDATE HISTOGRAM ON 列名;
- 查看直方图:查
information_schema.column_statistics
表(持久化存储)。
示例:给
users.age
生成直方图:这个桶(bucket)表示
age
在18-25岁的行占30%,优化器算age>25
时就知道大概占70%,而不是瞎猜50%。直方图的实战价值:让范围查询估算更准
某电商的商品表
products
,查询price > 1000
的商品:- 没有直方图时,优化器按“基数/2”估算,假设占50%(50万行),选全表扫描;
- 生成直方图后,发现
price>1000
只占10%(10万行),优化器改走price
索引,查询时间从5秒降到0.8秒。
4.3.4 统计信息的“新鲜度”:过时的统计会让优化器“算错账”
统计信息不是一成不变的,表数据变化(插入/删除/更新)后,统计信息会过时,导致成本估算不准。比如:
数据变化量(万行) | 估算误差率(%) | 说明 |
0 | 5 | 数据无变化时误差极小 |
10 | 8 | 变化10%时误差开始上升 |
20 | 15 | 变化20%时误差显著增大 |
30 | 25 | 超过阈值后误差快速增长 |
40 | 40 | 接近50%变化时误差翻倍 |
50 | 60 | 变化50%时误差达60%以上 |
注:当数据变化超过10%(10万行),误差率显著上升
怎么保持统计信息新鲜?
MySQL提供两种更新统计信息的方式:
- 自动更新:
innodb_stats_auto_recalc=ON
(默认开启):当表数据变化超过10%(行数变化比例)时,自动触发统计信息更新;- 优点:无需手动操作;
- 缺点:大表更新可能有性能波动(短时间锁表,更新过程约占表大小的0.1% IO)。
- 手动更新:
ANALYZE TABLE 表名;
:强制更新表和索引的统计信息(包括直方图);ANALYZE TABLE 表名 UPDATE HISTOGRAM ON 列名;
:只更新指定列的直方图(效率更高);- 适用场景:大表批量更新后、发现执行计划异常时、重要报表查询前。
实战:统计信息过时导致的慢查询
某游戏的用户等级表
user_level
,原本100万行,新增50万行后,查询level > 50
突然变慢。排查发现:- 统计信息显示总行数100万,优化器估算
level>50
占20%(20万行),选走level
索引;
- 实际总行数150万,
level>50
占60%(90万行),走索引回表成本远高于全表扫描;
- 手动执行
ANALYZE TABLE user_level;
后,统计信息更新,优化器改走全表扫描,查询时间从8秒降到2秒。
4.4 成本模型源码深扒:从代码看“优化器怎么算账”
光说公式和统计信息还不够,咱们直接看源码,看看MySQL是怎么把这些逻辑写成代码的。成本模型的核心代码在
sql/cost_model.cc
和sql/opt_costmodel.h
里,咱们挑几个关键函数解析。4.4.1 核心成本计算函数:Cost_model_server::calculate_cost
这个函数是成本计算的“总开关”,输入操作类型和参数,输出总成本。简化版源码:
这个函数的逻辑很简单:根据操作类型(扫描、连接等),调用对应的成本计算函数,最后返回总成本。
4.4.2 全表扫描成本计算:calculate_scan_cost
全表扫描成本=IO成本+CPU成本,源码实现:
这段代码和咱们之前的公式完全对应,简单直接。比如1000页、100万行的全表扫描,成本=1000×1.0 + 100万×0.2=201000,和之前的例子一致。
4.4.3 索引扫描成本计算:calculate_index_scan_cost
索引扫描成本更复杂,包括索引页IO、回表数据页IO和CPU成本:
比如200索引页、20万索引行,回表80000数据页、80万数据行,成本=200×1.0 + 80000×1.0 + (20万+80万)×0.2=200+80000+200000=280200,和之前的例子一致(略有差异是因为数据页估算方式不同)。
4.4.4 排序成本计算:calculate_sort_cost
排序成本包括CPU排序成本和内存/磁盘成本,源码:
排序的CPU成本公式:
这是因为排序算法的时间复杂度为$O(n \log n)$,成本模型通过对数函数模拟这一特性。当行数从1万增长到100万时,排序CPU成本会增长约17倍(而非线性增长100倍),更符合实际性能特征。
4.4.5 成本参数的自定义入口:server_cost
和engine_cost
表
MySQL允许通过系统表自定义成本参数,核心表是
mysql.server_cost
和mysql.engine_cost
,源码中会加载这两个表的值覆盖默认参数:比如要永久调低IO成本,可直接更新表:
4.5 成本模型实战调优:让“算盘”更合实际
成本模型的参数不是一成不变的,需要根据硬件环境、业务场景调优,才能让优化器算得更准。这一节咱们分享几个实战调优案例,从参数调整到索引设计,全方位优化成本模型。
4.5.1 案例1:SSD服务器调优IO成本参数
背景:某互联网公司用SSD服务器部署MySQL,发现很多查询明明走索引更快,优化器却选全表扫描。排查发现,优化器算出来的索引IO成本太高。
原因分析:
SSD的IO速度比机械硬盘快10倍以上,但MySQL默认
io_block_read_cost=1.0
(按机械硬盘设置),导致索引扫描的IO成本被高估,优化器更倾向全表扫描。调优步骤:
- 降低IO成本参数,让优化器更愿意用索引:
- 验证效果:用
EXPLAIN
查看执行计划,发现原本全表扫描的查询现在走索引了。
调优前后对比:
场景 | 调优前成本(×1000) | 调优后成本(×1000) | 优化幅度 |
全表扫描IO成本 | 6250 | 1562 | 75% |
索引扫描IO成本(含回表) | 80200 | 20050 | 75% |
效果:
- 读多写少的查询性能提升30%-50%;
- 索引使用率从原来的60%提升到85%。
4.5.2 案例2:高CPU服务器调优CPU成本参数
背景:某金融公司用高性能CPU(32核)服务器,发现带复杂过滤条件的查询(比如
WHERE a=1 AND b>5 AND c LIKE '%x%'
)优化器总选索引扫描,但实际全表扫描更快。原因分析:
高性能CPU处理数据的速度快,但默认
row_evaluate_cost=0.2
太高,导致优化器低估CPU处理全表数据的能力,高估索引扫描的优势。调优步骤:
- 降低CPU成本参数,让优化器更愿意处理多行数据:
- 配合直方图,让过滤效果估算更准:
效果:
- 复杂过滤条件的查询,全表扫描比例从10%提升到30%;
- 平均查询时间从2.5秒降到1.8秒。
4.5.3 案例3:大表统计信息调优
背景:某电商的订单表
orders
(5000万行),每天新增100万行,查询WHERE create_time > '2023-10-01'
经常走全表扫描,加了索引也没用。原因分析:
- 自动更新统计信息(
innodb_stats_auto_recalc=ON
)在大表上触发不及时(需要变化10%才更新,5000万行需要变化500万行);
- 统计信息显示
create_time
索引基数低,优化器误以为选择性差。
调优步骤:
- 关闭自动更新(避免大表频繁更新影响性能):
- 定时手动更新统计信息(配合crontab每天凌晨执行):
- 增加统计信息采样量,提高准确性:
效果:
- 统计信息更新后,优化器正确估算
create_time> '2023-10-01'
的行数(约300万行);
- 执行计划从全表扫描改为
create_time
索引扫描,查询时间从15秒降到2秒。
4.5.4 案例4:直方图解决非等值查询估算不准
背景:某教育平台的用户表
students
,查询score > 90
的优秀学生,优化器一直走全表扫描,即使加了score
索引。原因分析:
score
列范围0-100,基数101,但数据分布不均:80-100分的学生只占5%;
- 没有直方图时,优化器按“(100-90)/(100-0)=10%”估算行数,认为走索引成本高;
- 实际
score>90
只占5%,走索引更快。
调优步骤:
- 生成
score
列的直方图:
- 验证直方图效果:
确认直方图正确记录了
score>90
的比例。
估算行数对比:
场景 | 行数(万) | 说明 |
实际行数 | 25 | 真实满足条件的行数 |
调优前估算行数 | 50 | 无直方图时误差100% |
调优后估算行数 | 25 | 有直方图时误差接近0 |
效果:
- 优化器估算行数从50万(10%)修正为25万(5%);
- 执行计划改为走
score
索引,查询时间从8秒降到1.2秒。
4.5.5 案例5:内存成本调优解决临时表磁盘写入
背景:某报表查询带
GROUP BY
和ORDER BY
,优化器频繁使用磁盘临时表,即使服务器内存充足(64GB)。原因分析:
- 内存成本参数
memory_block_cost=0.001
太低,优化器低估内存使用成本;
- 临时表大小阈值
tmp_table_size=16MB
太小,稍大结果集就触发磁盘写入。
调优步骤:
- 调低内存成本(相对提高磁盘成本):
- 调大临时表内存阈值:
效果:
- 优化器优先使用内存临时表,磁盘临时表使用率从70%降到10%;
- 报表查询平均时间从12秒降到3秒。
4.5.6 案例6:连接成本调优解决多表连接顺序问题
背景:某报表系统的多表连接查询,优化器总是先连大表
orders
(500万行),再连小表users
(100万行),导致中间结果集过大。原因分析:
- 连接成本计算中,小表驱动大表的成本更低(因为中间结果集=小表行数×连接选择性);
- 但
orders
表的统计信息过时,优化器低估了它的行数(以为300万行,实际500万行),导致算错连接成本。
调优步骤:
- 更新
orders
表统计信息:
- 调优连接成本参数(让小表驱动更有优势):
- 强制使用超图优化器的丛生树连接:
效果:
- 优化器选择
users
(小表)驱动orders
(大表),中间结果集从500万行降到100万行;
- 查询时间从5分钟降到45秒。
4.6 成本模型常见问题与解决办法:避坑指南
成本模型虽然强大,但实战中总会遇到各种“算错账”的问题。这一节总结几个高频问题,教你怎么诊断和解决。
4.6.1 问题1:统计信息过时导致执行计划异常
症状:
- 新增大量数据后,查询突然变慢;
EXPLAIN
显示的估算行数(rows
)和实际行数差距大(比如差10倍以上);
- 明显该走索引的查询走了全表扫描,或反之。
诊断方法:
- 用
SHOW TABLE STATUS
看Rows
是否接近实际行数;
- 用
SHOW INDEX
看索引Cardinality
是否合理(基数/行数应接近实际选择性);
- 对比
EXPLAIN ANALYZE
的估算行数和实际执行行数。
解决办法:
- 手动更新统计信息:
ANALYZE TABLE 表名;
- 对大表,用
ANALYZE TABLE 表名 UPDATE HISTOGRAM ON 列名;
只更新关键列;
- 调整自动更新阈值:对小表保持
innodb_stats_auto_recalc=ON
,大表建议定时手动更新;
- 调大
innodb_stats_persistent_sample_pages
(如100)提高统计准确性。
4.6.2 问题2:直方图未生成导致范围查询估算不准
症状:
- 非等值查询(
>
,<
,BETWEEN
)执行计划不合理;
EXPLAIN
的filtered
值明显不准(比如显示50%,实际只有10%);
- 8.0版本,确认表有大量数据但未生成直方图。
诊断方法:
- 查
information_schema.column_statistics
是否有目标列的直方图: 若无结果,说明未生成直方图。
解决办法:
- 生成直方图:
ANALYZE TABLE 表名 UPDATE HISTOGRAM ON 列名;
- 对经常做范围查询的列(如时间、价格),加入定时任务自动更新直方图;
- 确认
innodb_stats_persistent=ON
(默认开启),确保直方图持久化存储(重启不丢失)。
4.6.3 问题3:硬件环境变化但成本参数未调整
症状:
- 服务器从机械硬盘换成SSD后,索引扫描性能提升不明显;
- 升级高性能CPU后,全表扫描的查询还是走索引(本应全表更快);
- 内存从16GB升到64GB后,需要排序的查询还是用文件排序(本应内存排序)。
诊断方法:
- 检查成本参数是否匹配硬件:
解决办法:
- SSD服务器:
UPDATE mysql.server_cost SET cost_value = 0.25 WHERE cost_name = 'io_block_read_cost';
- 高性能CPU:
UPDATE mysql.server_cost SET cost_value = 0.1 WHERE cost_name = 'row_evaluate_cost';
- 大内存服务器:
UPDATE mysql.server_cost SET cost_value = 0.0005 WHERE cost_name = 'memory_block_cost';
- 同时调整
sort_buffer_size
和join_buffer_size
,匹配硬件能力(如8GB内存服务器可设为8MB)。
4.6.4 问题4:连接成本估算不准导致连接顺序错误
症状:
- 多表连接查询中,优化器选大表当驱动表,导致中间结果集过大;
- 明明小表驱动大表更快,但执行计划显示相反顺序;
EXPLAIN
的rows
在连接步骤中突然变大(比如从10万跳到100万)。
诊断方法:
- 用
EXPLAIN FORMAT=JSON
查看连接顺序和成本估算:
- 检查参与连接的表统计信息是否准确(
ANALYZE TABLE
后对比)。
解决办法:
- 更新所有表的统计信息:
ANALYZE TABLE 表1, 表2, ...;
- 启用超图优化器:
SET optimizer_switch = 'hypergraph_optimizer=on';
- 调低连接成本参数:
UPDATE mysql.engine_cost SET cost_value = 0.8 WHERE cost_name = 'join_cost' AND engine_name = 'innodb';
- 用优化器提示强制驱动表:
SELECT /*+ STRAIGHT_JOIN(小表, 大表) */ ...
4.6.5 问题5:临时表/排序成本估算不准导致性能下降
症状:
- 带
GROUP BY
或ORDER BY
的查询突然变慢;
EXPLAIN
的Extra
显示Using temporary
或Using filesort
;
- 实际内存足够,但优化器却用磁盘临时表/文件排序。
诊断方法:
- 检查临时表和排序的内存限制:
- 用
EXPLAIN ANALYZE
看是否发生磁盘写入:Using filesort (disk)
或Using temporary (disk)
。
解决办法:
- 调大内存限制(根据服务器内存):
- 调低内存成本参数,让优化器更愿意用内存:
UPDATE mysql.server_cost SET cost_value = 0.0005 WHERE cost_name = 'memory_block_cost';
- 加索引避免排序:
CREATE INDEX idx_col1_col2 ON 表(col1, col2)
(覆盖排序/分组列);
- 拆分大查询,减少单次排序/分组的数据量。
4.6.6 问题6:多列索引统计不准导致索引选择错误
症状:
- 多列索引(如
(a,b)
)的查询WHERE a=5 AND b=10
未走索引;
SHOW INDEX
显示索引基数低,但实际选择性很高。
诊断方法:
- 检查多列索引的统计信息是否准确,尤其是组合基数;
- 用
EXPLAIN ANALYZE
对比走索引和全表扫描的实际成本。
解决办法:
- 重建索引更新统计:
ALTER TABLE 表名 DROP INDEX 索引名, ADD INDEX 索引名(a,b);
- 手动更新统计信息:
ANALYZE TABLE 表名;
- 若索引选择性确实低,考虑拆分索引或增加过滤条件。
4.7 成本模型的未来:更智能、更精准
MySQL的成本模型一直在进化,从简单的行数乘法到现在的直方图+并行成本,未来还会更智能。结合官方 roadmap 和社区动态,这些方向值得关注:
4.7.1 自适应成本模型:会“学习”的优化器
未来的成本模型可能会引入机器学习,根据历史查询的实际执行成本调整参数。工作流程如下:
Oracle在MySQL 8.0.30+版本已经测试了“自适应成本模型”,在TPC-C测试中,执行计划准确性提升了20%。
4.7.2 硬件感知的成本模型:自动适配硬件环境
现在的成本参数需要手动调整(比如SSD调低IO成本),未来可能自动检测硬件类型:
- 启动时检测磁盘类型(SSD/机械硬盘),自动设置
io_block_read_cost
;
- 根据CPU核心数和频率,动态调整
row_evaluate_cost
;
- 监控内存使用率,实时调整内存成本参数。
4.7.3 细粒度成本控制:按操作类型调优
目前的成本参数是全局的,未来可能支持更细粒度的控制:
- 区分“索引扫描IO成本”和“数据扫描IO成本”;
- 对
JOIN
、GROUP BY
、ORDER BY
设置不同的CPU成本系数;
- 支持按表或索引设置单独的成本参数(比如大表的IO成本系数更高)。
4.7.4 实时成本修正:执行中动态调整
现在的成本估算在优化阶段完成,执行中不会修正。未来可能支持“边执行边修正”:
- 执行中发现实际行数和估算差距大,动态切换执行计划;
- 根据实时IO/CPU负载,调整成本权重(比如IO繁忙时提高IO成本)。
结语:成本模型是“优化器的普通话”
这一章咱们把MySQL成本模型从公式到源码、从统计信息到实战调优扒了个遍。你会发现,优化器的所有决策都基于“成本”这个共同语言——就像普通话一样,不管是单表查询、多表连接还是排序分组,都用成本值来衡量好坏。
理解成本模型,你就能看懂“为什么优化器这么选”,甚至能“引导”它做出更优决策:比如通过更新统计信息让它算得更准,通过调整成本参数让它适应硬件,通过设计索引降低实际成本。
下一章,咱们将进入“优化器调试兵器谱”,看看有哪些工具能帮你“透视”优化器的决策过程,从
optimizer trace
到EXPLAIN ANALYZE
,让你像“调试代码”一样调试SQL执行计划!- 作者:Honesty
- 链接:https://blog.hehouhui.cn/archives/mysql-cost-model-complete-analysis-optimizer-cost-calculation-principles
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。