MYSQL實戰四十五講總結筆記_03、事務隔離

前言:整理歸納,個人溫習之用,請支持正版極客時間

1、何謂事務?

*事務就是要保證一組數據庫操作,要麼全部成功,要麼全部失敗。在 MySQL 中,事務支持是在引擎層實現的。MySQL 是一個支持多引擎的系統,但並不是所有的引擎都支持事務。比如 MySQL 原生的 MyISAM 引擎就不支持事務,這也是 MyISAM 被 InnoDB 取代的重要原因之一
 
*ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔離性、持久性)
原子性(Atomicity) 事務是一個不可分割的工作單位,事務中的操作要麼都發生,要麼都不發生。
一致性(Consistency) 事務前後數據的完整性必須保持一致。 
隔離性(Isolation) 多個用戶併發訪問數據庫時,數據庫爲每一個用戶開啓的事務,不能被其他事務的操作數據所幹擾,多個併發事務之間要相互隔離。 
持久性(Durability) 一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來即使數據庫發生故障也不應該對其有任何影響。
 

2、隔離性與隔離級別

*當數據庫上有多個事務同時執行的時候,就可能出現髒讀(dirty read)、不可重複讀(non-repeatable read)、幻讀(phantom read)的問題
  • 髒讀:當數據庫中一個事務A正在修改一個數據但是還未提交或者回滾,另一個事務B 來讀取了修改後的內容並且使用了,之後事務A提交了。此情況僅會發生在: 讀未提交的隔離級別
  • 不可重複讀(虛讀):在一個事務A中多次操作數據,在事務A操作過程中(未最終提交),事務B也做了處理,並且該值發生了改變,這時候就會導致A在事務操作的時候,發現數據與第一次不一樣了。此情況僅會發生在:讀未提交、讀提交的隔離級別
  • 幻讀:第一個事務對一個表中的數據進行了修改,這種修改涉及到表中的全部數據行。同時,第二個事務也修改這個表中的數據,這種修改是向表中插入一行新數據。那麼,以後就會發生操作第一個事務的用戶發現表中還有沒有修改的數據行,就好象發生了幻覺一樣。此情況會回發生在:讀未提交、讀提交、可重複讀的隔離級別解決幻讀的方法是增加範圍鎖RangeS,鎖定檢索範圍爲只讀。
 
*SQL 標準的事務隔離級別包括:讀未提交(read uncommitted)、讀提交(read committed)、可重複讀(repeatable read)和串行化(serializable )。
  • 讀未提交:一個事務還沒提交時,它做的變更就能被別的事務看到
  • 讀提交:一個事務提交之後,它做的變更纔會被其他事務看到
  • 可重複讀:一個事務執行過程中看到的數據,總是跟這個事務在啓動時看到的數據是一致的。當然在可重複讀隔離級別下,未提交變更對其他事務也是不可見的
  • 串行化:顧名思義是對於同一行記錄,“寫”會加“寫鎖”,“讀”會加“讀鎖”。當出現讀寫鎖衝突的時候,後訪問的事務必須等前一個事務執行完成,才能繼續執行。
Ps:隔離級別越高,數據越安全,但性能越低
 
*例子
mysql> create table T(c int) engine=InnoDB;
insert into T(c) values(1);

 

  • “讀未提交”: V1 的值就是 2。這時候事務 B 雖然還沒有提交,但是結果已經被 A 看到了,V2、V3 也都是 2
  • “讀提交”: V1 是 1,V2 的值是 2。事務 B 的更新在提交後才能被 A 看到, V3 的值也是 2
  • “可重複讀”: V1、V2 是 1,V3 是 2。之所以 V2 還是 1,遵循的就是這個要求:事務在執行期間看到的數據前後必須是一致的
  • “串行化”:在事務 B 執行“將 1 改成 2”的時候,會被鎖住。直到事務 A 提交後,事務 B 纔可以繼續執行。所以從 A 的角度看, V1、V2 值是 1,V3 的值是 2。
 
