1. 程式人生 > >《MySQL實戰45講》學習筆記——事務隔離

《MySQL實戰45講》學習筆記——事務隔離

SQL標準事務隔離的級別包括:

  • 讀未提交,read uncommitted,一個事務還沒有提交,它做的變更就能被別的事務看到。
  • 讀提交,read commited,一個事務提交之後,它做的變更才會被其他事務看到。
  • 可重複讀,repeatable read,一個事務執行的過程中看到的資料,總是跟這個事務在啟動的時候看到的資料是一致的。
  • 序列化,對於同一行記錄,加讀寫鎖。如果事務讀寫衝突,必須序列。

”讀未提交“直接使用當前資料執行事務;”序列化“通過加鎖的方式使用當前資料執行事務,兩者不同的是後者因為加鎖,所以在事務執行期間不可能產生資料衝突。

對於”可重複讀“隔離級別,會在事務啟動的時候建立一個檢視,事務使用檢視資料;對於”讀提交“隔離級別,這個檢視是在每個SQL語句開始執行的時候建立的。

MVCC機制

每一行資料都有一個版本號,這個版本號就是寫入這行資料的事務ID,事務是唯一的,自增的。MVCC保留了未提交資料的歷史版本。這就是undolog。

可重複讀建立的檢視是虛擬的,就是事務的ID,當事務讀取一行資料的時候,如果當前的資料版本號高於事務ID,就向上追溯,直到事務ID那個版本。(忽略事務開始前未提交事務的版本,這個和寫入有關,後邊還會解釋)

長事務可能會造成較大的回滾日誌。

在MySQL 5.5以及以前的版本,回滾日誌跟資料字典一起放在ibdata檔案裡,即使長事務最終提交,回滾段被清理,檔案也不會變小

如何避免長事務對業務影響?(原文摘抄)

從應用開發端來看:

  • 確認是否開啟了autocommit,這個第一步通過SQL語句show variables like 'autocommit'檢視資料庫是否啟用,但是這樣做是不夠的,因為有些開發框架也會設定這個值,所以最終還是要看MySQL的general_log來確認。最終確保開啟了autocommit。
  • 確認是否有不必要的只讀事務,主要是有些框架會做,把這些只讀的事務去掉
  • 業務連線資料庫的時候,根據業務本身預估,通過SET MAX_EXECUTION_TIME命令,來控制每個語句執行的最長時間,避免單個語句意外執行太長時間。

從資料庫角度來看:

  • 監控 information_schema.Innodb_trx表,設定長事務閾值,超過了就報警或者Kill
  • Percona的pt-kill這個工具不錯,推薦使用
  • 在業務功能測試階段輸出所有的general _log,分析日誌提前發現問題
  • 如果使用的是MySQL5.6或者更新的版本,把innodb_undo_tablespaces設定成2(或者更大值)如果真的出現大事務導致回滾段過大,這樣設定後清理起來方便。

事務的寫入

寫不一樣,寫如果不在當前版本寫,就沒有意義。因為後來的查詢只查詢當前版本,並且undolog很快會被刪掉。所以寫入操作是在當前版本寫。

寫入後的查詢呢?版本號一定是跟著寫操作改變的,事務在當前版本寫入資料後,當前版本就成為了歷史版本,而改後當前的版本就是當前事務的ID,所以當前事務查詢不需要回溯,取到的就是修改後的值。通過這一步可以得出一個重要結論,資料行的歷史版本號不一定是按照從小到大的順序排列的。

兩個事物同時寫,靠行鎖保證資料一致性的。行鎖為什麼要等到事務提交才釋放?因為如果提前釋放,可能就會被別的事務修改,當前行的版本號又會改變,如果需要再次修改,當前讀的資料就不是自己設定的資料,這個事務的邏輯就不可控了。事務提交才釋放,能保證整個事務內部邏輯一致性。

上邊提到的忽略事務開始前未提交事務ID的問題。
如果資料行的歷史版本號不是按照從小到大排列的,那麼一個事務在查詢的時候是如何知道使用哪個版本的資料呢?如果這個事務在查詢之前修改過,那麼使用的就是自己ID的版本的資料;如果沒有修改過,就比較複雜了,因為即使資料的版本號比自己的事務ID小,也可能是當前事務啟動後提交的。解決的辦法是在事務啟動的時候記錄所有未提交的事務ID,在查詢搜尋版本的時候忽略他們。

TIPS:
事務中使用當前讀:
mysql> select k from t where id=1 lock in share mode;
mysql> select k from t where id=1 for update;

立即開始一個事務:
begin /start transaction並不是一個事務的起點,在執行第一個操作的時候事務才真正啟動,可以用start transaction with consistent snapshot