數據分庫是為了解決單個數據庫性能瓶頸,提升系統擴展性和穩定性。1.水平分庫通過哈希等規則將數據分散到多個數據庫,優點是可擴展性強,但存在跨庫事務和數據傾斜問題;2.垂直分庫按業務模塊拆分數據,優點是結構清晰便于維護,但可能仍存在單模塊數據量過大的問題;3.讀寫分離通過主從復制將讀寫操作分離,提高讀性能但存在延遲風險。實際應用中可根據業務需求選擇合適方案或組合使用,并需關注數據一致性與遷移策略。
數據分庫,簡單來說,就是把原本放在一個數據庫里的數據,拆分到多個數據庫中。為什么要這么做?最直接的原因就是數據量太大,單個數據庫扛不住了。更深層的原因,是為了提升性能、擴展性,讓系統更穩定。
數據分庫的核心目標是將數據分散存儲,以減輕單個數據庫的壓力,提高整體的讀寫能力。下面介紹三種常見的數據分庫架構方案,并附帶一些php實現上的考量。
水平分庫(Sharding)
水平分庫是最常見的分庫方式。它按照某種規則(例如用戶ID的哈希值),將數據分散到不同的數據庫中。每個數據庫存儲的是一部分數據,所有數據庫的數據加起來才是完整的數據集。
立即學習“PHP免費學習筆記(深入)”;
優點:
- 解決單庫數據量過大瓶頸。
- 理論上可以無限擴展,只要增加數據庫實例即可。
缺點:
- 跨庫事務處理復雜。
- 數據遷移和擴容需要重新計算路由規則。
- 需要考慮數據傾斜問題,即某些數據庫的數據量遠大于其他數據庫。
PHP實現示例(簡化版):
<?php class ShardingDatabase { private $databases; private $shardingKey; public function __construct(array $databases, string $shardingKey) { $this->databases = $databases; $this->shardingKey = $shardingKey; } private function getDatabase(int $shardingValue): PDO { $dbCount = count($this->databases); $dbIndex = $shardingValue % $dbCount; return $this->databases[$dbIndex]; // 假設 databases 數組存儲的是 PDO 連接 } public function query(string $sql, array $params, int $shardingValue) { $db = $this->getDatabase($shardingValue); $stmt = $db->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(PDO::FETCH_ASSOC); } public function insert(string $sql, array $params, int $shardingValue): bool { $db = $this->getDatabase($shardingValue); $stmt = $db->prepare($sql); return $stmt->execute($params); } } // 示例用法 $databases = [ new PDO("mysql:host=db1;dbname=users", "user", "password"), new PDO("mysql:host=db2;dbname=users", "user", "password"), ]; $shardingDb = new ShardingDatabase($databases, 'user_id'); // 插入數據,根據 user_id 分庫 $userId = 123; $sql = "INSERT INTO users (user_id, name) VALUES (:user_id, :name)"; $params = [':user_id' => $userId, ':name' => 'Alice']; $shardingDb->insert($sql, $params, $userId); // 查詢數據,同樣根據 user_id $sql = "SELECT * FROM users WHERE user_id = :user_id"; $params = [':user_id' => $userId]; $result = $shardingDb->query($sql, $params, $userId); print_r($result); ?>
這個例子非常簡化,實際應用中需要考慮連接池、錯誤處理、更復雜的路由規則等等。$shardingKey 定義了根據哪個字段進行分片,$shardingValue 則是該字段的具體值。
垂直分庫
垂直分庫是按照業務模塊將數據分散到不同的數據庫中。例如,可以將用戶相關的表放在一個數據庫,訂單相關的表放在另一個數據庫。
優點:
- 業務清晰,易于維護。
- 可以針對不同的業務模塊選擇不同的數據庫類型。
缺點:
- 某些業務模塊的數據量仍然可能很大。
- 跨模塊的關聯查詢需要通過服務調用或者數據同步來實現,增加了復雜度。
PHP實現示例:
垂直分庫在PHP代碼層面,更多的是體現在配置和管理上。你可以創建多個數據庫連接配置,每個配置對應一個業務數據庫。
<?php class DatabaseConfig { public static function getUserDbConfig(): array { return [ 'host' => 'user_db_host', 'dbname' => 'user_db', 'user' => 'user', 'password' => 'password', ]; } public static function getOrderDbConfig(): array { return [ 'host' => 'order_db_host', 'dbname' => 'order_db', 'user' => 'user', 'password' => 'password', ]; } } class UserDatabase { private $db; public function __construct() { $config = DatabaseConfig::getUserDbConfig(); $dsn = "mysql:host={$config['host']};dbname={$config['dbname']}"; $this->db = new PDO($dsn, $config['user'], $config['password']); } public function getUserById(int $userId): array { $stmt = $this->db->prepare("SELECT * FROM users WHERE id = :id"); $stmt->execute([':id' => $userId]); return $stmt->fetch(PDO::FETCH_ASSOC); } } class OrderDatabase { private $db; public function __construct() { $config = DatabaseConfig::getOrderDbConfig(); $dsn = "mysql:host={$config['host']};dbname={$config['dbname']}"; $this->db = new PDO($dsn, $config['user'], $config['password']); } public function getOrdersByUserId(int $userId): array { $stmt = $this->db->prepare("SELECT * FROM orders WHERE user_id = :user_id"); $stmt->execute([':user_id' => $userId]); return $stmt->fetchAll(PDO::FETCH_ASSOC); } } // 示例用法 $userDb = new UserDatabase(); $orderDb = new OrderDatabase(); $user = $userDb->getUserById(1); $orders = $orderDb->getOrdersByUserId(1); print_r($user); print_r($orders); ?>
這個例子展示了如何通過不同的配置,連接到不同的業務數據庫。
讀寫分離
讀寫分離是將讀操作和寫操作分別路由到不同的數據庫。通常情況下,寫操作路由到主庫,讀操作路由到從庫。主庫負責數據的更新,從庫負責數據的查詢。數據通過主從復制同步。
優點:
- 提高讀操作的性能。
- 降低主庫的壓力。
缺點:
- 數據存在延遲,可能出現讀到舊數據的情況。
- 需要考慮主從復制的延遲問題。
PHP實現示例:
<?php class ReadWriteDatabase { private $masterDb; private $slaveDbs; public function __construct(PDO $masterDb, array $slaveDbs) { $this->masterDb = $masterDb; $this->slaveDbs = $slaveDbs; } private function getSlaveDb(): PDO { $slaveCount = count($this->slaveDbs); $slaveIndex = rand(0, $slaveCount - 1); // 隨機選擇一個從庫 return $this->slaveDbs[$slaveIndex]; } public function query(string $sql, array $params, bool $isWrite = false) { $db = $isWrite ? $this->masterDb : $this->getSlaveDb(); $stmt = $db->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(PDO::FETCH_ASSOC); } public function insert(string $sql, array $params): bool { return $this->query($sql, $params, true); // 強制寫主庫 } public function update(string $sql, array $params): bool { return $this->query($sql, $params, true); // 強制寫主庫 } } // 示例用法 $masterDb = new PDO("mysql:host=master_db;dbname=mydb", "user", "password"); $slaveDbs = [ new PDO("mysql:host=slave_db1;dbname=mydb", "user", "password"), new PDO("mysql:host=slave_db2;dbname=mydb", "user", "password"), ]; $readWriteDb = new ReadWriteDatabase($masterDb, $slaveDbs); // 插入數據,寫主庫 $sql = "INSERT INTO users (name) VALUES (:name)"; $params = [':name' => 'Bob']; $readWriteDb->insert($sql, $params); // 查詢數據,讀從庫 $sql = "SELECT * FROM users WHERE name = :name"; $params = [':name' => 'Bob']; $result = $readWriteDb->query($sql, $params); print_r($result); ?>
這個例子展示了如何將寫操作路由到主庫,讀操作隨機路由到從庫。
如何選擇合適的分庫方案?
選擇哪種分庫方案,取決于你的具體業務場景。如果數據量巨大,且對事務要求不高,可以考慮水平分庫。如果業務模塊清晰,可以考慮垂直分庫。如果讀操作遠多于寫操作,可以考慮讀寫分離。當然,也可以將多種方案結合使用。
分庫后如何保證數據一致性?
數據一致性是分庫后需要重點考慮的問題。可以使用分布式事務、最終一致性方案等來保證數據一致性。
如何進行數據遷移?
數據遷移是一個復雜的過程,需要仔細規劃。可以使用工具或者編寫腳本來進行數據遷移。在遷移過程中,需要保證數據的完整性和可用性。