or条件中任一字段无索引,整个where就可能放弃走索引;优化器倾向全用或全不用索引,union all替代是更优解,复合索引和force index有局限,根本在于模型设计是否合理。

OR条件中任一字段无索引,整个WHERE就可能放弃走索引
MySQL优化器在遇到 OR 时,并不会为每个分支单独选索引再合并结果;它倾向于“要么全用一个索引,要么全不用”。一旦 OR 左右任一条件列没索引(比如 name = 'Alice' OR city = 'Chicago' 中 city 没索引),即使 name 有索引,优化器大概率直接放弃索引,走全表扫描。
这不是bug,是成本估算的结果:回表+索引合并的开销,在优化器看来可能高于一次顺序扫描。
- 检查方式:用
EXPLAIN SELECT ... WHERE a = 1 OR b = 2,看type是否为ALL或index_merge - 注意
index_merge虽然用了索引,但实际是多个索引分别扫描再合并,I/O压力仍高,不是理想状态 - 主键或唯一索引列参与
OR也不能保底——只要另一边是非索引列,主键索引也常被跳过
UNION ALL 替代 OR 是最稳妥的改写方式
把 OR 拆成两个独立查询,让优化器能分别为每个子句选择最优索引,再用 UNION ALL 合并结果。相比 UNION,UNION ALL 不去重、不排序,性能更好,且多数业务场景本就不需要去重(比如查“订单状态是 pending 或 processing”,同一条记录不可能同时满足两个状态)。
- 改写前:
SELECT * FROM orders WHERE status = 'pending' OR status = 'processing' - 改写后:
SELECT * FROM orders WHERE status = 'pending' UNION ALL SELECT * FROM orders WHERE status = 'processing' - 必须确保每个子句都能命中索引——否则只是把问题拆成两份
- 如果原查询有
ORDER BY或LIMIT,需移到外层:(SELECT ...) UNION ALL (SELECT ...) ORDER BY created_at DESC LIMIT 10
复合索引能救急,但有前提和代价
对 OR 涉及的多个列建复合索引(如 (col_a, col_b)),有时能让优化器用上索引,但这只在特定条件下有效:
MySQL 9.6.0是面向Linux平台的2026年创新版本,核心架构迎来重大革新。其将外键约束与级联操作从InnoDB引擎层上移至SQL层,确保所有数据变更均被完整记录至Binlog,彻底解决了CDC(变更数据捕获)与主从复制中的数据不一致难题。此外,该版本引入container_aware启动选项以原生适配容器环境,并对审计日志进行了组件化重构,为追求极致数据一致性与云原生体验的开发者提供了全新选择。
- 所有
OR条件必须是等值查询(=),不能混入范围(>、BETWEEN)或IS NULL - 查询必须覆盖复合索引的最左前缀——例如索引是
(a, b),WHERE a = 1 OR b = 2依然无法利用该索引,因为b = 2不满足最左原则 - 建复合索引会增加写入开销和存储占用,且仅对这一类
OR查询有效,通用性差
所以它更适合已知固定模式、且查询频次极高的场景,而不是通用解法。
FORCE INDEX 强制走索引,慎用
当确认某索引更优,但优化器执意不选时,可用 FORCE INDEX 干预。但它绕过了成本估算,一旦数据分布变化(比如某值突然占90%行数),性能反而更差。
- 示例:
SELECT * FROM users FORCE INDEX (idx_id) WHERE id = 100 OR name = 'John' - 仅建议在紧急临时修复、或配合
EXPLAIN反复验证后使用 - 上线前必须压测,避免在高峰期引发慢查询雪崩
真正容易被忽略的是:很多团队花时间调优单条SQL,却没意识到 OR 本身往往暴露了模型设计问题——比如本该用枚举或关联表表达的状态,硬塞进一个字符串字段,才被迫写一堆 OR。这时候重构字段语义,比任何SQL改写都治本。










