騰訊雲tca架構師含金量及架構師具備的能力
MySQL事務的隔離級別
令人驚訝的是,大部分資料庫系統都沒有提供真正的隔離性,最初或許是因為系統實現者並沒有真正理解這些問題。如今這些問題已經弄清楚了,但是資料庫實現者在正確性和效能之間做了妥協。ISO和ANIS SQL 標準指定了四種事務隔離級別的標準,但是很少有資料庫廠商遵循這些標準。比如Oracle資料庫就不支援READ UNCOMMITED 和REPEATABLE READ的事務隔離級別。
一、概念
以下幾個概念是事務隔離級別要實際解決的問題,所以需要搞清楚都是什麼意思。
1、髒讀
髒讀指的是讀到了其他事務未提交的資料,未提交意味著這些資料可能會回滾,也就是可能最終不會存到資料庫中,也就是不存在的資料。讀到了並一定最終存在的資料,這就是髒讀。
2、可重複讀
可重複讀指的是在同一個事務內,最開始讀到的資料和事務結束前的任意時刻讀到的同一批資料都是一致的。通常針對資料更新(UPDATE)操作。
3、不可重複讀
對比可重複讀,不可重複讀指的是在同一事務內,不同的時刻讀到的同一批資料可能是不一樣的,可能會受到其他事務的影響,比如其他事務改了這批資料並提交了。通常針對資料更新(UPDATE)操作。
4、幻讀
幻讀是針對資料插入(INSERT)操作來說的。假設事務A對某些行的內容作了更改,但是還未提交,此時事務B插入了與事務A更改前的記錄相同的記錄行,並且在事務A提交之前先提交了,而這時,在事務A中查詢,會發現好像剛剛的更改對於某些資料未起作用,但其實是事務B剛插入進來的,讓使用者感覺很魔幻,感覺出現了幻覺,這就叫幻讀。
二、MySQL 中執行事務
事務的執行過程如下,以 begin 或者 start transaction 開始,然後執行一系列操作,最後要執行 commit 操作,事務才算結束。當然,如果進行回滾操作(rollback),事務也會結束。
需要注意的是,begin 命令並不代表事務的開始,事務開始於 begin 命令之後的第一條語句執行的時候。例如下面示例中,select * from xxx 才是事務的開始
begin;
select * from xxx;
commit; -- 或者 rollback;
三、事務隔離級別
- 讀未提交(READ UNCOMMITTED)
- 讀提交 (READ COMMITTED)
- 可重複讀 (REPEATABLE READ)
- 序列化 (SERIALIZABLE)
各隔離級別的特點如下:
下面來依次介紹各個隔離級別的特點
1、讀未提交(READ UNCOMMITTED)
1)優點
讀未提交隔離級別是不加鎖的,所以它的效能是最好的,沒有加鎖、解鎖帶來的效能開銷。
2)缺點
它允許髒讀,也就是可能讀取到其他會話中未提交事務修改的資料。任務事務對資料的修改都會第一時間暴露給其他事務,即使事務還沒有提交。
2、讀提交(READ COMMITED)
要解決的問題-髒讀
1)優點
既然讀未提交沒辦法解決髒資料的問題,那麼就有了讀提交。讀提交就是一個事務只能讀到其他事務已經提交過的資料,也就是其他事務呼叫commit命令之後的資料,那髒資料問題就解決了。
2)缺點
讀提交解決了髒讀的問題,但是無法做到可重複讀,也沒辦法解決幻讀。
3)舉例
同樣開啟事務A和事務B兩個事務,在事務A中使用 update 語句將 id=1 的記錄行 age 欄位改為 10。此時,在事務B中使用 select 語句進行查詢,我們發現在事務A提交之前,事務B中查詢到的記錄 age 一直是1,直到事務A提交,此時在事務B中 select 查詢,發現 age 的值已經是 10 了。
這就出現了一個問題,在同一事務中(本例中的事務B),事務的不同時刻同樣的查詢條件,查詢出來的記錄內容是不一樣的,事務A的提交影響了事務B的查詢結果,這就是不可重複讀,也就是讀提交隔離級別。
說明:讀提交事務隔離級別是大多數流行資料庫的預設事務隔離界別,比如 Oracle,但其並不是 MySQL 的預設隔離級別。
3、可重複讀(REPEATABLE READ)
要解決的問題:不可重複讀
可重複是對比不可重複而言的,上面說不可重複讀是指同一事務不同時刻讀到的資料值可能不一致。而可重複讀是指,事務不會讀到其他事務對已有資料的修改,即使其他事務已提交,也就是說,事務開始時讀到的已有資料是什麼,在事務提交前的任意時刻,這些資料的值都是一樣的。但是,對於其他事務新插入的資料是可以讀到的,這也就引發了幻讀問題。
說明:
1)InnoDB 儲存引擎預設支援的隔離級別是REPEATABLE READ,但是與標準SQL不同的是,InnoDB 儲存引擎在REPEATABLE READ事務隔離級別下,使用Next-Key Lock鎖的演算法,因此避免幻讀的產生。
2)REPEATABLE READ級別作為 mysql 事務預設隔離級別,是事務安全與效能的折中,可能也符合二八定律(20%的事務存在幻讀的可能,80%的事務沒有幻讀的風險),我們在正確認識幻讀後,便可以根據場景靈活的防止幻讀的發生。
4、序列化 (SERIALIZABLE)
序列化是4種事務隔離級別中隔離效果最好的,解決了髒讀、可重複讀、幻讀的問題,但是效果最差,它將事務的執行變為順序執行,與其他三個隔離級別相比,它就相當於單執行緒,後一個事務的執行必須等待前一個事務結束。
說明:SERIALIZABLE 級別則是悲觀的認為幻讀時刻都會發生,故會自動的隱式的對事務所需資源加排它鎖,其他事務訪問此資源會被阻塞等待,故事務是安全的,但需要認真考慮效能。
四、總結
1、讀未提交和序列化基本上是不需要考慮的隔離級別,前者不加鎖限制,後者相當於單執行緒執行,效率太差。
2、讀提交解決了髒讀問題,行鎖解決了併發更新的問題。並且 MySQL 在可重複讀級別解決了幻讀問題,是通過行鎖和間隙鎖的組合 Next-Key 鎖實現的。
3、隔離級別越低,事務請求的鎖越少或保持鎖的時間就越短。這也是為什麼大多數資料庫系統預設的事務隔離級別是READ COMMITED。
4、InnoDB 的行鎖鎖定的是索引,而不是記錄本身,這一點也需要有清晰的認識,故某索引相同的記錄都會被加鎖,會造成索引競爭,這就需要我們嚴格設計業務 sql,儘可能的使用主鍵或唯一索引對記錄加鎖。索引對映的記錄如果存在,加行鎖,如果不存在,則會加 next-key lock / gap 鎖 / 間隙鎖,故 InnoDB 可以實現事務對某記錄的預先佔用,如果記錄存在,它就是本事務的,如果記錄不存在,那它也將是本是無的,只要本是無還在,其他事務就別想佔有它。
參考連結:
https://segmentfault.com/a/1190000016566788
https://zhuanlan.zhihu.com/p/117476959