我之前寫過一篇文章《為什麼MySQL選擇REPEATABLE READ作為預設隔離級別?》介紹過MySQL 的預設隔離級別是 Repeatable Reads以及背後的原因。
主要是因為MySQL在主從複製的過程是通過bin log 進行資料同步的,而MySQL早期只有statement這種bin log格式,這種格式下,bin log記錄的是SQL語句的原文。
當出現事務亂序的時候,就會導致備庫在 SQL 回放之後,結果和主庫內容不一致。
為了解決這個問題,MySQL採用了Repetable Read這種隔離級別,因為在 RR 中,會在更新資料的時候增加記錄鎖的同時增加間隙鎖。可以避免這種情況的發生。
關於MySQL的加鎖方式及加鎖原則,可以參考我寫的另外一篇《求你了,別再說資料庫鎖的只是索引了!》,這裡就不再贅述了。
在我知道MySQL 預設隔離級別是RR後,很長一段時間都以為應該不會有人去修改這個預設配置。
但是直到有一天,我們線上發生了一次死鎖的問題,我在排查的過程中,才發現我們的資料庫用的隔離級別沒有使用預設的 RR,而是修改成了Read Committed 。(關於那次死鎖排查過程,可以參考:一次資料庫的死鎖問題排查過程)
大家可以通過這個命令檢視資料庫當前的隔離級別:
select @@tx_isolation;
那麼,這裡不禁就有疑問了,為啥阿里要把這個資料庫隔離級別修改成 RC 呢,背後有什麼思考嗎?
RR 和 RC 的區別
想要搞清楚這個問題,我們需要先弄清楚 RR 和 RC 的區別,分析下各自的優缺點。
一致性讀
一致性讀,又稱為快照讀。快照即當前行資料之前的歷史版本。快照讀就是使用快照資訊顯示基於某個時間點的查詢結果,而不考慮與此同時執行的其他事務所執行的更改。
在MySQL 中,只有READ COMMITTED 和 REPEATABLE READ這兩種事務隔離級別才會使用一致性讀。
在 RC 中,每次讀取都會重新生成一個快照,總是讀取行的最新版本。
在 RR 中,快照會在事務中第一次SELECT語句執行時生成,只有在本事務中對資料進行更改才會更新快照。
在資料庫的 RC 這種隔離級別中,還支援"半一致讀" ,一條update語句,如果 where 條件匹配到的記錄已經加鎖,那麼InnoDB會返回記錄最近提交的版本,由MySQL上層判斷此是否需要真的加鎖。
鎖機制
資料庫的鎖,在不同的事務隔離級別下,是採用了不同的機制的。在 MySQL 中,有三種型別的鎖,分別是Record Lock、Gap Lock和 Next-Key Lock。
Record Lock表示記錄鎖,鎖的是索引記錄。
Gap Lock是間隙鎖,鎖的是索引記錄之間的間隙。
Next-Key Lock是Record Lock和Gap Lock的組合,同時鎖索引記錄和間隙。他的範圍是左開右閉的。
在 RC 中,只會對索引增加Record Lock,不會新增Gap Lock和Next-Key Lock。
在 RR 中,為了解決幻讀的問題,在支援Record Lock的同時,還支援Gap Lock和Next-Key Lock;
主從同步
在資料主從同步時,不同格式的 binlog 也對事務隔離級別有要求。
MySQL的binlog主要支援三種格式,分別是statement、row以及mixed,但是,RC 隔離級別只支援row格式的binlog。如果指定了mixed作為 binlog 格式,那麼如果使用RC,伺服器會自動使用基於row 格式的日誌記錄。
而 RR 的隔離級別同時支援statement、row以及mixed三種。
為什麼網際網路公司選擇使用 RC
提升併發
網際網路公司和傳統企業最大的區別是什麼?
高併發!
沒錯,網際網路業務的併發度比傳統企業要高處很多。2020年雙十一當天,訂單建立峰值達到 58.3 萬筆/秒。
很多人問,要怎麼做才能扛得住這麼大的併發量。其實,這背後的優化多到幾個小時都講不完,因為要做的、可以做的事情實在是太多了。
而有一個和我們今天這篇文章有關的優化,那就是通過修改資料庫的隔離級別來提升併發度。
為什麼 RC 比 RR 的併發度要好呢?
首先,RC 在加鎖的過程中,是不需要新增Gap Lock和 Next-Key Lock 的,只對要修改的記錄新增行級鎖就行了。
這就使得併發度要比 RR 高很多。
另外,因為 RC 還支援"半一致讀",可以大大的減少了更新語句時行鎖的衝突;對於不滿足更新條件的記錄,可以提前釋放鎖,提升併發度。
減少死鎖
因為RR這種事務隔離級別會增加Gap Lock和 Next-Key Lock,這就使得鎖的粒度變大,那麼就會使得死鎖的概率增大。
死鎖:一個事務鎖住了表A,然後又訪問表B;另一個事務鎖住了表B,然後企圖訪問表A;這時就會互相等待對方釋放鎖,就導致了死鎖。
總結
本文介紹了一些 MySQL資料庫的 RR 和 RC 兩種事務隔離級別。他們主要在加鎖機制、主從同步以及一致性讀方面存在一些差異。
而很多大廠,為了提升併發度和降低死鎖發生的概率,會把資料庫的隔離級別從預設的 RR 調整成 RC。
當然,這樣做也不是完全沒有問題,首先使用 RC 之後,就需要自己解決幻讀的問題,這個其實還好,很多時候幻讀問題其實是可以忽略的,或者可以用其他手段解決。
還有就是使用 RC 的時候,不能使用statement格式的 binlog,這種影響其實可以忽略不計了,因為MySQL是在5.1.5版本開始支援row的、在5.1.8版本中開始支援mixed,後面這兩種可以代替 statement格式。
所有的技術方案的選擇,都是一種權衡的藝術!