*如何實現?(此處視圖是InnoDB的一致性視圖,而不是虛擬表
  • “讀提交”隔離級別下,這個視圖是在每個 SQL 語句開始執行的時候創建的。
  • “讀未提交”隔離級別下直接返回記錄上的最新值,沒有視圖概念
  • “可重複讀”隔離級別下,這個視圖是在事務啓動時創建的,整個事務存在期間都用這個視圖(靜態,不受其它事物更新的影響)
  • “串行化”隔離級別下直接用加鎖的方式來避免並行訪問
 
*通過設置啓動參數 transaction-isolation配置隔離級別,用 show variables 來查看當前的值
mysql> show variables like 'transaction_isolation';
+-----------------------+----------------+
| Variable_name | Value |
+-----------------------+----------------+
| transaction_isolation | READ-COMMITTED |
+-----------------------+----------------+
 

3、事務隔離的實現

*在 MySQL 中,實際上每條記錄在更新的時候都會同時記錄一條回滾操作。記錄上的最新值,通過回滾操作,都可以得到前一個狀態的值。
假設一個值從 1 被按順序改成了 2、3、4,在回滾日誌裏面就會有類似下面的記錄。
當前值是 4,但是在查詢這條記錄的時候,不同時刻啓動的事務會有不同的 read-view。
如圖中看到的,在視圖 A、B、C 裏面,這一個記錄的值分別是 1、2、4,同一條記錄在系統中可以存在多個版本,就是數據庫的多版本併發控制(MVCC)。
對於 read-view A,要得到 1,就必須將當前值依次執行圖中所有的回滾操作得到。即使現在有另外一個事務正在將 4 改成 5,這個事務跟 read-view A、B、C 對應的事務是不會衝突的。
回滾日誌不可能一直保留,當沒有事務再需要用到這些回滾日誌時,回滾日誌會被刪除。也就是當系統裏沒有比這個回滾日誌更早的 read-view 的時候(不是很理解啊)
來自評論區作者回復:一個查詢事務開啓以後,在這個時刻之後,事務提交/回滾之前,所有更新產生的undo log都不能被刪除
 
*爲何不建議使用長事務?
長事務意味着系統裏面會存在很老的事務視圖。由於這些事務隨時可能訪問數據庫裏面的任何數據,所以這個事務提交之前,數據庫裏面它可能用到的回滾記錄都必須保留,這就會導致大量佔用存儲空間。在 MySQL 5.5 及以前的版本,回滾日誌是跟數據字典一起放在 ibdata 文件裏的,即使長事務最終提交,回滾段被清理,文件也不會變小。
除了對回滾段的影響,長事務還佔用鎖資源,也可能拖垮整個庫。
 

4、事務的啓動方式

  • 顯式啓動事務語句, begin 或 start transaction。配套的提交語句是 commit,回滾語句是 rollback
  • set autocommit=0,這個命令會將這個線程的自動提交關掉。意味着如果你只執行一個 select 語句,這個事務就啓動了,而且並不會自動提交。這個事務持續存在直到你主動執行 commit 或 rollback 語句,或者斷開連接。
 
*有些客戶端連接框架會默認連接成功後先執行一個 set autocommit=0 的命令。這就導致接下來的查詢都在事務中,如果是長連接,就導致了意外的長事務。因此建議使用 set autocommit=1, 通過顯式語句的方式來啓動事務。
 
*建議使用第一種,如果考慮多一次交互問題(對於一個需要頻繁使用事務的業務,第二種方式每個事務在開始時都不需要主動執行一次 “begin”,減少了語句的交互次數),可以使用commit work and chain語法。
 
*在 autocommit 爲 1 的情況下,用 begin 顯式啓動的事務,如果執行 commit 則提交事務。如果執行 commit work and chain,則是提交事務並自動啓動下一個事務,這樣也省去了再次執行 begin 語句的開銷。
 
*可以在 information_schema 庫的 innodb_trx 這個表中查詢長事務,比如下面這個語句,用於查找持續時間超過 60s 的事務。
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60
 

5、總結

*系統裏面應該避免長事務,如果你是業務開發負責人同時也是數據庫負責人,你會有什麼方案來避免出現或者處理這種情況呢?(答案來自下一節作者回復)
  • 首先,從應用開發端來看:確認是否使用了 set autocommit=0。這個確認工作可以在測試環境中開展,把 MySQL 的 general_log 開起來,然後隨便跑一個業務邏輯,通過 general_log 的日誌來確認。一般框架如果會設置這個值,也就會提供參數來控制行爲,你的目標就是把它改成 1。確認是否有不必要的只讀事務。有些框架會習慣不管什麼語句先用 begin/commit 框起來。我見過有些業務並沒有這個需要,但是也把好幾個 select 語句放到了事務中。這種只讀事務可以去掉。業務連接數據庫的時候,根據業務本身的預估,通過 SET MAX_EXECUTION_TIME 命令,來控制每個語句執行的最長時間,避免單個語句意外執行太長時間。(爲什麼會意外?在後續的文章中會提到這類案例)
  • 其次,從數據庫端來看:監控 information_schema.Innodb_trx 表,設置長事務閾值,超過就報警 / 或者 kill;Percona 的 pt-kill 這個工具不錯,推薦使用;在業務功能測試階段要求輸出所有的 general_log,分析日誌行爲提前發現問題;如果使用的是 MySQL 5.6 或者更新版本,把 innodb_undo_tablespaces 設置成 2(或更大的值)。如果真的出現大事務導致回滾段過大,這樣設置後清理起來更方便。
 
*MySQL中undo的內容會被記錄到redo中。比如一個事務在執行到一半的時候實例崩潰了,在恢復的時候先恢復redo,再根據redo構造undo回滾宕機前沒有提交的事務。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章