单列created_at索引常失效,因其无法覆盖多条件过滤或排序需求,导致回表成本高而触发全表扫描;应按等值条件在前、范围条件在后的原则建复合索引,如(level, service_name, created_at),并结合explain的type、rows和extra字段验证索引命中效果。

为什么 created_at 单列索引在日志表上常常不够用
日志表查询通常带时间范围(如 BETWEEN '2024-01-01' AND '2024-01-31'),但若还加了 WHERE status = 'error' 或 ORDER BY created_at DESC LIMIT 20,单列 created_at 索引就可能失效——MySQL 优化器发现回表成本高,干脆走全表扫描。
根本原因是:单列索引无法覆盖查询中涉及的其他过滤字段或排序需求,导致大量随机 I/O。
- 复合索引顺序必须把等值条件放最左,时间范围条件放右(如
(status, created_at)) - 如果常查
level和service_name,又按created_at排序,索引应为(level, service_name, created_at),而非反过来 -
created_at在复合索引里不能放在前面,除非查询只按它过滤(无其他WHERE条件)
如何判断现有查询是否命中索引
别只看 EXPLAIN 输出里有没有 key 字段,重点看 type、rows 和 Extra:
-
type是range或ref才算有效利用索引;ALL就是全表扫描 -
rows值接近表总行数?说明索引没起作用 -
Extra出现Using filesort或Using temporary,大概率是索引没覆盖ORDER BY或GROUP BY
执行 EXPLAIN SELECT * FROM log_table WHERE level = 'warn' AND created_at > '2024-01-01' ORDER BY created_at DESC;,再对比加索引前后 rows 变化,比看执行时间更可靠。
时间字段类型和索引效率的隐性关系
用 DATETIME 还是 TIMESTAMP 对索引本身没影响,但时区处理和默认值行为会间接破坏查询可优化性:
- 如果
created_at定义为TIMESTAMP DEFAULT CURRENT_TIMESTAMP,且应用层写入时未显式赋值,MySQL 自动填充的值可能因时区差异导致范围查询边界模糊 - 避免在索引字段上用函数:比如
WHERE DATE(created_at) = '2024-01-01'会让索引完全失效;改用created_at >= '2024-01-01' AND created_at - 分区表配合索引效果更好,但前提是分区键与查询条件对齐(例如按
created_atRANGE 分区),否则仍可能扫多个分区
线上加索引要防锁表和复制延迟
日志表往往数据量大、写入频繁,直接 ALTER TABLE ADD INDEX 可能阻塞写入,尤其在 MySQL 5.6 或更低版本。
- MySQL 5.6+ 支持
ALGORITHM=INPLACE,但仅限某些操作;确认方式:SHOW CREATE TABLE log_table查存储引擎,InnoDB 一般支持,MyISAM 不支持 - 生产环境优先用
pt-online-schema-change,它通过影子表+触发器实现无锁变更,但要注意主从延迟放大风险 - 如果表有唯一约束或外键,
pt-osc会自动禁用外键检查,需提前评估级联影响
真正麻烦的不是加索引本身,而是索引列选择错误后不得不删掉重建——每次重建都重复一次锁表或长耗时过程。











