本文目录导读:

- 方案一:MySQL主从复制(最推荐,真正的实时同步)
- 方案二:触发器 + MySQL UDF/HTTP请求(适用中小项目)
- 方案三:Canal/DeCanal(阿里开源,适合高并发)
- 方案四:PHP脚本定时轮询(最不推荐,但有特殊场景)
- 如何选择(决策树)
- 注意事项
针对PHP项目的跨库实时数据同步,有几种主流的实现方案,由于PHP本身是同步阻塞的脚本语言,实现真正的“实时”(毫秒级)同步通常需要借助数据库自身机制或中间件。
以下是几种常用方案,从简单到复杂,按适用场景排序:
MySQL主从复制(最推荐,真正的实时同步)
这是MySQL原生支持的方案,性能高、延迟低(毫秒级),无需修改PHP代码。
原理:
源库(Master)将数据变更写入binlog(二进制日志),目标库(Slave)通过IO线程拉取并重放这些日志。
实现步骤:
- 配置源库(开启binlog)
-- my.cnf [mysqld] log-bin=mysql-bin server-id=1 binlog-do-db=source_db # 只同步指定库
- 配置目标库
[mysqld] server-id=2 relay-log=mysql-relay-bin replicate-do-db=target_db
- 创建复制用户
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'target_host' IDENTIFIED BY 'password';
- 执行同步命令(在目标库)
CHANGE MASTER TO MASTER_HOST='source_host',MASTER_USER='repl',MASTER_PASSWORD='password',MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=0; START SLAVE;
优点:
- 无需代码,数据库自带功能,稳定可靠
- 延迟极低(< 1秒)
- 支持异构库名(如
source_db.user同步到target_db.user_copy)
缺点:
- 必须使用MySQL双方
- 需要两个数据库的网络连通
触发器 + MySQL UDF/HTTP请求(适用中小项目)
如果数据库不同(如MySQL同步到PostgreSQL或SQL Server),或者需要实时触发PHP业务逻辑,可用此方案。
原理:
在源表上创建触发器,每次INSERT/UPDATE/DELETE时,调用MySQL的sys_exec()或HTTP请求(通过mysql-udf-http插件)通知PHP脚本。
示例(MySQL触发器中调用PHP接口):
-- 需要安装 mysql-udf-http 插件
CREATE TRIGGER sync_user AFTER INSERT ON user
FOR EACH ROW
BEGIN
DECLARE result INT;
SET result = sys_exec(CONCAT('curl -s "http://sync-server/sync.php?table=user&action=insert&id=', NEW.id, '"'));
END;
PHP接收端(sync.php):
<?php
$table = $_GET['table'];
$action = $_GET['action'];
// 根据参数执行跨库SQL操作
$targetDb = new PDO(`mysql:host=target_db...`);
$targetDb->exec(`INSERT INTO {$table} ...`);
优点:
- 支持异构数据库(PHP里自由操作任何DB)
- 可以触发复杂业务逻辑(如数据转换、发送通知)
缺点:
- 对MySQL性能有影响(每次DML都要额外执行HTTP请求)
- 延迟取决于网络和PHP处理速度(通常几十到几百ms)
- 触发器失控风险(出现错误不易排查)
Canal/DeCanal(阿里开源,适合高并发)
Canal模拟MySQL从库协议,解析binlog并推送给PHP消费端,适合需要高吞吐、低延迟的场景。
架构:
MySQL binlog -> Canal -> (kafka/RabbitMQ) -> PHP Consumer
PHP端代码示例(使用canal-php库):
$client = new CanalClient('127.0.0.1', 11111);
$client->subscribe('your_filter');
while (true) {
$messages = $client->get(100);
foreach ($messages as $msg) {
$rowData = $msg['rowData'];
// 解析表名、操作(INSERT/UPDATE/DELETE)和字段
syncToTargetDb($rowData);
}
}
优点:
- 对源库几乎零侵入(仅需开启binlog)
- 支持非常高的并发和延迟要求
- 支持多消费者(可同时同步到多个目标库)
缺点:
- 需要额外部署Java环境(Canal本身是Java写的)
- 学习成本高
PHP脚本定时轮询(最不推荐,但有特殊场景)
仅适合对实时性要求不高的场景(如每小时同步一次)。
原理:
PHP脚本(Crontab)定期查询源库的变更记录(比如有一个updated_at字段):
SELECT * FROM user WHERE updated_at > '$last_sync_time';
PHP脚本:
$lastSyncTime = file_get_contents('last_sync.txt');
$sourceDb = new PDO(...);
$targetDb = new PDO(...);
$rows = $sourceDb->query("SELECT * FROM user WHERE updated_at > '$lastSyncTime'");
foreach ($rows as $row) {
$targetDb->exec("REPLACE INTO user (id, name) VALUES ({$row['id']}, '{$row['name']}')");
}
file_put_contents('last_sync.txt', date('Y-m-d H:i:s'));
优点:
- 实现最简单
- 兼容任何数据库
缺点:
- 延迟高(分钟级到小时级)
- 无法捕获删除操作(除非用软删除)
- 高并发下重复同步压力大
如何选择(决策树)
| 需求 | 推荐方案 |
|---|---|
| 双方都是MySQL,要求毫秒级 | 主从复制(方案一) |
| 不同数据库(MySQL → PostgreSQL等) | 触发器+PHP(方案二) |
| 高并发、需要可靠的消息队列 | Canal + Kafka(方案三) |
| 实时性要求不高的内部系统 | PHP定时轮询(方案四) |
| 需要同步时转换数据格式 | 方案二或三,在PHP中处理 |
注意事项
- 主键冲突:跨库同步时,注意主键是否自增,建议使用
uuid或全局唯一ID,避免冲突。 - 死锁:如果两个库互相读写,可能形成循环复制,需要设置
ignore-table。 - 性能监控:主从复制用
SHOW SLAVE STATUS查看Seconds_Behind_Master;Canal用JMX监控消费速度。 - 失败回滚:建议在PHP客户端(方案二/三)加入重试机制和死信队列。
最推荐方案一(主从复制),简单稳定,大部分场景够用,如果确实需要异构数据库,再考虑方案二或三。