MySQL · 特性分析 · 淺談 MySQL 5.7 XA 事務改進
關於MySQL XA 事務
MySQL XA 事務通常用於分布式事務處理當中。比如在分庫分表的場景下,當遇到一個用戶事務跨了多個分區,需要使用XA事務 來完成整個事務的正確的提交和回滾,即保證全局事務的一致性。
XA 事務在分庫分表場景的使用
下圖是個典型的分庫分表場景,前端是一個Proxy後麵帶若幹個MySQL實例,每個實例是一個分區。
假設一個表test定義如下,Proxy根據主鍵”a”算Hash決定一條記錄應該分布在哪個節點上:
create table test(a int primay key, b int) engine = innodb;
應用發到Proxy的一個事務如下:
begin;
insert into test values (1, 1);
update test set b = 1 where a = 10;
commit;
Proxy收到這個事務需要將它轉成XA事務發送到後端的數據庫以保證這個事務能夠安全的提交或回滾,一般的Proxy的處理步驟 如下:
- Proxy先收到begin,它隻需要設置一下自己的狀態不需要向後端數據庫發送
- 當收到 insert 語句時Proxy會解析語句,根據“a”的值計算出該條記錄應該位於哪個節點上,這裏假設是“分庫1”
- Proxy就會向分庫1上發送語句xa start ‘xid1’,開啟一個XA事務,這裏xid1是Proxy自動生成的一個全局事務ID;同時原來 的insert語句insert into values(1,1)也會一並發送到分庫1上。
- 這時Proxy遇到了update語句,Proxy會解析 where條件主鍵的值來決定該條語句會被發送到哪個節點上,這裏假設是“分庫2”
- Proxy就會向分庫2上發送語句xa start ‘xid1’,開啟一個XA事務,這裏xid1是Proxy之前已經生成的一個全局事務ID;同時原來 的update語句update test set b = 1 where a = 10也會一並發送到分庫2上。
- 最後當Proxy解析到commit語句時,就知道一個用戶事務已經結束了,就開啟提交流程
- Proxy會向分庫1和分庫2發送 xa end ‘xid1’;xa prepare ‘xid1’語句,當收到執行都成功回複後,則繼續進行到下一步,如果任何一個分 庫返回失敗,則向分庫1和分庫2 發送 xa rollback ‘xid1’,回滾整個事務
- 當 xa prepare ‘xid1’都返回成功,那麼 proxy會向分庫1和分庫2上發送 xa commit ‘xid1’,來最終提交事務。
這裏有一個可能的優化,即在步驟4時如果Proxy計算出update語句發送的節點仍然是“分庫1”時,在遇到commit時,由於隻涉 及到一個分庫,它可以直接向“分庫1”發送 xa end ‘xid1’; xa commit ‘xid1’ one phase來直接提交該事務,避免走 prepare階段來提高效率。
XA對事務安全的影響分析
從以上分庫分表場景下分布式事務的處理過程來看,整個分布式事務的安全性依賴是XA Prepare了的事務的可靠性,也就是在 數據庫節點上 XA Prepare了的事務必須是持久化了的,這樣當XA Commit發來時才可以提交。設想如下場景:
- Proxy已經向分庫1和分庫2上發送完了 xa prepare ‘xid1’語句,並得到了成功的回複
- Proxy向分庫1上發送了 ‘xa commit ‘xid1’語句,並已經成功返回
- 當 Proxy向分庫2上發送 ‘xa commit ‘xid1’時,網絡斷開了,或者分庫2的數據庫實例被kill了
- 當網絡恢複(這時相關的Session已經退出了)或數據庫實例再啟動後(或切換到備庫),XA prepare了的事務已經回滾了, 當Proxy XA commit ‘xid1’發過來後數據庫實例根本找不到xid1這個xa事務
上麵的過程就導致了分布式事務的不一致:分庫1提交了事務,分庫2回滾了事務,整個事務提交了一半,回滾了一半。
在MySQL 5.6中以上過程是可能發生的,因為xa prepare並沒有嚴格的持久化,當Session斷開,數據庫崩潰等情況下這些事務 會被回滾掉,而且的當一個主庫配置了SemiSync的備庫時xa prepare了的事務也不會被發送的備庫,如果主庫切換到備庫這些 事務也會丟失。
MySQL 5.7 XA可靠性改進
MySQL 5.7解決了 xa prepare了的事務的嚴格持久化問題,也就是在session斷開和實例崩潰重啟情況下這些事務不丟,同時在 xa prepare ‘xid1’返回之前XA事務也會同步到備庫。下麵將通過在5.6和5.7上分別執行xa prepare並對binlog event進行分析 來演示這個改進。
斷開連接對xa prepare的事務影響
在5.6和5.7上分別執行如下sql然後斷開連接,再重新連接使用的xa recover驗證 XA 事務是否回滾了。
xa start 'xid1';
insert into test values(1, 1);
xa end 'xid1';
xa prepare 'xid1';
-- 這裏斷開再連上新連接執行 xa recover
在 5.6 的版本上將返回空的結果,在 5.7 的版本上返回:
mysql> xa recover;
+----------+--------------+--------------+------+
| formatID | gtrid_length | bqual_length | data |
+----------+--------------+--------------+------+
| 1 | 4 | 0 | xid1 |
+----------+--------------+--------------+------+
1 row in set (0.00 sec)
說明斷開連接後 5.7的prepare了的xa事務沒有丟失。
XA 事務的 Binlog events 異同
在5.6和5.7上分別執行如下事務,然後用 show binlog events 查看兩者binlog的不同:
xa start 'xid1';
insert into test values(1, 1);
xa end 'xid1';
xa prepare 'xid1';
xa commit 'xid1';
5.6的結果:
mysql-bin.000001 | 304 | Gtid | 3706 | 352 | SET @@SESSION.GTID_NEXT= 'uuid:2'
mysql-bin.000001 | 352 | Query | 3706 | 424 | BEGIN
mysql-bin.000001 | 424 | Table_map | 3706 | 472 | table_id: 71 (test.test)
mysql-bin.000001 | 472 | Write_rows | 3706 | 516 | table_id: 71 flags: STMT_END_F
mysql-bin.000001 | 516 | Query | 3706 | 589 | COMMIT
5.7的結果:
mysql-bin.000001 | 544 | Gtid | 3707 | 592 | SET @@SESSION.GTID_NEXT= 'uuid:3'
mysql-bin.000001 | 592 | Query | 3707 | 685 | XA START X'78696431',X'',1
mysql-bin.000001 | 685 | Table_map | 3707 | 730 | table_id: 74 (test.t)
mysql-bin.000001 | 730 | Write_rows | 3707 | 774 | table_id: 74 flags: STMT_END_F
mysql-bin.000001 | 774 | Query | 3707 | 865 | XA END X'78696431',X'',1
mysql-bin.000001 | 865 | XA_prepare | 3707 | 905 | XA PREPARE X'78696431',X'',1
mysql-bin.000001 | 905 | Gtid | 3707 | 953 | SET @@SESSION.GTID_NEXT= 'uuid:4' |
mysql-bin.000001 | 953 | Query | 3707 | 1047 | XA COMMIT X'78696431',X'',1
可以看到 MySQL 5.6 XA 事務和普通事務的binlog是一樣的,並沒有體現 xa prepare。而到了 MySQL 5.7 XA 事務的binlog和 普通的事務是完全不同的,XA Prepare有單獨的Log event類型,有自己的Gtid,當開啟semi-sync的情況下,MySQL 5.7 執行 XA prepare 時會等備庫回複後才返回結果給客戶端,這樣XA prepare執行完就是安全的。
通過以上分析可以看出 MySQL 5.7在XA事務安全性方麵做了很大的改進,後續月報文章將會對它的實現做分析。
最後更新:2017-09-21 09:03:39