什么是FORCE INDEX?实战中如何用它优化慢SQL?
面试场景
面试官:”你在项目中做过SQL优化吗?能举个有亮点的例子吗?”
回答SQL优化问题,最忌讳说”给字段加索引”这类烂大街的答案。高分回答需要:
- 有具体的业务场景
- 说明慢在哪里(执行计划分析)
- 为什么慢(深层原因)
- 如何优化(解决方案)
- 效果如何(可量化的结果)
业务场景
某人口档案管理系统,需要查询”全市在世的、年龄最大的100个张姓居民”。
表结构(500万+数据量):
CREATE TABLE person (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL, -- 有索引 idx_name_age(name, age)
age SMALLINT NOT NULL, -- 有索引 idx_age_name(age, name)
status TINYINT NOT NULL, -- 0:在世 1:不在世
-- 其他字段略
KEY idx_name_age (name, age),
KEY idx_age_name (age, name)
);
业务SQL:
SELECT * FROM person
WHERE name LIKE '张%' AND status = 0
ORDER BY age DESC
LIMIT 100;
问题:执行耗时 12秒!
问题分析
执行计划
EXPLAIN SELECT * FROM person WHERE name LIKE '张%' AND status = 0 ORDER BY age DESC LIMIT 100;
| type | key | rows | Extra |
|---|---|---|---|
| index | idx_age_name | 5800000 | Using where |
问题所在
- type = index:全索引扫描,遍历整个索引树
- 使用了 idx_age_name:MySQL选择先按age排序,再过滤name
实际执行过程:
Step 1: 通过 idx_age_name 按 age 降序遍历
Step 2: 对每行检查 name LIKE '张%' 是否匹配
Step 3: 回表检查 status = 0
Step 4: 满100条后返回
由于”张姓”只占总人口10%左右,这种策略需要遍历大量不匹配的数据。
优化思路
换一个角度思考
如果先过滤name,可以直接排除90%的数据:
-- 查看过滤效果
SELECT COUNT(*) FROM person WHERE name LIKE '张%' AND status = 0;
-- 结果:52万条,只占总数据的10%
如果使用 idx_name_age(name, age) 索引:
Step 1: 通过前缀匹配 name LIKE '张%' 快速定位
Step 2: 在匹配结果内按 age 排序(索引第二列)
Step 3: 回表过滤 status = 0,取100条
解决方案:FORCE INDEX
SELECT * FROM person
FORCE INDEX (idx_name_age)
WHERE name LIKE '张%' AND status = 0
ORDER BY age DESC
LIMIT 100;
优化结果
| 优化前 | 优化后 | 提升 |
|---|---|---|
| 12秒 | 1.5秒 | 8倍 |
FORCE INDEX 深度解析
语法
SELECT ... FROM table_name FORCE INDEX (index_name) WHERE ...;
使用场景
| 场景 | 说明 |
|---|---|
| 优化器选错索引 | 本例:优化器误判LIKE性能差 |
| 统计信息不准 | 表数据变化大,统计信息未及时更新 |
| 复杂多表JOIN | 优化器难以准确评估各索引成本 |
注意事项
- 不保证100%生效:指定的索引必须在候选索引中
- 慎用:会绕过优化器的智能选择
- 治标方案:长期应考虑优化索引设计
为什么MySQL会选错索引?
优化器的决策依据
MySQL优化器基于成本模型选择执行计划,考虑因素:
- 索引扫描行数预估
- 回表成本
- 排序成本
本例的误判原因
- 优化器看到
ORDER BY age DESC,认为用idx_age_name可以避免排序 - 优化器对
LIKE '张%'的过滤效果预估不准 - 最终选择了全索引扫描,期望通过索引有序避免排序
实际情况:避免排序的收益 < 过滤90%数据的收益
相关优化技巧
查看优化器成本
EXPLAIN FORMAT=JSON SELECT ...;
{
"query_cost": "1234567.00", // 查看不同方案的cost
"rows_examined_per_scan": 5800000,
"access_type": "index"
}
优化器提示(MySQL 8.0+)
SELECT /*+ INDEX(person idx_name_age) */ *
FROM person
WHERE name LIKE '张%' AND status = 0
ORDER BY age DESC LIMIT 100;
更新统计信息
ANALYZE TABLE person;
面试答题框架
业务背景:人口系统,500万数据,查询张姓+年龄最大+100条
问题现象:执行12秒
执行计划:type=index,用了idx_age_name,全索引扫描
根因分析:优化器误判,先排序后过滤效率低
解决方案:FORCE INDEX强制使用idx_name_age
优化效果:12秒→1.5秒,提升8倍
深层理解:成本模型对LIKE过滤效果预估不准
总结
| 要点 | 说明 |
|---|---|
| FORCE INDEX作用 | 强制MySQL使用指定索引 |
| 适用场景 | 优化器选错索引时的应急手段 |
| 核心原理 | 绕过成本模型,人工指定执行路径 |
| 使用原则 | 先分析执行计划,确认确实选错才用 |
| 替代方案 | Optimizer Hints(8.0+)、优化索引设计 |