本文目录导读:

在PHP项目中实现MySQL主从同步,主要涉及数据库层面的配置和应用层面的读写分离,以下是完整的实现步骤:
数据库主从配置
主服务器配置 (Master)
# /etc/mysql/mysql.conf.d/mysqld.cnf [mysqld] server-id = 1 log_bin = /var/log/mysql/mysql-bin.log binlog_do_db = your_database_name # 要同步的数据库 binlog_ignore_db = mysql # 忽略的数据库
创建复制用户:
CREATE USER 'replication'@'%' IDENTIFIED BY 'password'; GRANT REPLICATION SLAVE ON *.* TO 'replication'@'%'; FLUSH PRIVILEGES;
查看主服务器状态:
SHOW MASTER STATUS; -- 记录 File 和 Position 值
从服务器配置 (Slave)
# /etc/mysql/mysql.conf.d/mysqld.cnf [mysqld] server-id = 2 relay_log = /var/log/mysql/mysql-relay-bin.log log_bin = /var/log/mysql/mysql-bin.log
配置从服务器连接主服务器:
CHANGE MASTER TO MASTER_HOST='master_ip', MASTER_USER='replication', MASTER_PASSWORD='password', MASTER_LOG_FILE='mysql-bin.000001', -- 从主服务器获取 MASTER_LOG_POS=123; -- 从主服务器获取 START SLAVE; -- 检查状态 SHOW SLAVE STATUS\G -- 确保 Slave_IO_Running 和 Slave_SQL_Running 都为 Yes
PHP应用层读写分离实现
基础连接配置
<?php
// config/database.php
return [
'master' => [
'host' => '192.168.1.100',
'port' => 3306,
'database' => 'your_database',
'username' => 'write_user',
'password' => 'write_password',
'charset' => 'utf8mb4',
],
'slaves' => [
[
'host' => '192.168.1.101',
'port' => 3306,
'database' => 'your_database',
'username' => 'read_user',
'password' => 'read_password',
'charset' => 'utf8mb4',
],
[
'host' => '192.168.1.102',
'port' => 3306,
'database' => 'your_database',
'username' => 'read_user',
'password' => 'read_password',
'charset' => 'utf8mb4',
],
],
];
PDO连接管理器
<?php
// lib/DatabaseManager.php
class DatabaseManager
{
private static $instances = [];
private $config;
private $masterConnection;
private $slaveConnection;
public function __construct($config)
{
$this->config = $config;
}
// 获取主库连接(写操作)
public function getMasterConnection()
{
if (!$this->masterConnection) {
$this->masterConnection = $this->createConnection($this->config['master']);
}
return $this->masterConnection;
}
// 获取从库连接(读操作)
public function getSlaveConnection()
{
// 随机选择一个从库
$slaves = $this->config['slaves'];
$slave = $slaves[array_rand($slaves)];
$key = $slave['host'] . ':' . $slave['port'];
if (!isset(self::$instances[$key])) {
self::$instances[$key] = $this->createConnection($slave);
}
return self::$instances[$key];
}
private function createConnection($dbConfig)
{
$dsn = sprintf(
"mysql:host=%s;port=%d;dbname=%s;charset=%s",
$dbConfig['host'],
$dbConfig['port'],
$dbConfig['database'],
$dbConfig['charset']
);
try {
$pdo = new PDO($dsn, $dbConfig['username'], $dbConfig['password'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
return $pdo;
} catch (PDOException $e) {
throw new Exception("Database connection failed: " . $e->getMessage());
}
}
}
读写分离的查询构建器
<?php
// lib/QueryBuilder.php
class QueryBuilder
{
private $dbManager;
public function __construct(DatabaseManager $dbManager)
{
$this->dbManager = $dbManager;
}
// 写操作 - 使用主库
public function insert($table, $data)
{
$pdo = $this->dbManager->getMasterConnection();
$columns = implode(', ', array_keys($data));
$placeholders = ':' . implode(', :', array_keys($data));
$sql = "INSERT INTO {$table} ({$columns}) VALUES ({$placeholders})";
$stmt = $pdo->prepare($sql);
return $stmt->execute($data);
}
// 读操作 - 使用从库
public function select($table, $conditions = [], $fields = '*')
{
$pdo = $this->dbManager->getSlaveConnection();
$sql = "SELECT {$fields} FROM {$table}";
if (!empty($conditions)) {
$where = [];
foreach ($conditions as $key => $value) {
$where[] = "{$key} = :{$key}";
}
$sql .= " WHERE " . implode(' AND ', $where);
}
$stmt = $pdo->prepare($sql);
$stmt->execute($conditions);
return $stmt->fetchAll();
}
// 更新操作 - 使用主库
public function update($table, $data, $conditions)
{
$pdo = $this->dbManager->getMasterConnection();
$setParts = [];
foreach ($data as $key => $value) {
$setParts[] = "{$key} = :set_{$key}";
}
$whereParts = [];
foreach ($conditions as $key => $value) {
$whereParts[] = "{$key} = :where_{$key}";
}
$sql = "UPDATE {$table} SET " . implode(', ', $setParts)
. " WHERE " . implode(' AND ', $whereParts);
$params = [];
foreach ($data as $key => $value) {
$params["set_{$key}"] = $value;
}
foreach ($conditions as $key => $value) {
$params["where_{$key}"] = $value;
}
$stmt = $pdo->prepare($sql);
return $stmt->execute($params);
}
// 删除操作 - 使用主库
public function delete($table, $conditions)
{
$pdo = $this->dbManager->getMasterConnection();
$whereParts = [];
foreach ($conditions as $key => $value) {
$whereParts[] = "{$key} = :{$key}";
}
$sql = "DELETE FROM {$table} WHERE " . implode(' AND ', $whereParts);
$stmt = $pdo->prepare($sql);
return $stmt->execute($conditions);
}
}
使用方法
<?php
// index.php
require_once 'config/database.php';
require_once 'lib/DatabaseManager.php';
require_once 'lib/QueryBuilder.php';
// 初始化
$config = include 'config/database.php';
$dbManager = new DatabaseManager($config);
$queryBuilder = new QueryBuilder($dbManager);
// 写操作 - 自动使用主库
$userId = $queryBuilder->insert('users', [
'name' => '张三',
'email' => 'zhangsan@example.com',
'created_at' => date('Y-m-d H:i:s')
]);
// 读操作 - 自动使用从库
$users = $queryBuilder->select('users', ['status' => 1], 'id, name, email');
// 更新操作 - 自动使用主库
$queryBuilder->update('users',
['status' => 2, 'updated_at' => date('Y-m-d H:i:s')],
['id' => $userId]
);
// 删除操作 - 自动使用主库
$queryBuilder->delete('users', ['id' => $userId]);
高级优化方案
连接池实现(使用Swoole)
<?php
// lib/ConnectionPool.php
class ConnectionPool
{
private $pool;
private $maxConnections;
private $connectionConfig;
public function __construct($config, $maxConnections = 10)
{
$this->connectionConfig = $config;
$this->maxConnections = $maxConnections;
$this->pool = new SplQueue();
}
public function getConnection()
{
if ($this->pool->count() > 0) {
return $this->pool->dequeue();
}
return $this->createConnection();
}
public function releaseConnection($connection)
{
if ($this->pool->count() < $this->maxConnections) {
$this->pool->enqueue($connection);
} else {
$connection = null;
}
}
private function createConnection()
{
// 创建连接逻辑
}
}
主从延迟处理
<?php
// 对于关键数据的读取,强制使用主库
class CriticalDataReader
{
private $dbManager;
public function getLatestUserData($userId)
{
// 某些场景下,读最新数据需要强制用主库
$pdo = $this->dbManager->getMasterConnection();
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$userId]);
return $stmt->fetch();
}
}
监控和告警
<?php
// lib/Monitor.php
class Monitor
{
public function checkReplicationStatus()
{
// 检查主从复制状态
$pdo = $this->dbManager->getSlaveConnection();
$stmt = $pdo->query("SHOW SLAVE STATUS");
$status = $stmt->fetch();
if ($status['Slave_IO_Running'] !== 'Yes' ||
$status['Slave_SQL_Running'] !== 'Yes') {
// 发送告警
$this->sendAlert('Replication is down!');
}
// 检查延迟
$secondsBehind = $status['Seconds_Behind_Master'];
if ($secondsBehind > 60) {
$this->sendAlert("Replication lag: {$secondsBehind} seconds");
}
}
private function sendAlert($message)
{
// 发送邮件、短信或使用告警系统
error_log($message);
// 实际项目中可以使用第三方服务
}
}
注意事项
- 事务处理:事务中的读写都应该使用主库
- 主从延迟:对实时性要求高的场景,考虑强制使用主库
- 连接管理:使用连接池减少连接开销
- 负载均衡:合理分配读请求到多个从库
- 监控告警:定期检查主从同步状态
- 容错处理:从库故障时自动切换到其他从库或主库
这个实现方案提供了完整的MySQL主从同步解决方案,从数据库配置到PHP应用层实现,涵盖了读写分离、连接管理、延迟处理等核心功能。