1. 程式人生 > >詳解Mysql分散式事務XA(跨資料庫事務)查詢快取

詳解Mysql分散式事務XA(跨資料庫事務)查詢快取

在開發中,為了降低單點壓力,通常會根據業務情況進行分表分庫,將表分佈在不同的庫中(庫可能分佈在不同的機器上)。在這種場景下,事務的提交會變得相對複雜,因為多個節點(庫)的存在,可能存在部分節點提交失敗的情況,即事務的ACID特性需要在各個不同的資料庫例項中保證。比如更新db1庫的A表時,必須同步更新db2庫的B表,兩個更新形成一個事務,要麼都成功,要麼都失敗。 

那麼我們如何利用mysql實現分散式資料庫的事務呢?
  • 資源管理器(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

如果innodb_support_xa的值是ON就說明mysql已經開啟對XA事務的支援了。 
如果不是就執行:

SET innodb_support_xa = ON
  • 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='唐大麥' WHERE id=1") ;
    if($return == false) {
       throw new Exception("庫[email protected]執行update member操作失敗!");
    }

    //$dbtest2
    $return = $dbtest2->query("UPDATE memberpoints SET point=point+10 WHERE memberid=1") ;
    if($return == false) {
       throw new Exception("庫[email protected]執行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。

查詢快取

MySQL查詢快取儲存查詢返回的完整結果。當查詢命中快取,MySQL會立刻返回結果,跳過了解析,優化和執行階段。

查詢快取執行過程:

  1.通過一個大小寫不敏感的檢檢視看SQL語句是不是以SEL開頭。

  2.若是以SEL開頭則獲取快取資料,若是命中則直接返回結果。

  3.若沒有命中,則通過SQL語句查詢資料。

  4.返回查詢結果給客戶端。同時存入查詢快取,但不是所有的查詢結果都會存入查詢快取,詳細見下面。

快取未命中可能的情況:

  1.由於查詢語句中包含不確定的函式,或者查詢結果太大,超過query_cache_limit的值,查詢結果無法快取。

  2.MySQL從未處理過這個查詢,查詢結果沒有快取過。

  3.之前快取了查詢結果,但是由於查詢快取記憶體不足,MySQL將某些快取逐出,導致未命中。

  4.快取失效操作太多。資料修改,記憶體不足,快取碎片都會導致快取失效。

  5.查詢快取還沒有完成預熱,MySQL還沒有機會將查詢結果都快取起來。

不會快取結果的情況:

  1.當查詢語句中有一些不確定的資料時,則不會被快取。如包含函式NOW(),CURRENT_DATE()等類似的函式,或者使用者自定義的函式,儲存函式,使用者變數等都不會被快取。

  2.當查詢的結果大於query_cache_limit設定的值時,結果不會被快取。

  3.對於InnoDB引擎來說,當一個語句在事務中修改了某個表,那麼在這個事務提交之前,所有與這個表相關的查詢都無法被快取。因此長時間執行事務,會大大降低快取命中率。

4. 查詢快取系統會跟蹤查詢中涉及到的每個表,如果這些表發生變換,那麼和這個表相關的所有的快取資料都將失效。(這種機制看起來效率較低,因為資料變化時很有可能對應的查詢結果並沒有變更,但是這種簡單實現代價很小,而這點對於一個非常繁忙的系統來說非常重要)

查詢快取帶來的額外消耗:

  1.在查詢之前必須先檢查是否命中快取。

  2.如果這個查詢可以被快取,那麼執行完成後,MySQL發現查詢快取中沒有這個查詢,則會將結果存入查詢快取,這會帶來額外的系統消耗。

  3.寫入或更新資料時,MySQL必須將對應表的所有快取都設定失效。如果查詢快取很大或者碎片很多時,這個操作可能帶來很大的系統消耗。

查詢快取記憶體使用:

  在查詢開始返回結果的時候就分配空間,而此時無法預知查詢結果有多大,所以MySQL無法為每一個查詢結果精確的分配快取空間。

  當需要將查詢結果快取的時候,MySQL先申請一個數據塊儲存結果,不論結果大小,都會至少申請一個query_cache_min_res_unit的空間,然後將結果寫入資料塊,若申請的資料塊不足以儲存結果,那麼再申請一個數據塊,直到全部儲存完成。

  當查詢完成後,如果申請的記憶體空間還有剩餘,MySQL會將其釋放,並放入空閒記憶體部分,這樣是不會產生碎片。

  通過下面的步驟來驗證這個問題:

  設定的分配記憶體塊的最小單位為4kb。

  首先執行 show status like 'qcache%'; 檢視快取使用情況。

  此時可以看到Qcache_free_blocks的值為1,說明有一個空閒塊。

  現在執行一條查詢語句 select * from people where id =10001,資料遠遠小於4kb,且之前沒有被快取。

然後再執行 show status like 'qcache%'; 檢視快取使用情況。

  而此時還是隻有一個空閒塊,說明在沒有併發查詢的情況下,MySQL會將儲存查詢結果後產生的剩餘空間釋放,不會產生碎片。

碎片如何產生:

  假設查詢的結果非常小,伺服器在併發的向兩個連結返回結果,這是在向A和B兩個記憶體塊寫入資料,返回完結果後回收剩餘的空間時,A剩餘的空間小於query_cache_min_res_unit設定的值,這樣就不能再次被查詢快取使用,從而產生了碎片,B剩餘的空間則會釋放,併入空閒記憶體部分。

除此之外,如果在快取失效時,可能導致留下太小的資料塊無法在後續快取中使用(就是小於query_cache_min_res_unit了

具體的關於查詢快取和快取記憶體使用和分配資訊請閱讀高效能mysql第309-317頁

如何減少碎片:

1)選擇合適的query_cache_min_res_unit,減少有碎片導致的記憶體空間浪費。設定合適的值可以平衡每個資料塊的大小和每次儲存結果時記憶體塊申請的次數。值太小浪費的空間少,但是記憶體申請會比較頻繁;值太大,那麼碎片會太多,浪費內訓資源

2)使用flush query cache 完成碎片整理。這個命令會將查詢快取重新排序,並將所有的空閒空間都聚集到查詢快取的一塊區域上。該命令不會將查詢快取清空,該命令會訪問所有的查詢快取,在這期間任何其他的連結都無法訪問查詢快取,從而會導致伺服器僵死一段時間,使用這個命令需要異常小心這一點。建議保持查詢空間小一點,以便維護時可以將伺服器僵死控制在非常短的時間內