ThinkPHP本身不提供开箱即用的全自动数据同步能力,必须按场景选型:主从复制靠MySQL原生机制,跨库批量同步需预定义多库连接、分片查询与insertAll写入,实时同步依赖Canal监听binlog,ES同步须通过消息队列解耦,所有方案均需可靠位点管理。

ThinkPHP 本身不提供开箱即用的“全自动数据同步”能力,所谓同步必须明确场景:是主从库实时复制、跨库批量补数、模型变更推到 ES,还是关联表写入一致性?没有统一解法,只有按需选型。
Canal 监听 binlog 实现实时同步,但 MySQL 和 PHP 都得配对
Canal 不是 ThinkPHP 的功能,它是独立服务,TP 只负责消费其推送的变更事件。所以第一步不是写 TP 代码,而是确认 MySQL 是否达标:
-
log_bin必须为ON,否则 Canal 连不上 -
binlog_format必须是ROW,MIXED或STATEMENT会导致字段变更丢失 - 用户权限要包含
REPLICATION SLAVE和REPLICATION CLIENT,光有SELECT不行
PHP 端用 canal-php 扩展,但它默认不保活——$client->get() 一断就退出。不能直接写 while(true) 在命令里硬跑,得用 nohup + 日志心跳,或交由 systemd/pm2 管理进程。另外注意:rowChange.getRows() 返回的是索引数组,不是关联数组,别直接用 $row['id']。
跨数据库批量同步,靠分片 + insertAll,别碰 limit offset
两个库之间拉数据,比如从 mysql 同步到 backup_db,核心是避免全量加载和锁表。ThinkPHP 的 Db::connect('backup_db') 必须在 database.php 中预定义连接名,不能靠 Db::connect([]) 动态传参——后者每次调用都重建连接,高并发下 MySQL 很快报 Too many connections。
- 按主键范围切片,例如每次查
id BETWEEN 10001 AND 15000 - 用
insertAll()一次性写入,比循环insert()快 5–10 倍 - 单次
insertAll别超 5000 条,否则可能触发 MySQL 的max_allowed_packet报错 - 禁用
limit+offset分页,同步过程中数据被删/改会导致跳行或重复
模型事件里直接 HTTP 同步 ES,90% 会状态不一致
在 afterSave 里调 Http::post() 推数据到 Elasticsearch,看着简单,实则埋雷:
立即学习“PHP免费学习笔记(深入)”;
- 事务回滚了,HTTP 请求早已发出,ES 数据无法自动撤回
- 并发更新同一条记录,两个
afterSave可能同时触发,ES 写两次 - 网络超时或 ES 暂不可用,整个请求卡住,拖慢主业务响应
正解是只在事件里投递任务 ID 到队列:$this->dispatch(new SyncToEsJob($this->id, 'update')),再由独立 worker 查数据、构造文档、重试失败项。队列驱动必须是 redis 或 database,不能是 sync(同步执行)。
关联表同步更新,together 参数只在 TP6.0+ 有效且条件苛刻
save() 加 ['together' => ['items']] 并不能无脑生效。它依赖三个硬条件同时满足:
- 关联定义中必须显式设置
'autoWrite' => true - 从表模型(如
OrderItem)必须声明正确的主键(pk),否则更新失败 - 传入的数据数组里,含
id视为更新,不含id视为新增;要删旧记录,得在数组里彻底不出现那条,不是传id加空值
together 不支持 TP5.x,也不级联到子关联(比如 OrderItem 下还有 sku_info 就得另写逻辑)。高频小更新慎用,因为它会触发多条 SQL:UPDATE order → DELETE FROM order_item WHERE ... → INSERT INTO order_item ...。



















