MySQL优化器自动重排WHERE条件顺序,真正影响性能的是索引设计、是否覆盖、ICP支持及避免对索引字段使用函数或运算。

WHERE 条件顺序不影响 MySQL 执行计划
MySQL 优化器会自动重排 WHERE 子句中的条件顺序,按索引选择性、数据分布、成本估算重新决定执行路径。你写成 WHERE status = 'active' AND created_at > '2023-01-01' 还是反过来,对执行计划没有实质影响——除非你用的是非常老的 MySQL 5.5 且关闭了优化器(几乎不存在)。
真正起作用的是:字段是否在索引中、索引是否覆盖、条件是否能触发索引下推(ICP)、是否用了函数或表达式导致索引失效。
- 不要手动把“过滤性强”的条件写前面来“优化”,这是过时经验
- 用
EXPLAIN看key和possible_keys,而不是靠肉眼猜顺序 - 如果发现
type是ALL或index,说明没走有效索引,该看的是索引设计,不是 WHERE 写法
避免在 WHERE 字段上用函数或运算
对索引字段做函数调用(如 DATE(created_at))、类型转换(如 CAST(user_id AS CHAR))、数学运算(如 price * 1.1 > 100),会让 MySQL 无法使用该字段上的 B+ 树索引,只能全表扫描或索引全扫。
常见错误写法:
SELECT * FROM orders WHERE YEAR(created_at) = 2023;
正确替代方式:
- 改用范围查询:
created_at >= '2023-01-01' AND created_at - 如果必须按年查且高频,可加生成列 + 索引:
ALTER TABLE orders ADD COLUMN year_created TINYINT GENERATED ALWAYS AS (YEAR(created_at)) STORED, ADD INDEX idx_year (year_created); - 避免
LIKE '%abc',它无法利用索引;LIKE 'abc%'可以走索引前缀
IN 列表过大时注意性能拐点
WHERE id IN (1,2,3,...,2000) 看似简单,但当值超过几百个时,MySQL 会退化为多个等值查找合并,优化器可能放弃使用索引、改走全表扫描,尤其在旧版本(如 5.6)中更明显。
实操建议:
- 单次
IN值数控制在 200 以内,超量拆成多批次查询 - 若来源是另一张表,优先用
JOIN替代IN (SELECT ...),后者容易触发临时表和文件排序 - MySQL 8.0+ 支持
IN子查询的物化优化,但仍有阈值,仍建议用JOIN更可控 - 考虑用临时表承载大批量 ID:
CREATE TEMPORARY TABLE tmp_ids(id BIGINT PRIMARY KEY); INSERT INTO tmp_ids VALUES (...); SELECT * FROM t JOIN tmp_ids USING(id);
NULL 判断要小心索引失效风险
WHERE col IS NULL 在有允许 NULL 的普通索引(非唯一索引)上通常可以走索引;但 WHERE col != 'x' 或 WHERE col 'x' 会隐式包含 NULL,而 B+ 树索引不存储 NULL 值(除非是唯一索引的 NULL 特殊处理),这类查询往往触发全索引扫描甚至全表扫描。
典型陷阱:
SELECT * FROM users WHERE email != 'test@example.com'; -- 若 email 允许 NULL,此语句实际返回所有 email 不等于该值的行 + 所有 email IS NULL 的行
更安全写法:
- 明确拆开:
WHERE email != 'test@example.com' AND email IS NOT NULL - 如果业务上 email 不应为 NULL,就该设为
NOT NULL并加约束,让优化器更敢用索引 - 对可空字段建索引时,留意
EXPLAIN中的rows是否异常高——可能正在扫描大量 NULL 项
最常被忽略的一点:复合索引中只要有一个字段允许 NULL,且查询条件跳过它(比如用 WHERE a = 1 AND c = 3,跳过 b),而 b 是可空字段,某些旧版本可能无法高效利用后续字段的索引下推。别只盯着 WHERE 怎么写,先确认表结构和索引定义是否干净。











