ThinkPHP 6.0 实现 MySQL 大表归档与冷热分离需手动控制表路由、分批迁移及索引保障,禁用已移除的 partition 配置;归档表结构须与原表完全一致(含字段类型、长度、NULL 性、主键),删除外键,统一 InnoDB 引擎,并为高频字段建索引。

ThinkPHP 6.0 处理 MySQL 大表数据归档与冷热分离,必须绕开框架已移除的 partition 配置,手动控制表路由、分批迁移与索引保障——配置了 'partition' => ['type' => 'year'] 也完全无效,SQL 日志里根本不会出现带后缀的表名。
归档表结构必须与原表完全一致
先用 ThinkPHP 迁移命令生成归档表:php think migrate:create create_archive_order_table,避免手动建表漏掉字符集或默认值。
字段名、类型(DATETIME 不能换成 TIMESTAMP)、长度(VARCHAR(255) 缩成 VARCHAR(191) 就会 INSERT SELECT 失败)、NULL 性、主键定义全部照搬,一个都不能改。
外键约束必须删除——归档表不参与业务关联,留着会导致 INSERT INTO SELECT 报错 【Cannot add or update a child row: a foreign key constraint fails】。
立即学习“PHP免费学习笔记(深入)”;
为 create_time、user_id 等高频查询字段单独建索引,否则归档后查历史订单会全表扫描。
引擎统一用 InnoDB,别用 ARCHIVE(不支持 UPDATE/DELETE)或 MyISAM(事务中归档失败会导致主表也回滚不了)。
按年动态计算归档表名并写入
封装独立工具类,禁止在模型 save() 或 select() 中重写逻辑:
复制 AI 写代码
class ArchiveTable {
public static function getOrderTable($time) {
return 'order_' . date('Y', $time);
}
}
业务中直接调用:
Db::table(ArchiveTable::getOrderTable($createTime))->insert($data);
Db::table(ArchiveTable::getOrderTable(strtotime('2024-12-31')))->where('status', 1)->select();
这一步操作起来很简单,直接把文件拖进去就行。
千万别用 $model->partition['type'] === 'year' 这类 TP3.2 的旧写法——TP6+ 中该属性根本不存在,运行时会报 Notice: Undefined index。
分批迁移冷数据(INSERT + DELETE 成对执行)
第一步:确保 create_time 字段上有有效索引,否则 WHERE 条件会触发全表扫描。
第二步:用 Db::execute() 执行原生 SQL,每次只处理 5000 行:
Db::execute("INSERT INTO order_2024 SELECT * FROM order WHERE create_time = '2024-01-01' ORDER BY id LIMIT 5000");
第三步:立即执行对应 DELETE,且条件必须和上一步完全一致:
Db::execute("DELETE FROM order WHERE create_time = '2024-01-01' ORDER BY id LIMIT 5000");
第四步:检查 ROW_COUNT() 返回值是否一致,不一致就中止流程,防止丢数据。
第五步:删完立刻执行 Db::execute("ANALYZE TABLE order"),更新统计信息,防后续查询走错索引。
冷热查询路由层实现
方法一:时间范围判断自动选库
if ($endDate $table = ArchiveTable::getOrderTable(strtotime($endDate));
return Db::table($table)->where($condition)->select();
} else {
return OrderModel::where($condition)->select();
}
方法二:强制指定表名绕过全局作用域
OrderModel::withoutGlobalScope()->table(ArchiveTable::getOrderTable(1704067200))->where('user_id', 123)->select();
注意:不用 withoutGlobalScope(),软删除作用域会干扰归档表查询结果。
用 MySQL 分区表替代归档表(仅限新项目)
先确认主键含分区键:ALTER TABLE order DROP PRIMARY KEY → ALTER TABLE order ADD PRIMARY KEY(id, create_time);
再执行分区语句:
ALTER TABLE order PARTITION BY RANGE (TO_DAYS(create_time)) (
PARTITION p2024 VALUES LESS THAN (TO_DAYS('2025-01-01')),
PARTITION p2025 VALUES LESS THAN (TO_DAYS('2026-01-01')),
PARTITION pmax VALUES LESS THAN MAXVALUE
);
验证是否生效:EXPLAIN PARTITIONS SELECT * FROM order WHERE create_time
过期分区用 ALTER TABLE order DROP PARTITION p2024 秒级清理,不走事务日志,不触发索引维护。



















