日志表加索引仍全表扫描,主因是未触发分区裁剪或索引最左前缀失效;应采用按时间范围分区+局部复合索引,索引顺序需匹配高频查询条件(如app_id、created_at、id),并确保分区键包含在索引中。

为什么日志表加了索引还是全表扫描?
日志类表(如 t_operation_log、sys_log)常有“加了索引但 EXPLAIN 显示 type=ALL”的问题——根本不是索引没建,而是查询条件没触发分区裁剪或索引最左前缀失效。比如按时间范围查最近7天,却在 created_at 上建了单列索引,而 WHERE 中还带了 status = 'failed' 和 user_id = 123,MySQL 无法复用单列索引做多条件过滤。
更关键的是:日志表数据持续写入、历史数据几乎不更新,但查询永远集中在最近时间段。这时全局索引会越来越臃肿,B+树深度增加,key_len 变大,Buffer Pool 缓存效率下降。
- 单列索引对多条件组合查询基本无效,优化器大概率弃用
- 索引字段顺序错位(如建了
INDEX(created_at, user_id),但查询是WHERE user_id = ? AND created_at > ?),导致最左前缀不匹配 - 没启用分区裁剪:即使用了
PARTITION BY RANGE (TO_DAYS(created_at)),若 WHERE 条件写成DATE(created_at) = '2026-05-01',函数包裹会让分区失效
如何为日志表设计分区 + 局部索引
局部索引(Local Index)指每个分区各自维护一份索引结构,而非全局统一索引。它天然适配日志场景:新分区写入快、老分区索引体积小、查询只扫相关分区的索引页。
正确做法是先分区、再在每个分区内建复合索引,且顺序必须贴合高频查询模式。例如常见查询:SELECT * FROM t_log WHERE app_id = ? AND created_at BETWEEN ? AND ? ORDER BY id DESC,对应建表语句应为:
CREATE TABLE t_log (
id BIGINT PRIMARY KEY,
app_id INT NOT NULL,
created_at DATETIME NOT NULL,
level VARCHAR(10),
content TEXT,
INDEX idx_app_time_id (app_id, created_at, id)
) PARTITION BY RANGE (TO_DAYS(created_at)) (
PARTITION p20260401 VALUES LESS THAN (TO_DAYS('2026-04-01')),
PARTITION p20260501 VALUES LESS THAN (TO_DAYS('2026-05-01')),
PARTITION p20260601 VALUES LESS THAN (TO_DAYS('2026-06-01')),
PARTITION pmax VALUES LESS THAN MAXVALUE
);
- 分区键必须是索引的一部分(这里
created_at同时用于分区和索引),否则 MySQL 拒绝创建 -
idx_app_time_id是局部索引:每个分区都有自己的 B+ 树,互不影响 - 顺序严格按查询过滤强度排:
app_id(等值,高区分度)→created_at(范围,分区键)→id(覆盖排序,避免Using filesort) - 不要在分区表上建全局索引(Global Index),它会导致跨分区锁和维护开销剧增
分区后怎么验证是否真正生效?
光看 EXPLAIN 不够,得确认两点:是否只访问目标分区、是否命中局部索引。执行以下命令:
EXPLAIN PARTITIONS SELECT * FROM t_log WHERE app_id = 1001 AND created_at >= '2026-05-01' AND created_at <p>观察输出中的 <code>partitions</code> 列——如果只显示 <code>p20260501</code>,说明分区裁剪成功;若出现 <code>p20260401,p20260501,p20260601</code>,就是 WHERE 条件没对齐分区边界(比如用了 <code>MONTH(created_at) = 5</code> 这种函数)。</p>
-
key列必须显示idx_app_time_id,不能是NULL或主键 -
rows值应显著低于总行数(比如分区里 200 万行,rows显示 1.2 万,说明索引过滤有效) -
Extra里不能有Using temporary或Using filesort;若有,说明ORDER BY id没被索引覆盖,需补全索引尾部字段
容易被忽略的维护动作
分区表不是一劳永逸。日志表每天新增分区,旧分区可能长期无人访问,但统计信息过期会导致优化器误判。这些动作必须定期跑:
- 每月对已归档分区执行
ANALYZE TABLE t_log PARTITION(p20260401);,仅分析该分区,比全表ANALYZE快一个数量级 - 删除过期分区用
ALTER TABLE t_log DROP PARTITION p20260101;,比DELETE WHERE快百倍且不锁表 - 新增分区要提前建好(比如每月1号自动运行脚本),避免查询时临时新建分区引发性能抖动
- 监控
INFORMATION_SCHEMA.PARTITIONS表,防止分区数超过 8000(MySQL 限制),建议控制在 60 个以内
局部索引真正的代价不在查询,而在 DDL —— 给分区表加新索引,MySQL 会逐个分区重建,耗时是单表的 N 倍。所以索引设计必须一次到位,上线前用 EXPLAIN + 真实数据量压测验证。











