FORCE INDEX 与 USE INDEX

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
喜欢就支持一下吧
点赞14 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容