tp8双写迁移需确保事务一致性、字段语义对齐与主键兼容:旧表为事实源,db::transaction()包裹双写且旧表操作在前;新表须显式指定分片连接;字段类型、时间格式、json及主键策略须手动映射转换;历史迁移时禁用双写并独立校验内容摘要。

TP8 项目做双写迁移,不是加个Db::table('new_user')再写一遍就完事——它必须和旧表保持事务一致性、字段语义对齐、主键策略兼容,否则上线即数据错乱。
双写逻辑必须包裹在事务里,且旧表优先
双写不是“先写新表再写旧表”或反过来,而是以旧表为事实源,所有业务逻辑仍走旧表,新表仅同步。一旦新表写失败,整个事务回滚,不能出现“旧表成功、新表失败”的脏状态。
- 用
Db::transaction()包裹两段写入,且旧表操作放在前面:Db::name('user')->insert($data)(注意:TP8 已不支持Db::name(),此处仅为示意旧表名;实际应确认旧表是否还在用Db::table('user_old')) - 新表写入必须用
Db::table('user_shard_01'),并显式指定连接(如->connection('shard1')),避免误写到主库 - 不要在模型中做双写——TP8 的模型生命周期与事务边界不完全重合,容易漏掉
down()或触发冗余事件
字段映射和类型转换必须手动对齐
分片表往往调整了字段类型(比如INT→BIGINT)、去掉了冗余索引、合并了JSON字段,但双写时不能直接insert($data)原样转发,否则会因类型不匹配报错或截断。
- 对
created_at/updated_at这类时间字段,TP8 默认用datetime,但旧表可能是int时间戳,需统一转为date('Y-m-d H:i:s') - JSON 字段在 TP8 中默认用
json类型,但旧表存的是text,插入前要json_encode(),且down()回滚时得json_decode()还原 - 主键若从自增 ID 改为雪花 ID,双写阶段必须保留旧表
id作为外键关联字段,新表用shard_id单独存,不可覆盖
迁移脚本执行时禁用双写,校验脚本必须独立运行
异步跑历史数据迁移(php think migrate:run --step=1000)期间,双写机制必须关闭,否则新老数据会互相污染。但关了双写不等于可以跳过校验。
- 在
command类中加开关配置:config('migration.double_write_enabled', false),并在双写逻辑开头读取该值 - 校验不能只比行数——要用
SELECT MD5(GROUP_CONCAT(... ORDER BY id))比内容摘要,尤其注意NULL和空字符串的等价性 - 校验失败日志必须包含具体差异字段和样本 ID,例如:
user.id=12345: old.status=1, new.status=NULL,方便快速定位映射逻辑漏洞
TP8 的think-migration不负责双写,它只管结构
topthink/think-migration:^4.0能帮你建好user_shard_01表,但不会自动给你加双写钩子。它和双写是正交问题:一个是数据库结构版本管理,一个是数据流控制。
- 别在
up()里写Db::table('user_old')->select()->chunk(1000, [...])——迁移文件只该定义结构,数据搬运属于命令行任务 - 双写逻辑应放在中间件或服务类中,通过
Event::trigger('user_created', $data)解耦,而不是硬编码在控制器里 - 如果用了多应用模式,确保
app/middleware/DoubleWriteMiddleware.php在每个需要双写的应用下都注册,TP8 不会跨应用自动加载
双写最危险的时刻不是上线那一刻,而是你认为“已经跑完全部历史数据”之后还开着双写——这时候任何新写入都可能因字段映射遗漏、时区偏差或字符集不一致,悄悄写歪一条记录,而监控根本抓不到。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











