OR 条件与索引合并
场景痛点
用户查询 WHERE phone='13800138000' OR city='北京',phone 有索引但 city 没有。OR 中只要一侧无索引,整个查询退化为全表扫描。
问题分析
sql
-- bad.sql: OR 连接两个条件,city 列无索引
SELECT id, username, phone, status, city, created_at
FROM t_user_or
WHERE phone = '13800138000' OR city = '北京';EXPLAIN: type=ALL, key=NULL -- 全表扫描。
原因:OR 要求两侧都能快速定位才能用 index_merge。city 无索引,优化器直接放弃所有索引。
优化方案
sql
-- good.sql: 用 UNION 改写(需先给 city 建索引)
SELECT id, username, phone, status, city, created_at
FROM t_user_or WHERE phone = '13800138000'
UNION
SELECT id, username, phone, status, city, created_at
FROM t_user_or WHERE city = '北京';每个子查询独立走索引(idx_phone / idx_city),再合并去重。
| 指标 | 优化前 (bad) | 优化后 (good) |
|---|---|---|
| 访问类型 | ALL | ref (两个子查询) |
| 使用索引 | NULL | idx_phone + idx_city |
| 扫描行数 | 300,000 | ~1 + ~60000 |
| 附加信息 | Using where | 各自走索引 |
🚀 OR改UNION,每个子查询独立走索引
避坑指南
注意事项
- OR 两侧都有索引时,MySQL 可能用 index_merge,但效果不如 UNION 稳定。
- UNION 会去重,确认无重复用
UNION ALL避免去重排序开销。 - 优先用 IN 替代 OR:
WHERE id IN (1, 2, 3)比id=1 OR id=2 OR id=3更高效。 - OR 两侧是同一列时用 IN:
status=1 OR status=2->status IN (1,2)。
本地复现
bash
./scripts/run-case.sh 06-or-condition