优化器选错执行计划的原因与Hint干预的边界——统计信息、代价模型与版本兼容性

举报
这个DBA有点耶 发表于 2026/10/16 16:27:56 2026/10/16
【摘要】 统计信息过期、数据分布倾斜、代价模型局限——优化器选错执行计划的原因有很多。当SQL没改,执行计划却突然变差时,除了更新统计信息,还有一条路:用Hint强制干预优化器的决策。本文拆解MySQL 8.0的Hint语法体系、五类常用Hint的实战场景,以及Hint的局限与替代方案。

大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!

有没有遇到过这种情况:一条SQL跑了半年都好好的,突然某天开始变慢了。SQL没改,索引没动,数据量也没暴涨,但执行计划从索引扫描变成了全表扫描。

你更新了统计信息,重建了索引,重启了数据库——执行计划还是那个错的。

优化器到底在想什么?当它选错的时候,除了干瞪眼,还能怎么办?

今天把优化器选错执行计划的原因、Hint的语法体系和实战用法、以及比Hint更好的替代方案,一次性讲清楚。

先搞懂几个词:

CBO(Cost-Based Optimizer) :基于代价的优化器。MySQL的优化器根据统计信息估算不同执行计划的代价,选择代价最低的方案。

统计信息:表和索引的元数据,包括行数、基数、数据分布等。优化器做决策的核心依据。

基数估算:优化器对某个条件过滤后剩余行数的估算值。基数估算偏差是执行计划选错的常见原因。

Optimizer Hint:写在SQL注释中的优化器指令,强制优化器使用指定的索引、连接顺序或执行策略。

一、优化器为什么会选错执行计划

优化器选错执行计划,通常有三个原因。

原因一:统计信息过期。 表的数据量从100万涨到了1000万,但统计信息还停留在三个月前。优化器以为表只有100万行,选了一个“看起来很快”的执行计划,实际跑起来慢得离谱。

原因二:数据分布倾斜。 优化器用采样估算基数,如果数据分布不均匀,采样结果可能严重偏离实际。比如status='DONE'占全表90%,但采样时恰好抽到了几个status='PENDING'的行,优化器以为status='DONE'选择性很高,选了索引扫描,实际需要扫描90%的行。

原因三:代价模型的局限。 MySQL的代价模型基于固定的成本常数估算磁盘I/O和CPU开销。这些常数是静态的,但真实系统的负载是动态的。当并发上来、缓存命中率下降、磁盘I/O压力增大,优化器认为“便宜”的方案实际执行起来可能贵得离谱。

二、MySQL 8.0的Hint语法体系

MySQL的Hint写在SQL语句的/* */注释中,格式是/*+ hint_name(args) */。它紧跟在SELECT、UPDATE、DELETE等关键字之后。

SELECT /*+ INDEX(orders idx_create_time) */ 
    id, amount, create_time 
FROM orders 
WHERE create_time >= '2026-01-01';

MySQL 8.0支持的Hint分为几大类:

类别 常用Hint 作用
索引选择 INDEX、NO_INDEX、FORCE INDEX 强制或禁止使用某个索引
连接顺序 JOIN_ORDER、JOIN_FIXED_ORDER 控制多表连接的驱动表顺序
连接算法 HASH_JOIN、NO_HASH_JOIN、BNL、NO_BNL 控制JOIN算法
查询重写 SEMIJOIN、NO_SEMIJOIN、MERGE、NO_MERGE 控制子查询和派生表的重写策略
并行执行 PARALLEL 控制并行查询线程数

三、五类常用Hint实战

第一类:索引选择——INDEX / NO_INDEX / FORCE INDEX

-- 强制使用 idx_create_time
SELECT /*+ INDEX(orders idx_create_time) */ * FROM orders 
WHERE create_time >= '2026-01-01';

-- 禁止使用 idx_status
SELECT /*+ NO_INDEX(orders idx_status) */ * FROM orders 
WHERE status = 'PAID';

-- 强制走索引(类似FORCE INDEX,但语法更灵活)
SELECT /*+ FORCE INDEX(orders idx_user_id) */ * FROM orders 
WHERE user_id = 12345;

适用场景:优化器选错了索引,你想强制它走某个索引。

第二类:连接顺序——JOIN_ORDER / JOIN_FIXED_ORDER

