php 不负责数据库主从复制,应由 mysql/postgresql/redis 等底层机制实现;php 仅需处理强一致性读路由、幂等双写、可靠消息消费与告警,严禁手动双写或时间戳增量同步。

主从复制不是 PHP 负责的事,别在代码里硬写同步逻辑
PHP 是应用层语言,不参与数据库底层的数据流控制。所谓“PHP 实现数据同步”,99% 的场景其实是误判了责任边界——真正该做同步的是 MySQL 自身的主从复制(CHANGE MASTER TO)、PostgreSQL 的逻辑复制,或 Redis 的哨兵/集群机制。你在 mysqli 或 pdo 里手动 INSERT 两次、用 curl 调两个接口,这不是同步,是耦合+风险。
常见错误现象:mysqli_query($master, $sql); mysqli_query($slave, $sql); —— 主库成功、从库失败时事务断裂,数据立刻不一致;更糟的是,你把这种写法封装成“同步函数”,上线后才发现延迟、重试、回滚全没设计。
正确做法:
- MySQL 主从:配置
server-id、log-bin、read_only=1在从库,用SHOW SLAVE STATUS监控Seconds_Behind_Master - 跨平台(如 MySQL → elasticsearch):用 Canal、Debezium 或自研 binlog 解析服务,PHP 只消费变更消息,不做写入决策
- 临时兜底需求(如迁移期双写):必须加幂等键(
UNIQUE KEY (biz_id, event_type)),且写从库前先查主库确认状态
PHP 里唯一该碰的“同步”是读写分离后的强一致性读
当业务要求“刚写完马上能读到”,而主从有延迟,这时 PHP 才要介入协调——但目标不是“让从库变快”,而是“绕过从库,强制走主库”。这本质是路由策略,不是数据同步。
立即学习“PHP免费学习笔记(深入)”;
使用场景:用户注册后跳转个人页、订单支付后查状态、评论提交后刷新列表。
关键参数差异:
-
$pdo->setAttribute(PDO::ATTR_PERSISTENT, true)不解决同步问题,只复用连接;反而可能因连接复用导致读到旧连接里的从库缓存 - 读写分离中间件(如 ProxySQL、MaxScale)需配合 PHP 的
SET session sql_log_bin = 0(仅限必要 DDL)或/* FORCE_MASTER */注释提示 - 自研路由:在写操作后 5 秒内,对同一
user_id的读请求自动打到主库,用apcu_store("force_master_{$uid}", time(), 5)简单标记
跨平台同步失败时,PHP 要做的只是可靠地记录和告警,不是重试
比如 MySQL 数据要同步到 Elasticsearch,PHP 接收 binlog 消息后调用 elasticsearch-php 的 index() 方法失败——此时最危险的动作就是循环重试。ES 临时不可用、mapping 冲突、磁盘满,都会让重试变成雪崩。
性能与兼容性影响:
- 同步失败日志必须包含完整上下文:
binlog_file、position、es_error、http_code,否则无法定位是网络抖动还是数据格式错 - 不要用
sleep(1)做退避,改用指数退避(usleep(pow(2, $retry) * 100000)),且最大重试 3 次后进死信队列 - ES 同步建议用 Bulk API(
bulk()),单条index()在高并发下会触发 ES 的EsRejectedExecutionException
最容易被忽略的点:时间戳字段在同步中根本不可信
所有依赖 created_at 或 updated_at 做增量同步的方案,在跨时区、NTP 不稳、主从时钟偏差 >1s 的机器上必然出错。MySQL 的 NOW() 是服务器本地时间,PHP 的 date('Y-m-d H:i:s') 是 PHP 进程所在机器时间,ES 的 @timestamp 又是 ES 节点时间——三者不一致时,你会漏数据或重复同步。
务实解法只有两个:
- 用数据库的自增 ID 或 binlog position 做游标,彻底抛弃时间字段
- 如果必须用时间,统一用 UTC 时间戳(
time()),且所有环节禁用date_default_timezone_set(),靠系统时钟校准(chrony)保障误差
这事关同步的可靠性底线,但几乎没人检查服务器时间差。