本文目录导读:

- 使用 PDO + 自定义连接管理(基础方案)
- 使用 ThinkPHP 框架(内置支持)
- 使用 Laravel 框架(内置读写分离)
- 使用 Hyberf/ThinkSwoole(协程方案)
- 使用重工具库(如 Medoo,支持主从分离)
- 推荐方案
在 PHP 中实现多主多从数据库连接,核心在于读写分离和负载均衡,以下是几种常用的实现方案:
使用 PDO + 自定义连接管理(基础方案)
<?php
class DatabaseManager {
private static $instance = null;
private $writeConnections = [];
private $readConnections = [];
// 主库配置(可多个)
private $writeConfigs = [
[
'host' => '192.168.1.10',
'dbname' => 'test',
'user' => 'admin',
'pass' => 'password',
'port' => 3306
],
[
'host' => '192.168.1.11',
'dbname' => 'test',
'user' => 'admin',
'pass' => 'password',
'port' => 3306
]
];
// 从库配置(可多个)
private $readConfigs = [
[
'host' => '192.168.2.10',
'dbname' => 'test',
'user' => 'readonly',
'pass' => 'read12138',
'port' => 3306
],
[
'host' => '192.168.2.11',
'dbname' => 'test',
'user' => 'readonly',
'pass' => 'read12138',
'port' => 3306
]
];
// 当前连接选择
private $currentConnection = null;
private function __construct() {}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
// 获取写连接(轮询策略)
private function getWriteConnection() {
if (empty($this->writeConnections)) {
foreach ($this->writeConfigs as $config) {
$this->writeConnections[] = $this->createConnection($config);
}
}
// 轮询选择主库
static $writeIndex = 0;
$connection = $this->writeConnections[$writeIndex % count($this->writeConnections)];
$writeIndex++;
return $connection;
}
// 获取读连接(负载均衡策略)
private function getReadConnection() {
if (empty($this->readConnections)) {
foreach ($this->readConfigs as $config) {
$this->readConnections[] = $this->createConnection($config);
}
}
// 随机选择从库(也可以使用加权轮询)
$connection = $this->readConnections[array_rand($this->readConnections)];
return $connection;
}
// 创建PDO连接
private function createConnection($config) {
$dsn = "mysql:host={$config['host']};dbname={$config['dbname']};port={$config['port']}";
return new PDO($dsn, $config['user'], $config['pass'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_TIMEOUT => 5
]);
}
// 执行写入操作
public function write($sql, $params = []) {
$conn = $this->getWriteConnection();
$stmt = $conn->prepare($sql);
$stmt->execute($params);
return $stmt->rowCount();
}
// 执行读取操作
public function read($sql, $params = []) {
$conn = $this->getReadConnection();
$stmt = $conn->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll();
}
// 获取最后插入ID(从写连接)
public function getInsertId() {
$conn = $this->getWriteConnection();
return $conn->lastInsertId();
}
// 事务处理(必须在主库)
public function transaction($callback) {
$conn = $this->getWriteConnection();
try {
$conn->beginTransaction();
$result = $callback($conn);
$conn->commit();
return $result;
} catch (Exception $e) {
$conn->rollBack();
throw $e;
}
}
}
// 使用示例
$db = DatabaseManager::getInstance();
// 读操作
$users = $db->read("SELECT * FROM users WHERE status = ?", [1]);
// 写操作
$db->write("INSERT INTO users (name, email) VALUES (?, ?)", ['张三', 'zhangsan@example.com']);
$id = $db->getInsertId();
// 事务操作
$db->transaction(function ($conn) {
$conn->exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
$conn->exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2");
});
?>
使用 ThinkPHP 框架(内置支持)
// 数据库配置文件 `config/database.php`
return [
// 默认使用主库
'default' => env('database.driver', 'mysql'),
// 主库配置(多个)
'connections' => [
'mysql' => [
'type' => 'mysql',
'host' => '192.168.1.10,192.168.1.11', // 多个主库
'port' => '3306',
'database' => 'test',
'username' => 'admin',
'password' => 'password',
'charset' => 'utf8mb4',
'prefix' => '',
'debug' => true,
'read' => [
// 从库配置(多个)
[
'host' => '192.168.2.10',
'username' => 'readonly',
'password' => 'read12138'
],
[
'host' => '192.168.2.11',
'username' => 'readonly',
'password' => 'read12138'
]
],
'write' => [
// 写库配置(可选,默认使用主库)
[
'host' => '192.168.1.10',
'username' => 'admin',
'password' => 'password'
]
],
],
],
];
// 使用示例
$users = Db::name('users')->select(); // 自动使用从库
Db::name('users')->insert(['name' => '张三']); // 自动使用主库
使用 Laravel 框架(内置读写分离)
// 数据库配置文件 `config/database.php`
'mysql' => [
'driver' => 'mysql',
'read' => [
// 多个从库
'host' => [
'192.168.2.10',
'192.168.2.11',
'192.168.2.12'
],
],
'write' => [
// 多个主库
'host' => [
'192.168.1.10',
'192.168.1.11'
],
],
'sticky' => true, // 记住会话中已写入的数据库,防止主从同步延迟
'database' => env('DB_DATABASE', 'test'),
'username' => env('DB_USERNAME', 'admin'),
'password' => env('DB_PASSWORD', 'password'),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],
// 使用示例
$users = DB::table('users')->get(); // 自动使用从库
DB::table('users')->insert(['name' => '张三']); // 自动使用主库
使用 Hyberf/ThinkSwoole(协程方案)
<?php
declare(strict_types=1);
namespace App\Pool;
use Hyperf\DbConnection\Db;
use Hyperf\Utils\Coroutine;
class DatabaseService {
// 主库连接池
private static $masterPools = [
'db1' => ['host' => '192.168.1.10'],
'db2' => ['host' => '192.168.1.11']
];
// 从库连接池
private static $slavePools = [
'db1' => ['host' => '192.168.2.10'],
'db2' => ['host' => '192.168.2.11']
];
public static function read(string $sql, array $params = []) {
// 随机从从库池中选择
$pool = self::$slavePools[array_rand(self::$slavePools)];
return Db::connection($pool['host'])->select($sql, $params);
}
public static function write(string $sql, array $params = []) {
// 轮询从主库池中选择
static $index = 0;
$pools = array_values(self::$masterPools);
$pool = $pools[$index % count($pools)];
$index++;
$result = Db::connection($pool['host'])->insert($sql, $params);
return $result;
}
}
使用重工具库(如 Medoo,支持主从分离)
<?php
use Medoo\Medoo;
$database = new Medoo([
'type' => 'mysql',
'master' => [
['host' => '192.168.1.10', 'username' => 'admin', 'password' => 'password'],
['host' => '192.168.1.11', 'username' => 'admin', 'password' => 'password']
],
'slave' => [
['host' => '192.168.2.10', 'username' => 'readonly', 'password' => 'read123'],
['host' => '192.168.2.11', 'username' => 'readonly', 'password' => 'read123']
],
'database' => 'test',
'error' => PDO::ERRMODE_EXCEPTION
]);
// 读操作(自动路由到从库)
$users = $database->select("users", "*");
// 写操作(自动路由到主库)
$database->insert("users", ["name" => "张三"]);
- 读写分离:根据 SQL 类型自动路由
- 负载均衡:主库轮询写入,从库随机/轮询读取
- 故障转移:某个主/从挂掉后可自动切换
- 一致性处理:使用
sticky机制防止主从延迟导致的数据不一致 - 监控与统计:记录各节点 SQL 执行时间和状态
推荐方案
- 简单项目:使用 PDO 自定义连接管理
- 中型项目:使用 ThinkPHP 或 Laravel 内置支持
- 分布式大型项目:使用 MyCat/ShardingSphere 做中间件代理
- 高并发场景:使用 Swoole 协程连接池
选择方案时需要考虑项目的复杂度、团队技术栈和业务需求。