FORCE INDEX 与 USE INDEX
为什么需要强制指定索引
MySQL 优化器通常是智能的,但偶尔会”犯糊涂”——选择了不是最优的索引:
-- 优化器明明有更好的索引,却选了错误的
EXPLAIN SELECT * FROM orders
WHERE status = 'paid' AND created_at > '2024-01-01';
-- key: idx_created_at(错误选择)
-- 实际应该用 idx_status_created
此时需要人工干预,强制 MySQL 使用正确的索引。
USE INDEX
USE INDEX 是建议,不是强制:
-- 建议优化器使用 idx_status_created
SELECT * FROM orders USE INDEX (idx_status_created)
WHERE status = 'paid' AND created_at > '2024-01-01';
-- 多个索引建议
SELECT * FROM orders USE INDEX (idx_status_created, idx_status)
WHERE status = 'paid' AND created_at > '2024-01-01';
特点:
- 只是"建议"
- 优化器仍然可以**选择不使用**
- 如果优化器认为全表扫描更好 → 全表扫描
- 优先级低于优化器自身的评估
FORCE INDEX
FORCE INDEX 是强制:
-- 强制使用 idx_status_created
SELECT * FROM orders FORCE INDEX (idx_status_created)
WHERE status = 'paid' AND created_at > '2024-01-01';
-- 强制使用主键
SELECT * FROM orders FORCE INDEX (PRIMARY)
WHERE id BETWEEN 1000 AND 2000;
-- 多个强制(优化器在指定列表中选择)
SELECT * FROM orders FORCE INDEX (idx_status, idx_status_created)
WHERE status = 'paid' AND created_at > '2024-01-01';
特点:
- 优化器必须从指定索引中选择
- 不能全表扫描(除非指定索引都无法使用)
- 如果指定索引都不适用 → 全表扫描(兜底)
FORCE INDEX vs USE INDEX 对比
| 特性 | USE INDEX | FORCE INDEX |
|---|---|---|
| 强制程度 | 建议 | 强制(有兜底) |
| 优化器可忽略 | ✅ 是 | ❌ 否(除非索引不可用) |
| 全表扫描回退 | ✅ 优化器可回退 | ❌ 仅在所有指定索引不可用时才回退 |
| 适用场景 | 轻微引导 | 优化器明显选错 |
实战场景
场景一:优化器选择了错误索引
-- 索引:idx_created(created_at), idx_status_created(status, created_at)
-- 查询:优化器选了 idx_created,但应该用 idx_status_created
EXPLAIN SELECT * FROM orders
WHERE status = 'paid' AND created_at > '2024-01-01';
-- key: idx_created(只用到了 created_at 条件)
-- 强制使用复合索引
EXPLAIN SELECT * FROM orders FORCE INDEX (idx_status_created)
WHERE status = 'paid' AND created_at > '2024-01-01';
-- key: idx_status_created(status 也参与了索引查找)
场景二:全表扫描比索引更差
-- 表数据量 1000 万,status='pending' 占比 0.1%
-- 但优化器不知道(Cardinality 统计不准)
-- 优化器选择了全表扫描
EXPLAIN SELECT * FROM orders WHERE status = 'pending';
-- type: ALL, rows: 10000000
-- 强制用索引
EXPLAIN SELECT * FROM orders FORCE INDEX (idx_status)
WHERE status = 'pending';
-- type: ref, rows: 10000(快了 1000 倍)
场景三:复合索引前缀截断
-- 索引 idx(a, b, c)
-- MySQL 可能只用了 a,但其实 b, c 也很有用
-- 通过其他方式解决(不是用 FORCE INDEX 就能解决的)
-- 这种情况 FORCE INDEX 也帮不了你
合理使用 FORCE INDEX
-- 1. 确认优化器确实选错了
-- 先不用 FORCE INDEX 执行一次
-- 再用 FORCE INDEX 执行一次
-- 对比执行时间
-- 2. 使用 EXPLAIN 验证
EXPLAIN SELECT * FROM orders FORCE INDEX (idx_status_created)
WHERE status = 'paid' AND created_at > '2024-01-01';
-- 确认 key 确实用了 idx_status_created
-- 3. 长期方案:更新统计信息或用查询重写
ANALYZE TABLE orders;
-- 如果 ANALYZE 后优化器选对了 → 就不需要 FORCE INDEX
FORCE INDEX 的缺点
缺点一:数据变化后可能失效
-- 今天:status='paid' 占 10%
FORCE INDEX (idx_status) -- 效果好
-- 一个月后:status='paid' 占 80%
FORCE INDEX (idx_status) -- 反而不如全表扫描!
-- 但 FORCE INDEX 还在,查询变慢了
缺点二:不是数据库原生”学习”
-- 更好的做法
-- 1. 更新统计信息(ANALYZE TABLE)
-- 2. 优化索引设计(比如建更好的复合索引)
-- 3. 改写 SQL 语句
-- 4. 最后才用 FORCE INDEX 兜底
缺点三:维护成本高
-- 每次索引变更、索引名修改、索引删除
-- 都要同步修改 SQL 代码
-- 建议:只在必要时用,并添加注释
SELECT * FROM orders FORCE INDEX (idx_status_created) -- 2024-01-15: 优化器选错,待分析
WHERE status = 'paid' AND created_at > '2024-01-01';
更优雅的替代方案
-- 方案 1:ANALYZE TABLE 更新统计
ANALYZE TABLE orders;
-- 方案 2:删除或设为不可见那个"坏"索引
ALTER TABLE orders ALTER INDEX idx_created INVISIBLE;
-- 方案 3:写更精确的 SQL
-- 用 AND 代替 OR
-- 避免在索引列上使用函数
-- 保持 WHERE 条件顺序清晰
-- 方案 4:IGNORE INDEX(反选,建议不要用的索引)
SELECT * FROM orders IGNORE INDEX (idx_created)
WHERE status = 'paid' AND created_at > '2024-01-01';
面试要点
- USE INDEX = 建议:优化器可以忽略
- FORCE INDEX = 强制:优化器必须用(除非索引不可用)
- 适用场景:优化器选错索引、统计信息不准
- 不建议滥用:数据分布变化后 FORCE INDEX 可能适得其反
- 更好的方案:先更新统计信息(ANALYZE TABLE),再考虑改写查询或优化索引
- 最后手段:FORCE INDEX 是”最后的手段”,不是日常优化手段
一句话总结:FORCE INDEX 和 USE INDEX 是 DBA 在优化器”犯错”时的纠偏工具,但依赖它不如把索引设计和统计信息维护做好。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END


暂无评论内容