动态查询条件本身不决定索引设计,真正决定的是实际执行的sql路径;where中含or或is null会导致优化器无法预判过滤逻辑,大概率全表扫描,应由应用层判断参数非空后拼接真实条件。

动态查询条件本身不决定索引设计,真正决定的是你最终生成的 SQL 实际执行路径——不是“可能传哪些参数”,而是“每次请求到底用了哪几个条件、怎么写的”。
WHERE 中带 OR 或 IS NULL 的写法会让索引失效
常见错误是 MyBatis 或 JDBC 动态拼接时写成:WHERE (user_id = ? OR ? IS NULL) AND (status = ? OR ? IS NULL)。这种结构让优化器无法预判过滤逻辑,大概率退化为全表扫描(EXPLAIN 显示 type=ALL 或 key=NULL)。
- MySQL 不能对
OR左右两边分别走索引再合并,尤其当一边是IS NULL时,索引统计信息失效 - 哪怕你建了
INDEX(user_id, status),这种写法也基本用不上 - 真实业务中,应由应用层判断参数是否为空,只拼接实际存在的条件
复合索引字段顺序必须匹配高频固定组合
假设你 70% 的请求是查 user_id = ? AND created_at > ?,剩下 30% 是查 status = ? AND created_at BETWEEN ? AND ?,那就不能只建一个 INDEX(user_id, status, created_at) —— 它对 status + created_at 场景无效,因为 status 不在最左。
- 优先按「最常出现的等值字段」排最左,比如
user_id区分度高、几乎必填,就放第一 - 范围字段(
>、BETWEEN、LIKE 'abc%')只能放最后,它后面的字段无法用于 WHERE 过滤 - 如果
status单独查询也频繁,且选择性低(比如只有 3–5 个值),单独建INDEX(status)比塞进复合索引更有效
用 EXPLAIN 验证是否真用了索引,而不是“看起来有”
别只看 key 列是不是你建的索引名,重点盯三处:
-
key_len:数值变小说明只用了索引前缀,比如你建了(a,b,c),但key_len只显示前两列长度,说明c没参与过滤 -
rows:下降明显才代表生效;如果和总行数接近,说明还是扫了大半张表 -
Extra:出现Using index才是覆盖索引;Using index condition表示用了 ICP,但仍有回表;Using where; Using index是常见误导项,只表示 WHERE 用了索引,不等于覆盖
不要试图用一个索引覆盖所有动态组合
当查询模式高度分散(比如有时查 city,有时查 age,偶尔查 city + age + gender),硬凑一个 INDEX(city, age, gender) 反而拖慢写入,还让单字段查询变慢。
- 拆成
INDEX(city)、INDEX(age)、INDEX(gender)更实际 - 对高频组合(如
city + age),再额外补一个INDEX(city, age),不是替代,而是叠加 - 注意
VARCHAR(500)字段进索引要谨慎,考虑前缀索引(如INDEX(title(100)))或冗余精简字段
真正难的不是建索引,是把业务查询路径理清楚——哪些条件总是同时出现,哪些永远不一起用,哪些字段改得勤、哪些几乎只读。没这个前提,索引建得再“标准”,也救不了动态 SQL 的随意拼接。











