首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 實作 MySQL 讀寫分離:一主多從架構完整代碼方案

     編輯:PHP 相關知識

隨著流量增長,單一 MySQL 實例難以承受高併發讀寫壓力。主從複製 + 讀寫分離是經典擴展策略:寫入操作走 Master,讀取操作分流至多台 Slave。本文提供一個輕量級 PHP 類別,封裝連線管理、自動路由、故障切換核心邏輯,可直接整合進專案。

核心特性

  • 一主多從,Slave 陣列可動態擴充
  • 自動判斷 SQL 類型:SELECT 走 Slave,其餘走 Master
  • Slave 故障自動降級至 Master,不中斷服務
  • 保留原生 mysqli 介面,支援預處理、事務、多查詢等所有功能
  • 缺點:長連線模式下需同時維持 Master/Slave 連線

完整類別代碼 (RW_Mysqli.php)

<?php
class RW_Mysqli {
    private $master;
    private $slaves = [];
    private $slaveIndex = 0;
    private $lastUsed = 'master'; // 'master' | 'slave'

    // 設定:$masterCfg, $slaveCfgs 皆為 ['host'=>, 'user'=>, 'pass'=>, 'db'=>, 'port'=>3306]
    public function __construct(array $masterCfg, array $slaveCfgs = []) {
        $this->master = $this->connect($masterCfg);
        foreach ($slaveCfgs as $cfg) {
            $link = $this->connect($cfg);
            if ($link) $this->slaves[] = $link;
        }
    }

    private function connect(array $cfg) {
        $mysqli = @new mysqli($cfg['host'], $cfg['user'], $cfg['pass'], $cfg['db'], $cfg['port'] ?? 3306);
        if ($mysqli->connect_errno) {
            error_log("MySQL 連線失敗 [{$cfg['host']}]: " . $mysqli->connect_error);
            return false;
        }
        $mysqli->set_charset('utf8mb4');
        return $mysqli;
    }

    // 核心:取得執行查詢的連線
    private function getLink(string $sql): mysqli {
        $isSelect = (stripos(trim($sql), 'SELECT') === 0);
        if ($isSelect && $this->slaves) {
            // 簡單輪詢
            $link = $this->slaves[$this->slaveIndex];
            $this->slaveIndex = ($this->slaveIndex + 1) % count($this->slaves);
            // 檢查連線存活
            if (!$link->ping()) {
                error_log("Slave {$link->host_info} ping 失敗,降級至 Master");
                return $this->master;
            }
            $this->lastUsed = 'slave';
            return $link;
        }
        $this->lastUsed = 'master';
        return $this->master;
    }

    // 對外介面:完全模擬 mysqli 常用方法
    public function query(string $sql) {
        return $this->getLink($sql)->query($sql);
    }
    public function prepare(string $sql) {
        return $this->getLink($sql)->prepare($sql);
    }
    public function begin_transaction() { return $this->master->begin_transaction(); }
    public function commit() { return $this->master->commit(); }
    public function rollback() { return $this->master->rollback(); }
    public function affected_rows() { return $this->master->affected_rows; }
    public function insert_id() { return $this->master->insert_id; }
    public function close() {
        $this->master->close();
        foreach ($this->slaves as $s) $s->close();
    }
    // 魔術方法代理其餘 mysqli 方法至 Master
    public function __call($method, $args) {
        return $this->master->$method(...$args);
    }
    public function getLastUsed(): string { return $this->lastUsed; }
}
?>

使用範例

<?php
require 'RW_Mysqli.php';

$master = ['host'=>'10.0.0.1', 'user'=>'rw', 'pass'=>'secret', 'db'=>'app'];
$slaves = [
    ['host'=>'10.0.0.2', 'user'=>'ro', 'pass'=>'secret', 'db'=>'app'],
    ['host'=>'10.0.0.3', 'user'=>'ro', 'pass'=>'secret', 'db'=>'app'],
];

$db = new RW_Mysqli($master, $slaves);

// 寫入 → Master
$db->query("INSERT INTO logs (msg) VALUES ('訪問首頁')");

// 讀取 → Slave (輪詢)
$res = $db->query("SELECT * FROM logs ORDER BY id DESC LIMIT 10");
while ($row = $res->fetch_assoc()) { /* ... */ }

// 預處理語句同樣自動路由
$stmt = $db->prepare("SELECT * FROM users WHERE email = ?");
$stmt->bind_param('s', $email);
$stmt->execute();

// 事務強制走 Master
$db->begin_transaction();
try {
    $db->query("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
    $db->query("UPDATE accounts SET balance = balance + 100 WHERE id = 2");
    $db->commit();
} catch (Exception $e) {
    $db->rollback();
}
?>

生產環境建議強化

  1. 連線池:搭配 Swoole/RoadRunner 或 PHP-FPM 持久連線減少握手開銷。
  2. 健康檢查:定期 ping() 或 SHOW SLAVE STATUS 檢測複製延遲,自動剔除落後過大的 Slave。
  3. 權重路由:依據 Slave 硬體規格分配讀取權重,而非簡單輪詢。
  4. 讀寫一致性:關鍵業務(如註冊後立即查詢)可強制 $db->master->query() 讀 Master,避免複製延遲導致「剛寫入讀不到」。
  5. 監控告警:記錄 getLastUsed() 分佈,掌握讀寫比例與故障切換頻率。

此方案零依賴、易理解、易擴展,適合中小型專案快速導入讀寫分離。大規模系統建議改用 ProxySQL、MyCat 或 Vitess 等中間件層解耦。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved