ThinkPHP 6.0 并未内置“数据归档”功能,这一点需要明确。作为 ORM 与请求调度层,TP6 的底层归档逻辑必须由开发者自行设计——不要期待有现成的 Db::archive() 或 $model->moveToArchive() 方法,它们确实不存在。

真正可落地的归档方案,本质上仍是“应用层控制 + 数据库配合”:先通过 TP6 查询出待归档数据,再借助原生 SQL 或事务批量迁移,最后清理源表。核心不在于 TP6 本身能做什么,而在于如何安全地触发和管控整个归档流程。
TP6.0 里没有内置的「数据归档」功能
ThinkPHP 6.0 未提供自动归档、冷热分离或表分区能力——这些功能均需开发者自行实现。不要指望有现成的 Db::archive() 或 $model->moveToArchive() 方法,它们确实不存在。
真正可落地的归档,是“应用层控制 + 数据库配合”:先用 TP6 查询出待归档数据,再通过原生 SQL 或事务批量搬移,最后清理源表。关键不在于 TP6 能做什么,而是你如何安全地触发和管控这个过程。
冷热分离:按时间字段 + 索引优化做判断
典型场景是订单、日志、操作记录等按 create_time 划分冷热。TP6 查询时必须避免全表扫描,否则归档脚本一跑就锁表、拖垮线上服务。
- 确保
create_time字段有索引(单列或联合索引,如(status, create_time)) - 归档条件必须走索引:使用
where('create_time', '<=', $date),避免使用whereYear('create_time', $year)—— 后者无法命中索引 - TP6 的
chunk()分批处理要配合limit和order:否则可能漏数据或重复归档
举个安全分页归档的例子:
$date = '2024-01-01';
$model->where('create_time', '<=', $date)
->where('status', 1) // 加业务状态过滤,缩小范围
->order('id ASC') // 必须有序,避免 chunk 跳过/重复
->chunk(1000, function ($items) use ($archiveModel) {
$archiveModel->insertAll($items->toArray());
$items->each(function ($item) use ($model) {
$model->where('id', $item['id'])->delete();
});
});
历史表分区:MySQL 5.7+ 才支持 RANGE 分区,TP6 不感知
TP6 完全不处理表分区逻辑,分区是数据库层面的事。你得手动用 MySQL 命令建分区表,再让 TP6 的模型指向它(比如把 orders_2023 当成独立模型),而不是指望 TP6 自动生成分区。
常见错误:
- 在 TP6 模型里写
table('orders PARTITION (p2023)')—— 无效,PDO 不支持 PARTITION 语法直写 - 用
Db::execute()执行ALTER TABLE orders REORGANIZE PARTITION...但没加事务和锁检查,导致主从延迟或阻塞写入 - 分区字段用了
datetime但没转成UNIX_TIMESTAMP,MySQL 分区要求表达式必须是 deterministic
正确做法:分区建好后,在 TP6 中用不同模型分别操作当前表与历史表,例如:
// 归档时写入
app\model\archive\Orders2023::create($data);
// 查询时按需切换
if ($year < 2024) {
$list = app\model\archive\Orders2023::where(...)->select();
} else {
$list = app\model\Orders::where(...)->select();
}
运维脚本必须带事务、限流和幂等校验
归档不是一次性的开发任务,而是长期运行的运维动作。TP6 写的命令行脚本(php think archive:orders)最容易出问题的地方不在逻辑,而在边界控制。
- 必须用事务包裹「查 + 插 + 删」三步,哪怕跨库也要考虑分布式事务成本(通常建议同库)
- 加
sleep(0.1)或usleep(50000)控制吞吐,避免瞬间打爆 I/O - 每次归档前先查
SELECT COUNT(*) FROM orders WHERE create_time <= ? AND archived = 0,用archived字段标记是否已归档,防止重跑 - 别依赖 PHP 时间函数生成归档时间点,用数据库当前时间:
Db::raw('NOW() - INTERVAL 12 MONTH')
最常被忽略的一点:归档后的 ANALYZE TABLE 和 OPTIMIZE TABLE 不该由 TP6 触发——那是 DBA 的事,脚本里只负责通知或写日志。
