在開發中,為了降低單點壓力,通常會根據業務情況進行分表分庫,將表分布在不同的庫中(庫可能分布在不同的機器上)。在這種場景下,事務的提交會變得相對復雜,因為多個節點(庫)的存在,可能存在部分節點提交失敗的情況,即事務的ACID特性需要在各個不同的數據庫實例中保證。比如更新db1庫的A表時,必須同步更新db2庫的B表,兩個更新形成一個事務,要么都成功,要么都失敗。
那么我們如何利用MySQL實現分布式數據庫的事務呢?
Mysql 5.7為我們提供了分布式事務解決方案(https://dev.mysql.com/doc/refman/5.7/en/xa.html)
這里先聲明兩個概念:
- 資源管理器(resource manager):用來管理系統資源,是通向事務資源的途徑。數據庫就是一種資源管理器。資源管理還應該具有管理事務提交或回滾的能力。
- 事務管理器(transaction manager):事務管理器是分布式事務的核心管理者。事務管理器與每個資源管理器(resource
manager)進行通信,協調並完成事務的處理。事務的各個分支由唯一命名進行標識。
mysql在執行分布式事務(外部XA)的時候,mysql服務器相當於xa事務資源管理器,與mysql鏈接的客戶端相當於事務管理器。
分布式事務原理:分段式提交
分布式事務通常采用2PC協議,全稱Two Phase Commitment Protocol。該協議主要為了解決在分布式數據庫場景下,所有節點間數據一致性的問題。分布式事務通過2PC協議將提交分成兩個階段:
- prepare;
- commit/rollback
階段一為准備(prepare)階段。即所有的參與者准備執行事務並鎖住需要的資源。參與者ready時,向transaction manager報告已准備就緒。
階段二為提交階段(commit)。當transaction manager確認所有參與者都ready后,向所有參與者發送commit命令。
如下圖所示:
事務協調者transaction manager
因為XA 事務是基於兩階段提交協議的,所以需要有一個事務協調者(transaction manager)來保證所有的事務參與者都完成了准備工作(第一階段)。如果事務協調者(transaction manager)收到所有參與者都准備好的消息,就會通知所有的事務都可以提交了(第二階段)。MySQL 在這個XA事務中扮演的是參與者的角色,而不是事務協調者(transaction manager)。
Mysql的XA事務分為外部XA和內部XA
- 外部XA用於跨多MySQL實例的分布式事務,需要應用層作為協調者,通俗的說就是比如我們在PHP中寫代碼,那么PHP書寫的邏輯就是協調者。應用層負責決定提交還是回滾,崩潰時的懸掛事務。MySQL數據庫外部XA可以用在分布式數據庫代理層,實現對MySQL數據庫的分布式事務支持,例如開源的代理工具:網易的DDB,淘寶的TDDL等等。
- 內部XA事務用於同一實例下跨多引擎事務,由Binlog作為協調者,比如在一個存儲引擎提交時,需要將提交信息寫入二進制日志,這就是一個分布式內部XA事務,只不過二進制日志的參與者是MySQL本身。Binlog作為內部XA的協調者,在binlog中出現的內部xid,在crash recover時,由binlog負責提交。(這是因為,binlog不進行prepare,只進行commit,因此在binlog中出現的內部xid,一定能夠保證其在底層各存儲引擎中已經完成prepare)。
MySQL XA事務基本語法
XA {START|BEGIN} xid [JOIN|RESUME] 啟動xid事務 (xid 必須是一個唯一值; 不支持[JOIN|RESUME]子句)
XA END xid [SUSPEND [FOR MIGRATE]] 結束xid事務 ( 不支持[SUSPEND [FOR MIGRATE]] 子句)
XA PREPARE xid 准備、預提交xid事務
XA COMMIT xid [ONE PHASE] 提交xid事務
XA ROLLBACK xid 回滾xid事務
XA RECOVER 查看處於PREPARE 階段的所有事務
PHP調用MYSQL XA事務示例
1、首先要確保mysql開啟XA事務支持
SHOW VARIABLES LIKE '%xa%'
- 1
- 1
如果innodb_support_xa的值是ON就說明mysql已經開啟對XA事務的支持了。
如果不是就執行:
SET innodb_support_xa = ON
- 1
- 1
開啟
2、代碼如下:
<?PHP$dbtest1 = new mysqli("172.20.101.17","public","public","dbtest1")or die("dbtest1 連接失敗");$dbtest2 = new mysqli("172.20.101.18","public","public","dbtest2")or die("dbtest2 連接失敗");
//為XA事務指定一個id,xid 必須是一個唯一值。$xid = uniqid("");
//兩個庫指定同一個事務id,表明這兩個庫的操作處於同一事務中$dbtest1->query("XA START '$xid'");//准備事務1$dbtest2->query("XA START '$xid'");//准備事務2
try {
//$dbtest1
$return = $dbtest1->query("UPDATE member SET name='twm' WHERE id=1") ;
if($return == false) {
throw new Exception("庫dbtest1@172.20.101.17執行update member操作失敗!");
}
//$dbtest2
$return = $dbtest2->query("UPDATE memberpoints SET point=point+10 WHERE memberid=1") ;
if($return == false) {
throw new Exception("庫dbtest1@172.20.101.18執行update memberpoints操作失敗!");
}
//階段1:$dbtest1提交准備就緒
$dbtest1->query("XA END '$xid'");
$dbtest1->query("XA PREPARE '$xid'");
//階段1:$dbtest2提交准備就緒
$dbtest2->query("XA END '$xid'");
$dbtest2->query("XA PREPARE '$xid'");
//階段2:提交兩個庫
$dbtest1->query("XA COMMIT '$xid'");
$dbtest2->query("XA COMMIT '$xid'");
}
catch (Exception $e) {
//階段2:回滾
$dbtest1->query("XA ROLLBACK '$xid'");
$dbtest2->query("XA ROLLBACK '$xid'");
die($e->getMessage());
}
$dbtest1->close();$dbtest2->close();
?>
XA的性能問題
XA的性能很低。一個數據庫的事務和多個數據庫間的XA事務性能對比可發現,性能差10倍左右。因此要盡量避免XA事務,例如可以將數據寫入本地,用高性能的消息系統分發數據。或使用數據庫復制等技術。只有在這些都無法實現,且性能不是瓶頸時才應該使用XA。