-- 强制以 orders 为驱动表,users 为被驱动表
SELECT /*+ JOIN_ORDER(orders, users) */ 
    o.id, u.username 
FROM orders o 
JOIN users u ON o.user_id = u.id;

适用场景:优化器选错了驱动表,导致被驱动表全表扫描。你明确知道某张表过滤后结果集更小,应该作为驱动表。

第三类:连接算法——HASH_JOIN / NO_HASH_JOIN

-- 强制使用 HASH JOIN
SELECT /*+ HASH_JOIN(orders, users) */ 
    o.id, u.username 
FROM orders o 
JOIN users u ON o.user_id = u.id;

-- 禁止 HASH JOIN,回退到 Nested Loop
SELECT /*+ NO_HASH_JOIN(orders, users) */ 
    o.id, u.username 
FROM orders o 
JOIN users u ON o.user_id = u.id;

适用场景:MySQL 8.0.18+ 引入Hash Join后,优化器可能在某些场景下选错算法。用Hint强制切换。

第四类:查询重写——SEMIJOIN / NO_SEMIJOIN / MERGE / NO_MERGE

-- 禁止半连接优化,保持子查询语义
SELECT /*+ NO_SEMIJOIN() */ * FROM orders 
WHERE user_id IN (SELECT id FROM users WHERE vip = 1);

-- 强制合并派生表,不物化
SELECT /*+ MERGE(derived_table) */ * FROM (
    SELECT user_id, SUM(amount) AS total FROM orders GROUP BY user_id
) AS derived_table WHERE total > 1000;

适用场景:子查询被优化器重写成了半连接,但重写后的执行计划反而更差。或者派生表被物化了,但你希望它被合并到外层查询中。

第五类:并行执行——PARALLEL

-- 强制使用4个并行线程
SELECT /*+ PARALLEL(4) */ COUNT(*) FROM orders;

适用场景:大表扫描类查询,单线程跑得太慢,想用并行查询加速。

四、Hint的局限与风险

Hint不是万能药。它有明确的局限:

局限一:版本升级后Hint可能失效。 MySQL 8.0的Hint语法和5.7不同。5.7的/*+ INDEX */在8.0中仍然可用,但部分Hint的行为可能变化。

局限二:Hint写错了不会报错,只会静默忽略。 如果表名或索引名写错了,优化器会忽略这个Hint,不会给你任何提示。SQL照常执行,只是Hint没生效。

局限三:Hint可能产生反效果。 你强制优化器走某个索引,但数据分布已经变了,这个索引不再是最优选择。Hint会把优化器锁死在一个过时的决策上。

局限四:Hint是硬编码的。 同一个SQL模板,在不同数据量、不同参数下,最优执行计划可能完全不同。用Hint固定一个执行计划,意味着其他场景下可能变慢。

一个真实案例:某团队用Hint强制一条SQL走索引A,上线后前三个月性能很好。第四个月业务数据分布变化,索引A的选择性大幅下降,但Hint还在强制走索引A。查询性能从50ms降到了2s,排查了三天才发现是Hint的问题。

五、比Hint更好的选择

在写Hint之前,先试试这些方案:

方案一:更新统计信息。 ANALYZE TABLE更新表的统计信息。80%的执行计划选错问题,更新统计信息就能解决。

方案二:优化索引设计。 优化器选错索引,可能是因为缺少一个更合适的索引。建一个更匹配查询模式的联合索引,比强制优化器走某个索引更有效。

方案三:改写SQL。 把子查询改成JOIN、把OR改成UNION ALL、把SELECT *改成只查必要字段——这些改写可能让优化器自然选到更好的执行计划。

方案四:用EXPLAIN ANALYZE诊断。 MySQL 8.0.18+的EXPLAIN ANALYZE直接输出实际执行行数和时间。对比估算值和实际值,能精确定位优化器在哪个步骤判断失误。

六、小结

Hint是优化器失控时的“紧急刹车”,不是日常调优的常规武器。优化器选错执行计划的原因通常是统计信息过期、数据分布倾斜或代价模型局限。先用ANALYZE TABLE更新统计信息,再检查索引设计是否合理,最后才考虑用Hint。如果必须用Hint,记住它的局限:版本升级可能失效、写错不会报错、可能产生反效果。Hint应该是临时方案,不是永久配置。

小耶在手,SQL 不愁

还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽……我们下次见~

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。