索引“失效”是优化器主动弃用而非删除,主因是统计信息过期和索引覆盖不足;需查cardinality、stat_n_diff_key_vals并analyze table验证,优先为where+set字段建最小覆盖索引。

为什么 UPDATE 频繁时索引会“失效”
不是索引真被删了,而是优化器在执行计划里主动弃用——尤其当更新字段本身是索引列,或该列值变化剧烈、区分度骤降时。EXPLAIN 显示 type 变成 ALL 或 index,key 为空,就是典型信号。
根本原因有两个:一是统计信息过期(INFORMATION_SCHEMA.STATISTICS 里的 cardinality 滞后),二是索引覆盖不足,导致每次更新都要回表+维护二级索引树,成本高到优化器宁愿全表扫。
- 高频更新会让
cardinality快速失真,特别是TINYINT或状态类字段(如status只有 0/1/2) - 如果
WHERE条件用的是id,但SET更新的是updated_at,而这个字段又在某个联合索引里,那该索引的 B+ 树节点分裂会变频繁,页分裂率升高,优化器可能判定“维护成本 > 使用收益” -
innodb_stats_auto_recalc默认开启,但只在表变更行数超 10% 才触发,小表或慢速累积更新容易卡在临界点以下
怎么判断是不是统计信息拖了后腿
别猜,直接查。重点看三处:
- 执行
SHOW INDEX FROM table_name,对比Cardinality和实际唯一值数量(可用SELECT COUNT(DISTINCT col) FROM table_name验证) - 检查
information_schema.INNODB_SYS_INDEXES中的stat_n_diff_key_vals,它比SHOW INDEX更实时,但需PROCESS权限 - 运行
ANALYZE TABLE table_name后立刻EXPLAIN同一条UPDATE,看key是否恢复
注意:ANALYZE TABLE 是阻塞操作(尤其大表),生产环境建议搭配 innodb_stats_persistent = ON + 定期 mysqlcheck --analyze 调度,避免手动触发抖动。
索引覆盖不够?优先补 WHERE + SET 的最小组合
别一上来就建“全字段索引”。先抓最痛的 SQL,用 pt-query-digest 或慢日志定位 TOP 3 更新语句,再按这个公式设计索引:WHERE 条件字段前置 + SET 中被更新且常用于查询的字段后置。
- 例如:频繁执行
UPDATE orders SET status = ?, updated_at = NOW() WHERE user_id = ? AND id > ?,最优索引是(user_id, id, status, updated_at)—— 前两列满足WHERE范围扫描,后两列让更新不回表(覆盖写) - 避免把
TEXT/BLOB列塞进索引;VARCHAR(500)要加前缀长度(如INDEX (content(100))),否则索引体积膨胀,更新更慢 - 如果更新语句带
ORDER BY ... LIMIT 1,索引必须包含ORDER BY字段,否则无法用索引排序,优化器大概率放弃索引
高频更新字段本身要不要单独建索引
绝大多数情况:不要。尤其是状态位、计数器、时间戳这类低区分度字段(status IN (0,1)、counter % 100)。
- 区分度 SELECT COUNT(*) FROM t GROUP BY col 粗略算下分布就清楚
- 如果必须查这个字段(比如后台“查所有待处理订单”),优先走覆盖索引 +
WHERE过滤,而不是给status单独建索引 - 例外:该字段是联合索引最左前缀,且其他条件固定(如
WHERE status = 1 AND created_at > '2024-01-01'),此时索引(status, created_at)有效,但本质靠的是范围裁剪能力,不是status自身
真正要盯紧的,是那些在 WHERE 里高频出现、又被频繁更新的字段——它们才是索引失效链的起点。其他字段更新再快,只要不参与查询过滤,就只是存储层压力,不是查询优化问题。











