不能直接用AUTO_INCREMENT主键+单表长期存日志,因写入频繁、查询偏重近期数据,会导致SELECT变慢、ALTER TABLE锁表不可控、备份恢复耗时飙升;须用含NOT NULL DATETIME字段的RANGE COLUMNS分区,主键需包含该字段,避免TIMESTAMP,定期维护分区并确保查询条件覆盖分区字段。

为什么不能直接用 AUTO_INCREMENT 主键 + 单表长期存日志
因为操作日志写入频繁、查询偏重近期数据,单表无限制增长会导致 SELECT 变慢、ALTER TABLE 锁表时间不可控、备份恢复耗时飙升。MySQL 5.7+ 支持按月分区,但前提是表结构必须含一个 DATE 或 DATETIME 类型的分区字段,且该字段不能为 NULL(否则无法做 RANGE COLUMNS 分区)。
常见错误是把 created_at 设为 DEFAULT CURRENT_TIMESTAMP 却没加 NOT NULL,建表时会报错:ERROR 1064 (42000): VALUES LESS THAN value must be strictly increasing for each partition。
实操建议:
- 日志表主键用复合主键:
(id, created_at),其中id是自增整型,created_at是非空时间戳 - 分区字段必须是主键的一部分,否则 MySQL 拒绝创建 RANGE 分区
- 避免用
TIMESTAMP类型——它受时区影响,跨服务器迁移或应用时区变更时容易出错,统一用DATETIME - 建表后立即执行
ANALYZE TABLE,否则首次分区查询可能走全表扫描
怎样用 RANGE COLUMNS 实现按月自动分区
MySQL 不支持“自动新增分区”,所谓“自动归档”其实是靠定时任务(如 Linux cron)调用 SQL 脚本,在每月初提前创建下个月的分区,并删除超过保留期(如 12 个月)的旧分区。
示例:假设日志表叫 op_log,分区字段为 created_at,当前是 2024-06,则需在 6 月 1 日凌晨执行:
ALTER TABLE op_log REORGANIZE PARTITION p_max INTO (
PARTITION p_202406 VALUES LESS THAN ('2024-07-01'),
PARTITION p_max VALUES LESS THAN (MAXVALUE)
);
注意:p_max 必须始终存在,且必须是最后一个分区;新增分区的 VALUES LESS THAN 值必须严格大于前一分区,否则报错 ERROR 1486 (HY000)。
实操建议:
- 用存储过程封装分区管理逻辑,传入年月参数,避免手写 SQL 出错
- 所有分区名统一格式如
p_YYYYMM,方便脚本识别和清理 - 执行
ALTER TABLE ... REORGANIZE PARTITION会锁表,务必避开业务高峰,生产环境建议在凌晨低峰期运行 - 不要依赖
information_schema.PARTITIONS的PARTITION_DESCRIPTION字段解析边界值——它返回的是字符串,不同 MySQL 版本格式不一致,应改用SHOW CREATE TABLE解析
归档脚本里最容易漏掉的三件事
很多团队写了分区脚本,但上线后发现旧数据没删、新分区没生效、或者查不到刚写入的日志——问题往往不在 SQL 本身,而在周边动作缺失。
实操建议:
- 每次新增分区后,立刻执行
SELECT COUNT(*) FROM op_log PARTITION(p_202406)验证分区是否可写,避免因权限或语法隐藏错误导致数据误入p_max - 删除过期分区前,先用
mysqldump --no-create-info --where="created_at < '2023-06-01'"导出一份冷备,再执行ALTER TABLE op_log DROP PARTITION p_202305 - 应用层写日志时,必须显式指定
created_at = NOW(),不能依赖默认值——某些 ORM(如 Django 的auto_now_add=True)在批量插入时可能不触发,默认值只在单条 INSERT 中生效,导致部分记录created_at为NULL,被强制路由到p_max,破坏分区预期
分区表查询性能不升反降?检查这几点
加了分区不代表查询变快。如果查询条件不含分区字段,MySQL 仍会扫描所有分区(EXPLAIN 显示 partitions: all),甚至比普通表更慢。
实操建议:
- 所有带
WHERE的日志查询,必须包含created_at范围条件,例如WHERE created_at >= '2024-05-01' AND created_at < '2024-06-01',才能触发分区裁剪 - 避免在
created_at上用函数,如WHERE DATE(created_at) = '2024-05-10'—— 这会让分区失效,改用范围写法:WHERE created_at >= '2024-05-10 00:00:00' AND created_at < '2024-05-11 00:00:00' - 如果常按用户 ID 查日志,可在
created_at外再加二级分区(SUBPARTITION BY HASH(user_id)),但要注意子分区数不宜过多(建议 ≤ 8),否则元数据开销明显上升
分区不是银弹。真正关键的是写入路径可控、查询条件明确、运维动作可验证——缺一不可。


















