日志表索引易吃空间因长文本字段建普通索引会全量存储且选择性低;应使用前缀索引,长度需通过区分度计算确定,如select count(distinct left(log_content,16))/count(*) from logs。

日志表索引为什么特别容易吃空间?
因为日志字段(如 log_content、user_agent、url)普遍是 VARCHAR(2000) 甚至 TEXT 类型,直接建普通索引会把整列内容塞进 B+Tree,一个索引可能比原数据还大。更糟的是,这类字段选择性低(大量重复值),索引利用率极差,纯属浪费。
用前缀索引替代全字段索引,但长度不能拍脑袋定
对长文本字段建索引,必须用前缀,但前缀太短会失效,太长又没省多少空间。关键看实际区分度:
-
SELECT COUNT(DISTINCT LEFT(log_content, 16)) / COUNT(*) FROM logs;—— 如果结果 - 通常
email用 8~12、url用 32~64、user_agent用 64~128 是较安全的起点 - 避免对
TEXT字段直接加索引:MySQL 5.7+ 要求显式指定前缀长度,否则报错ERROR 1170 (42000): BLOB/TEXT column used in key specification without a key length
联合索引优先覆盖高频查询模式,别堆单列索引
日志查询通常是组合条件,比如 WHERE level = 'ERROR' AND created_at BETWEEN ... AND ...。这时建单列 level 索引 + 单列 created_at 索引,不如一个 (level, created_at) 联合索引节省空间且更高效:
- 遵循最左前缀:查询只用
level也能走该索引;但只查created_at就完全失效 - 高选择性列放前面:
level(几类值)选择性远低于created_at,所以如果查询中created_at总是带范围,而level是等值,建议调换顺序为(created_at, level) - 删除冗余单列索引:已有
(created_at, level),就别再留created_at单独索引——它被完全覆盖了
对时间字段用分区表 + 局部索引,比全局索引省得多
日志按时间滚动,老数据基本只读不写。用 PARTITION BY RANGE (TO_DAYS(created_at)) 拆分后,每个分区只需维护自己的小索引:
- 全局索引要扫描整个 B+Tree;分区后,优化器能直接定位到 1~2 个分区,索引体积按分区数线性下降
- 删历史数据变成
DROP PARTITION p_2024,毫秒级,不用DELETE触发索引逐行更新 - 注意:主键必须包含分区键,例如
PRIMARY KEY(id, created_at),否则建表失败
真正省空间的关键不是“少建几个索引”,而是让每个索引都精准匹配真实查询路径,并用分区把索引规模压到可管理的粒度。一上来就给所有字段加索引,和完全不加一样危险——前者只是把问题从查询慢,变成了磁盘满、写入卡、备份崩。











