历史数据归档本身不拖慢查询,但若不做冷热分离致主表数据堆积,SELECT、UPDATE、DELETE 会因索引膨胀、缓冲池污染、锁竞争加剧而明显变慢;TP5.1 的 partition 仅支持插入和单条查询时按规则算表名,无法自动迁移冷数据,需手动编写迁移脚本并确保读写逻辑绕过原表。

历史数据归档本身不直接拖慢查询,但若不做冷热分离、任由全量数据堆积在主表,SELECT、UPDATE、DELETE 都会因索引膨胀、缓冲池污染、锁竞争加剧而明显变慢——尤其当单表超 500 万行后,TP5.1 的 ORM 查询构建器和 MySQL 执行计划就容易失准。
TP5.1 无法靠 partition 自动归档冷数据
ThinkPHP 5.1 的 partition 方法只在插入/单条查询时按规则算表名,它不提供“自动将 2022 年前的数据迁入 t_order_2022 并从 t_order 删除”的能力。所谓“分表即归档”是误解——你得自己写迁移脚本,且迁移后必须确保所有读写逻辑都绕过原表。
-
partition不改 SQL 路由:查where create_time 仍走主表,不会自动 UNION 所有历史子表 - 没有归档触发器:框架不监听时间字段变化,也不会在 insert 时判断是否该进冷表
- 迁移脚本需手动执行:比如用
Db::table('t_order')->where('create_time', 'select()拉出数据,再批量写入t_order_archive_2021,最后delete
手动分区 + 表名路由才是可控归档路径
放弃 partition 配置幻想,直接在模型或服务层控制表名,才能把“哪年数据进哪张表”这件事握在手里。核心是让业务代码明确知道当前操作的是热表还是冷表。
- 写入时:根据
$data['create_time']算出后缀,如$suffix = date('Y', strtotime($data['create_time'])),再调用Db::table('order_' . $suffix)->insert($data) - 查询时:不能依赖模糊条件,必须传入时间范围参数,例如接口
/api/order?year=2023→ 查order_2023;没传 year 就默认查order_current(指向最新月/年表) - 归档动作:单独跑命令行任务,如
php think archive:orders --before=2022-01-01,它负责 select+insert+delete,并更新统计表或标记位 - 注意时区一致性:PHP
date()和 MySQL 的NOW()时区要对齐,否则2022-12-31 23:00可能被算成 2023 年表
冷表放从库 or 单独数据库更安全
把冷数据扔进主库同一实例的其他表,只是逻辑隔离;真正降低 I/O 压力和避免长事务阻塞,得物理分离——冷表挂到低配从库,或独立归档库。TP5.1 不支持跨库 JOIN,反而让这事变得简单:查热数据用主连接,查冷数据显式切连接。
立即学习“PHP免费学习笔记(深入)”;
- 在
database.php配置额外连接:'archive' => [ 'hostname' => '192.168.1.100', 'database' => 'archive_db' ] - 查冷数据时不用模型,直接
Db::connect('archive')->table('order_2021')->where(...)->select() - 别在事务里混用主库和归档库:TP5.1 的
Db::transaction()只作用于单连接,跨库提交失败无法回滚 - 备份策略要拆开:主库每日全备+binlog,归档库可每周全备一次,甚至用
mysqldump --where="create_time 增量导出
真正的难点不在怎么分表,而在业务代码里所有涉及时间范围的操作,都得提前决定查哪张表——漏掉一个 where 条件没带年份,或某个定时任务忘了切连接,冷热边界就立刻失效。这不是框架能兜住的事,是每个查询入口的硬性契约。



















