mysql 8.0+需外部调度+sql脚本实现按月自动清理历史分区,优先用truncate partition(8.0.21+支持),否则用drop partition;postgresql依赖pg_cron扩展执行truncate子表;clickhouse需ttl配合手动drop partition id;三者均须严防时区、夏令时及interval计算偏差导致误删。

MySQL 8.0+ 按月自动清理历史分区的正确姿势
MySQL 原生不支持“自动 TRUNCATE 过期分区”功能,必须靠外部调度 + SQL 脚本配合实现。核心思路是:用 SELECT 查出待清理的分区名,再拼成 ALTER TABLE ... DROP PARTITION 或 TRUNCATE PARTITION 语句执行。
注意:TRUNCATE PARTITION 只在 MySQL 8.0.21+ 支持,且仅适用于 RANGE 和 LIST 分区表;老版本只能用 DROP PARTITION(会重建表结构,开销更大)。
- 先确认分区策略:
SHOW CREATE TABLE your_table,检查是否为RANGE COLUMNS(time_col)或RANGE (TO_DAYS(time_col)) - 查过期分区名示例(按月、保留最近6个月):
SELECT PARTITION_NAME FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_SCHEMA = 'your_db' AND TABLE_NAME = 'your_table' AND PARTITION_DESCRIPTION
- 若用
TO_DAYS()分区,PARTITION_DESCRIPTION存的是天数,需转成日期比对;若用RANGE COLUMNS,则PARTITION_DESCRIPTION是字符串形式的日期字面量(如 '2024-01-01'),可用STR_TO_DATE()处理
PostgreSQL 中用 pg_cron 实现定时 TRUNCATE 分区
PostgreSQL 本身无自动分区清理机制,但可通过扩展 pg_cron 在数据库内调度任务。关键点在于:必须用 TRUNCATE 而非 DROP,因为 TRUNCATE 支持指定分区(子表),且不触发外键级联或触发器。
- 确保已启用
pg_cron:CREATE EXTENSION IF NOT EXISTS pg_cron;,并配置shared_preload_libraries = 'pg_cron' - 分区表需是原生声明式分区(
PARTITION BY RANGE (created_at)),每个分区是独立子表,命名通常为parent_table_y2023m01 - 定时脚本示例(每月1号删掉早于3个月的分区):
SELECT cron.schedule('0 0 1 * *', $$ DO $$ BEGIN EXECUTE format('TRUNCATE %I', ( SELECT partition_name FROM ( SELECT inhrelid::regclass AS partition_name, pg_get_expr(relpartbound, inhrelid) AS bound FROM pg_inherits JOIN pg_class ON inhrelid = pg_class.oid WHERE inhparent = 'your_table'::regclass ) t WHERE bound ~ '20[0-9]{2}-0[1-9]|1[0-2]' AND bound - ⚠️ 注意:PostgreSQL 的
TRUNCATE不支持通配符或动态分区列表,必须逐个执行;生产环境建议加EXISTS判断分区是否存在,避免报错中断
ClickHouse 自动 TTL + DROP PARTITION 的边界情况
ClickHouse 的 TTL 可自动删除过期数据,但它默认是异步、延迟执行的,且只删数据不删分区目录。真要彻底释放磁盘空间,仍需手动 ALTER TABLE ... DROP PARTITION。
- 建表时设 TTL(例如按
event_time字段):CREATE TABLE logs ( event_time DateTime, ... ) ENGINE = MergeTree() PARTITION BY toYYYYMM(event_time) ORDER BY (event_time) TTL event_time + INTERVAL 6 MONTH;
- TTL 生效后,数据被标记为过期,但分区文件仍存在;需定期运行:
ALTER TABLE logs DROP PARTITION ID '202312';
(ID 来自SELECT partition_id FROM system.parts WHERE table = 'logs' AND active) - 别直接用
DROP PARTITION 202312(不带 ID),旧版本 ClickHouse 对数字分区名解析不稳定;优先用PARTITION ID字符串形式 - 自动化脚本中务必加锁或判断:
system.mutations是否有未完成的 TTL 清理任务,否则DROP PARTITION可能报错 “Cannot drop partition because mutation is running”
通用陷阱:跨时区、夏令时和分区边界值计算
所有方案中最容易翻车的不是语法,而是时间计算逻辑本身——尤其当业务服务器、数据库时区、分区字段类型(DATETIME vs TIMESTAMP)、以及夏令时切换混在一起时。
- MySQL 中
TO_DAYS(NOW())返回的是本地时区的天数,但分区表达式若用UNIX_TIMESTAMP(created_at),而created_at是TIMESTAMP类型,就隐含了时区转换,结果可能偏差1天 - PostgreSQL 的
current_date是会话时区,但分区边界如果是'2024-01-01'::DATE字面量,则无时区问题;若用generate_series()动态建分区,必须显式AT TIME ZONE 'UTC' - ClickHouse 的
toYYYYMM()按本地时间计算,但集群节点时区不一致会导致同一时间点落在不同分区 —— 必须统一所有节点的/etc/timezone - 任何脚本里都别写死
INTERVAL 6 MONTH,它不等于180天(2月只有28天),应改用DATE_SUB(NOW(), INTERVAL 180 DAY)或更稳妥的LAST_DAY(DATE_SUB(NOW(), INTERVAL 7 MONTH)) + INTERVAL 1 DAY
分区自动清理这事,逻辑越简单越可靠。宁可多跑一次查分区、少省一行计算,也别让时间算错导致误删。










