MySQL -- 事務隔離
- 事務特性: ACID (Atomicity、Consistency、Isolation、Durability)
- 如果多個 事務併發執行 時,就可能會出現 髒讀 、 不可重複讀 、 幻讀 (phantom read)等問題
- 解決方案: 隔離級別
- 隔離級別越高,效率就會越低
- SQL標準的事務隔離級別
- READ-UNCOMMITTED
- 一個事務還未提交時,它所做的變更能被別的事務看到
- READ-COMMITTED
- 一個事務提交之後,它所做的變更才會被其他事務看到
- REPEATABLE-READ
- 一個事務在執行過程中所看到的資料,總是跟這個事務在啟動時看到的資料是一致的
- 同樣,在RR隔離級別下,未提交的變更對其他事務也是不可見的
- SERIALIZABLE
- 對同一行記錄,寫會加寫鎖,讀會加讀鎖,鎖級別是 行鎖
- 當出現讀寫鎖衝突時,後訪問的事務必須等前一個事務執行完成,才能繼續執行
- READ-UNCOMMITTED
- 預設隔離級別
- Oracle:READ-COMMITTED
- MySQL:REPEATABLE-READ
mysql> SHOW VARIABLES LIKE '%isolation%'; +---------------+-----------------+ | Variable_name | Value| +---------------+-----------------+ | tx_isolation| REPEATABLE-READ | +---------------+-----------------+
樣例
mysql> create table T(c int) engine=InnoDB; insert into T(c) values(1);
隔離級別 | V1 | V2 | V3 | 備註 |
---|---|---|---|---|
READ-UNCOMMITTED | 2 | 2 | 2 | |
READ-COMMITTED | 1 | 2 | 2 | |
REPEATABLE-READ | 1 | 1 | 2 | |
SERIALIZABLE | 1 | 1 | 2 | 事務B在執行『1->2』時被鎖住,等事務A提交後才能繼續執行 |
實現
- 在實現上,資料庫裡面會建立一個 檢視 (read-view),訪問的時候會以檢視的邏輯結果為準
- REPEATABLE-READ的檢視是在 事務啟動時 建立的,整個事務存在期間都用這個檢視
- 事務啟動:begin後的第一個 DML 語句, begin語句本身不會開啟事務
- READ-COMMITTED的檢視在 每個SQL語句開始執行時 建立的
- READ-UNCOMMITTED 沒有檢視概念 ,直接返回 記錄上的最新值 ( 記憶體 ,InnoDB Buffer Pool)
- SERIALIZABLE則直接用 加鎖 (行鎖)的方式來避免並行訪問
RR隔離的實現
實際上,每條記錄在 更新 的時候都會同時( 在redolog和binlog提交之前 )記錄一條 回滾操作
記錄上的最新值,通過回滾操作,都可以得到前一個狀態的值
多版本
變更記錄:1->2->3->4

- 當前值為4,但在查詢這條記錄的時候, 不同時刻啟動的事務會有不同的檢視
- 在檢視A、B和C,這一個記錄的值分別是1、2和4
- 同一條記錄在系統中可以存在多個版本,這就是 MVCC ( 多版本併發控制 )
- 對於檢視A,要得到1,必須 將當前值依次執行圖中的所有回滾操作
- 這會存在一定的 效能開銷
- 這裡的檢視是 邏輯檢視 , 並不是快照
- 這裡的檢視是InnoDB( 儲存引擎層 )的read-view,也不是Server層都VIEW(虛表)
- 即使此時有另外一個事務正在將4改成5,這個事務跟檢視A、B和C所對應的事務並不衝突
刪除回滾段
- 當沒有事務需要用到這些回滾段時 ,回滾段就會被刪除
- 不被事務所需要的回滾段: 比系統中最早檢視還要早的回滾段
長事務
- 長事務意味著系統裡面存在 很老的事務檢視
- 長事務隨時可能訪問資料庫裡面的任何資料,在這個事務提交之前,它 可能用到的回滾段都必須保留
- 因此這會導致 佔用大量的儲存空間
- <= MySQL5.5,回滾段跟資料字典一起放在 ibdata 檔案裡,即使長事務最終提交,回滾段被清理, 檔案也不會變小
- RC隔離級別一般不會導致回滾段過長的問題
# 查詢持續時間超過60s的事務 mysql> select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60;
事務的啟動方式
- 啟動方式
- 顯式啟動事務, begin(start transaction) + commit/rollback
- set autocommit=0 + commit/rollback
- set autocommit=0:關閉自動提交
- 一些客戶端框架會在預設連線成功後執行set autocommit=0,導致 接下來的查詢都在事務中
- 如果是 長連線 ,就會導致 意外的長事務
- 推薦方式
- set autocommit=1 + begin(start transaction) + commit/rollback
- set autocommit=1 + begin(start transaction) + (commit and chain)/(rollback and chain)
- 適用於頻繁使用事務的業務
- 省去再次執行begin語句的開銷
- 從程式開發的角度能夠明確地知道每個語句是否處於事務中
避免長事務的方案
應用開發端
- 確保 set autocommit=1 ,可以通過 general_log 來確認
- 確認程式中是否有 不必要的只讀事務
- 業務連線資料庫的時候,預估 每個語句執行的最長時間 ( max_execution_time )
mysql> SHOW VARIABLES LIKE '%general_log%'; +------------------+-----------------------------------------------+ | Variable_name| Value| +------------------+-----------------------------------------------+ | general_log| OFF| | general_log_file | /data_db3/mysql/3323/data/ym_DB_12_100071.log | +------------------+-----------------------------------------------+
# Introduced 5.7.8 # 0 -> disable mysql> SHOW VARIABLES LIKE '%max_execution_time%'; +--------------------+-------+ | Variable_name| Value | +--------------------+-------+ | max_execution_time | 0| +--------------------+-------+
資料庫端
- 監控 information_schema.innodb_trx ,設定長事務閾值,告警或者Kill(工具:pt-kill)
- 在業務功能的測試階段要求輸出所有的general_log,分析日誌行為並提前發現問題
參考資料
《MySQL實戰45講》
轉載請註明出處:http://zhongmingmao.me/2019/01/16/mysql-transaction-isolation/
訪問原文「MySQL -- 事務隔離」獲取最佳閱讀體驗並參與討論