前言php
你們能夠從任何一個gii生成model類開始代碼上溯,會發現:yii2的model層基於ActiveRecord實現DAO訪問數據庫的能力。mysql
而ActiveRecord的繼承鏈能夠繼續上溯,最終會發現model實際上是一個component,而component是yii2作IOC的重要組成部分,提供了behaviors,event的能力供繼承者擴展。sql
(IOC,component,behaviors,event等概念能夠參考http://www.digpage.com/學習)數據庫
先不考慮上面的一堆概念,一個站點發展歷程通常是1個庫1個表,1個庫N個表,M個庫N個表這樣走過來的,下面拿訂單表爲例,分別說說。服務器
1)1庫1表:yii2默認採用PDO鏈接mysql,框架默認會配置一個叫作db的component做爲惟一的mysql鏈接對象,其中dsn分配了數據庫地址,數據庫名稱,配置以下:yii2
?app
1框架 2運維 3yii 4 5 6 7 8 |
'components' => [ 'db' => [ 'class' => 'yii\db\Connection' , 'dsn' => 'mysql:host=10.10.10.10;port=4005;dbname=wordpress' , 'username' => 'wp' , 'password' => '123' , 'charset' => 'utf8' , ], |
這就是yii2作IOC的一個典型事例,model層默認就會取這個db作爲mysql鏈接對象,因此model訪問都通過這個connection,能夠從ActiveRecord類裏看到。
?
1 2 3 4 5 6 7 8 9 10 11 12 |
class ActiveRecord extends BaseActiveRecord { /** * Returns the database connection used by this AR class. * By default, the "db" application component is used as the database connection. * You may override this method if you want to use a different database connection. * @return Connection the database connection used by this AR class. */ public static function getDb() { return Yii:: $app ->getDb(); } |
追蹤下去,最後會走yii2的ioc去建立名字叫作」db」的這個component返回給model層使用。
?
1 2 3 4 5 6 7 8 9 |
abstract class Application extends Module { /** * Returns the database connection component. * @return \yii\db\Connection the database connection. */ public function getDb() { return $this ->get( 'db' ); } |
yii2上述實現決定了只能鏈接了1臺數據庫服務器,選擇了其中1個database,那麼具體訪問哪一個表,是經過在Model裏覆寫tableName這個static方法實現的,ActiveRecord會基於覆寫的tableName來決定表名是什麼。
?
1 2 3 4 5 6 7 8 9 10 |
class OrderInfo extends \yii\db\ActiveRecord { /** * @inheritdoc * @return */ public static function tableName() { return 'order_info' ; } |
2)1庫N表:由於orderInfo數據量變大,各方面性能指標有所降低,而單機硬件性能還有較大冗餘,因而能夠考慮分多張order_info表,均攤數據量。假設咱們要份8張表,那麼能夠依據uid(用戶ID)%8來決定訂單存儲在哪一個表裏。
然而1庫1表的時候,tableName()
返回是的order_info,因而理所應當的重載這個函數,提供一種動態變化的能力便可,例如:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class OrderInfo extends \yii\db\ActiveRecord { private static $partitionIndex_ = null; // 分表ID /** * 重置分區id * @param unknown $uid */ private static function resetPartitionIndex( $uid = null) { $partitionCount = \Yii:: $app ->params[ 'Order' ][ 'partitionCount' ]; self:: $partitionIndex_ = $uid % $partitionCount ; } /** * @inheritdoc */ public static function tableName() { return 'order_info' . self:: $partitionIndex_ ; } |
提供一個resetParitionIndex($uid)
函數,在每次操做model以前主動調用來標記分表的下標,而且重載tableName來爲model層拼接生成本次操做的表名。
3)M庫N表:1庫N表逐漸發展,單機存儲和性能達到瓶頸,只能將數據分散到多個服務器存儲,因而提出了分庫的需求。可是從」1庫1表」的框架實現邏輯來看,model層默認取db配置做爲mysql鏈接的話,是沒有辦法訪問多個mysql實例的,因此必須解決這個問題。
通常產生這個需求,產品已經進入中期穩步發展階段。有2個思路解決M庫問題,1種是yii2經過改造直連多個地址進行訪問多庫,1種是yii2仍舊只連1個地址,而這個地址部署了dbproxy,由dbproxy根據你訪問的庫名代理鏈接多個庫。
若是此前沒有熟練的運維過dbproxy,而且php集羣規模沒有大到單個mysql實例客戶端鏈接數過多拒絕服務的境地,那麼第1種方案就能夠解決了。不然,應該選擇第2種方案。
不管選擇哪一種方案,咱們都應該進一步改造tableName()
函數,爲database名稱提供動態變化的能力,和table動態變化相似。
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class OrderInfo extends \yii\db\ActiveRecord { private static $databaseIndex_ = null; // 分庫ID private static $partitionIndex_ = null; // 分表ID /** * 重置分區id * @param unknown $uid */ private static function resetPartitionIndex( $uid = null) { $databaseCount = \Yii:: $app ->params[ 'Order' ][ 'databaseCount' ]; $partitionCount = \Yii:: $app ->params[ 'Order' ][ 'partitionCount' ]; // 先決定分到哪一張表裏 self:: $partitionIndex_ = $uid % $partitionCount ; // 再根據表的下標決定分到哪一個庫裏 self:: $databaseIndex_ = intval (self:: $partitionIndex_ / ( $partitionCount / $databaseCount )); } /** * @inheritdoc */ public static function tableName() { $database = 'wordpress' . self:: $databaseIndex_ ; $table = 'order_info' . self:: $partitionIndex_ ; return $database . '.' . $table ; } |
在分表邏輯基礎上稍做改造,便可實現分庫。假設分8張表,那麼分別是00,01,02,03…07,而後決定分4個庫,那麼00,01表在00庫,02,03表在01庫,04,05表在02庫,06,07表在03庫,根據這個規律對應的計算代碼如上。最終ActiveRecord生效的代碼都會相似於」select * from wordpress0.order_info1
″,這樣就能夠解決鏈接dbproxy訪問多庫的需求了。
那麼yii直接訪問多Mysql實例怎麼作呢,其實相似tableName()
,咱們只須要覆蓋getDb()
方法便可,同時要求咱們首先配置好4個mysql實例,從而能夠經過yii的application經過IOC設計來生成多個db鏈接,全部改動以下:
先配置好4個數據庫,給予不一樣的component id以便區分,它們鏈接了不一樣的mysql實例,其中dsn裏的dbname只要存在便可(防止PDO執行use database
時候不存在報錯),真實的庫名是經過tableName()
動態變化的。
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
'db0' => [ 'class' => 'yii\db\Connection' , 'dsn' => 'mysql:host=10.10.10.10;port=6184;dbname=wordpress0' , 'username' => 'wp' , 'password' => '123' , 'charset' => 'utf8' , // 'tablePrefix' => 'ktv_', ], 'db1' => [ 'class' => 'yii\db\Connection' , 'dsn' => 'mysql:host=10.10.10.11;port=6184;dbname=wordpress2' , 'username' => 'wp' , 'password' => '123' , 'charset' => 'utf8' , // 'tablePrefix' => 'ktv_', ], 'db2' => [ 'class' => 'yii\db\Connection' , 'dsn' => 'mysql:host=10.10.10.12;port=6184;dbname=wordpress4' , 'username' => 'wp' , 'password' => '123' , 'charset' => 'utf8' , // 'tablePrefix' => 'ktv_', ], 'db3' => [ 'class' => 'yii\db\Connection' , 'dsn' => 'mysql:host=10.10.10.13;port=6184;dbname=wordpress6' , 'username' => 'wp' , 'password' => '123' , 'charset' => 'utf8' , // 'tablePrefix' => 'ktv_', ], |
覆寫getDb()
方法,根據庫下標返回不一樣的數據庫鏈接便可。
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
class OrderInfo extends \yii\db\ActiveRecord { private static $databaseIndex_ = null; // 分庫ID private static $partitionIndex_ = null; // 分表ID /** * 重置分區id * @param unknown $uid */ private static function resetPartitionIndex( $uid = null) { $databaseCount = \Yii:: $app ->params[ 'Order' ][ 'databaseCount' ]; $partitionCount = \Yii:: $app ->params[ 'Order' ][ 'partitionCount' ]; // 先決定分到哪一張表裏 self:: $partitionIndex_ = $uid % $partitionCount ; // 再根據表的下標決定分到哪一個庫裏 self:: $databaseIndex_ = intval (self:: $partitionIndex_ / ( $partitionCount / $databaseCount )); } /** * 根據分庫分表,返回庫名.表名 */ public static function tableName() { $database = 'wordpress' . self:: $databaseIndex_ ; $table = 'order_info' . self:: $partitionIndex_ ; return $database . '.' . $table ; } /** * 根據分庫結果,返回不一樣的數據庫鏈接 */ public static function getDb() { return \Yii:: $app ->get( 'db' . self:: $databaseIndex_ ); } |
這樣,不管是yii鏈接多個mysql實例,仍是yii鏈接1個dbproxy,均可以實現了。
網上有一些例子,試圖經過component的event機制,經過在component的配置中指定onUpdate,onBeforeSave等自定義event去hook不一樣的DAO操做來隱式(自動)的變動database或者connection或者tablename的作法,都是基於model object才能實現的,若是直接使用model class的相似updateAll()
方法的話,是繞過DAO直接走了PDO的,不會觸發這些event,因此並非完備的解決方案。
這樣的方案原理簡單,方案對框架無侵入,只是每次DB操做前都要顯式的resetPartitionIndex($uid)
調用。若是要作到用戶無感知,那必須對ActiveRecord類進行繼承,進一步覆蓋全部class method的實現以便插入選庫選表邏輯,代價太高。
補充:關於分庫分表的一些實踐細節,分表數量建議2^n,例如n=3的狀況下分8張表,而後肯定一下幾個庫,庫數量是2^m,但要<=表數量,例如這裏1個庫,2個庫,4個庫,8個庫都是能夠的,表順序坐落在這些庫裏便可。
爲何數量都是2指數,是由於若是面臨擴容需求,數據的遷移將方便一些。假設分了2張表,數據按uid%2打散,要擴容成4張表,那麼只須要把表0的部分數據遷移到表2,表1的部分數據遷移到表3,便可完成擴容,也就是uid%2和uid%4形成的遷移量是很小的,這個能夠本身算一下。
總結
以上就是關於yii2實現分庫分表的所有內容了,但願本文的內容對你們的學習或者工做能帶來必定的幫助,若是有疑問你們能夠留言交流。