我們知道缠导,我們可以通過這個命令查看數(shù)據(jù)庫當前的隔離級別,MySQL 默認隔離級別是RR.?
select @@tx_isolation;
ANSI/ISO SQL定義的標準隔離級別有四種旗闽,從高到底依次為:可序列化(Serializable)酬核、可重復讀(Repeatable Reads)蜜另、提交讀(Read Committed)、未提交讀(Read Uncommitted)嫡意。
RU隔離級別下举瑰,可能發(fā)生臟讀、幻讀蔬螟、不可重復讀等問題此迅。
未提交讀的數(shù)據(jù)庫鎖情況(實現(xiàn)原理)
事務在讀數(shù)據(jù)的時候并未對數(shù)據(jù)加鎖。
事務在修改數(shù)據(jù)的時候只對數(shù)據(jù)增加行級共享鎖旧巾。
RC隔離級別下耸序,解決了臟讀的問題,存在幻讀鲁猩、不可重復讀的問題坎怪。
提交讀的數(shù)據(jù)庫鎖情況
事務對當前被讀取的數(shù)據(jù)加 行級共享鎖(當讀到時才加鎖),一旦讀完該行廓握,立即釋放該行級共享鎖搅窿;
事務在更新某數(shù)據(jù)的瞬間(就是發(fā)生更新的瞬間),必須先對其加 行級排他鎖隙券,直到事務結(jié)束才釋放男应。
RR隔離級別下,解決了臟讀娱仔、不可重復讀的問題沐飘,存在幻讀的問題。
可重復讀的數(shù)據(jù)庫鎖情況
事務在讀取某數(shù)據(jù)的瞬間(就是開始讀取的瞬間)牲迫,必須先對其加 行級共享鎖耐朴,直到事務結(jié)束才釋放;
事務在更新某數(shù)據(jù)的瞬間(就是發(fā)生更新的瞬間)恩溅,必須先對其加 行級排他鎖隔箍,直到事務結(jié)束才釋放。
Serializable隔離級別下脚乡,解決了臟讀蜒滩、幻讀、不可重復讀的問題奶稠。
可序列化的數(shù)據(jù)庫鎖情況
事務在讀取數(shù)據(jù)時俯艰,必須先對其加 表級共享鎖 ,直到事務結(jié)束才釋放锌订;
事務在更新數(shù)據(jù)時竹握,必須先對其加 表級排他鎖 ,直到事務結(jié)束才釋放辆飘。
雖然可序列化解決了臟讀啦辐、不可重復讀谓传、幻讀等讀現(xiàn)象。但是序列化事務會產(chǎn)生以下效果:
1.無法讀取其它事務已修改但未提交的記錄芹关。
2.在當前事務完成之前续挟,其它事務不能修改目前事務已讀取的記錄。
3.在當前事務完成之前侥衬,其它事務所插入的新記錄诗祸,其索引鍵值不能在當前事務的任何語句所讀取的索引鍵范圍中。
這四種隔離級別是ANSI/ISO SQL定義的標準定義的轴总,我們比較常用的MySQL對這四種隔離級別是都支持的直颅。
Oracle默認的隔離級別是 RC,而MySQL默認的隔離級別是 RR怀樟。那么功偿,你知道為什么嗎?
Oracle 的隔離級別
前面我們說過往堡,Oracle只只支持ANSI/ISO SQL定義的Serializable和Read Committed脖含,其實,根據(jù)Oracle官方文檔給出的介紹投蝉,Oracle支持三種隔離級別:
即Oracle支持Read Committed、Serializable和Read-Only征堪。
Read-Only只讀隔離級別類似于可序列化隔離級別瘩缆,但是只讀事務不允許在事務中修改數(shù)據(jù),除非用戶是SYS佃蚜。
在Oracle這三種隔離級別中庸娱,Serializable和Read-Only顯然都是不適合作為默認隔離級別的,那么就只剩Read Committed這個唯一的選擇了谐算。
MySQL 的隔離級別
在MySQL設計之處熟尉,他的定位就是提供一個穩(wěn)定的關(guān)系型數(shù)據(jù)庫。而為了要解決MySQL單點故障帶來的問題洲脂,MySQL采用主從復制的機制斤儿。
所謂主從復制,其實就是通過搭建MySQL集群恐锦,整體對外提供服務往果,集群中的機器分為主服務器(Master)和從服務器(Slave),主服務器提供寫服務一铅,從服務器提供讀服務陕贮。
為了保證主從服務器之間的數(shù)據(jù)的一致性,就需要進行數(shù)據(jù)同步.
MySQL在主從復制的過程中潘飘,數(shù)據(jù)的同步是通過bin log進行的肮之,簡單理解就是主服務器把數(shù)據(jù)變更記錄到bin log中掉缺,然后再把bin log同步傳輸給從服務器,從服務器接收到bin log之后戈擒,再把其中的數(shù)據(jù)恢復到自己的數(shù)據(jù)庫存儲中眶明。
那么,binlog里面記錄的是什么內(nèi)容呢峦甩?格式是怎樣的呢赘来?
MySQL的bin log主要支持三種格式,分別是statement凯傲、row以及mixed犬辰。MySQL是在5.1.5版本開始支持row的、在5.1.8版本中開始支持mixed冰单。
statement和row最大的區(qū)別幌缝,當binlog的格式為statemen時,binlog 里面記錄的就是 SQL 語句的原文(這句話很重要=肭贰:选!后面會用的到)荒叼。
關(guān)于這幾種格式的區(qū)別轿偎,就不在這里詳細展開了,之所以要支持row格式被廓,主要是因為statement格式中存在很多問題坏晦,最明顯的就是可能會導致主從數(shù)據(jù)庫的數(shù)據(jù)不一致。
那么嫁乘,這個主從同步和bin log我們要講的隔離級別有啥關(guān)系呢馆铁?
有關(guān)系沛慢,而且關(guān)系很大焦读。
因為MySQL早期只有statement這種bin log格式侥袜,這時候,如果使用提交讀(Read Committed)挎春、未提交讀(Read Uncommitted)這兩種隔離級別會出現(xiàn)問題看疙。
比如,在MySQL官網(wǎng)上直奋,有人就給官方曾經(jīng)提過一個相關(guān)的Bug
這個bug的復現(xiàn)過程如下:
有一個數(shù)據(jù)庫表t1狼荞,表中有如下兩條記錄:
CREATETABLE`t1` (? `a`int(11)DEFAULTNULL,? `b`int(11)DEFAULTNULL,? KEY `a` (`a`)) ENGINE=InnoDBDEFAULTCHARSET=latin1;insertintot1values(10,2),(20,1);
接著開始執(zhí)行兩個事務的寫操作:
以上兩個事務執(zhí)行之后,數(shù)據(jù)庫里面的記錄會變成(11帮碰,2)和(20相味,2),這個發(fā)上在主庫的數(shù)據(jù)變更大家都能理解殉挽。
因為事務的隔離級別是read committed丰涉,所以拓巧,事務1在更新時,只會對b=2這行加上行級鎖一死,不會影響到事務2對b=1這行的寫操作肛度。
以上兩個事務執(zhí)行之后,會在bin log中記錄兩條記錄投慈,因為事務2先提交承耿,所以UPDATE t1 SET b=2 where b=1;會被優(yōu)先記錄,然后再記錄UPDATE t1 SET a=11 where b=2;(再次提醒:statement格式的bin log記錄的是SQL語句的原文)
這樣bin log同步到備庫之后伪煤,SQL語句回放時加袋,會先執(zhí)行UPDATE t1 SET b=2 where b=1;,再執(zhí)行UPDATE t1 SET a=11 where b=2;抱既。
這時候职烧,數(shù)據(jù)庫中的數(shù)據(jù)就會變成(11,2)和(11防泵,2)蚀之。這就導致主庫和備庫的數(shù)據(jù)不一致了!=菖ⅰ足删!
為了避免這樣的問題發(fā)生。MySQL就把數(shù)據(jù)庫的默認隔離級別設置成了Repetable Read锁右,那么壹堰,Repetable Read的隔離級別下是如何解決這樣問題的那?
那是因為Repetable Read這種隔離級別骡湖,會在更新數(shù)據(jù)的時候不僅對更新的行加行級鎖,還會增加GAP lock峻厚。上面的例子响蕴,在事務2執(zhí)行的時候,因為事務1增加了GAP lock惠桃,就會導致事務執(zhí)行被卡住浦夷,需要等事務1提交或者回滾后才能繼續(xù)執(zhí)行。
除了設置默認的隔離級別外辜王,MySQL還禁止在使用statement格式的bin log的情況下劈狐,使用READ COMMITTED作為事務隔離級別。
一旦用戶主動修改隔離級別呐馆,嘗試更新時肥缔,會報錯:
ERROR1598(HY000):Binaryloggingnotpossible. Message: Transaction level'READ-COMMITTED' in InnoDB is not safe for binlog mode 'STATEMENT'
小結(jié)
所以,為什么MySQL選擇RR作為默認的數(shù)據(jù)庫隔離級別汹来,其實就是為了兼容歷史上的那種statement格式的bin log续膳。
那么改艇,為啥阿里要把這個數(shù)據(jù)庫隔離級別修改成 RC 呢,背后有什么思考嗎坟岔?
RR 和 RC 的區(qū)別
想要搞清楚這個問題谒兄,我們需要先弄清楚 RR 和 RC 的區(qū)別,分析下各自的優(yōu)缺點社付。
一致性讀
一致性讀承疲,又稱為快照讀∨缚В快照即當前行數(shù)據(jù)之前的歷史版本燕鸽。快照讀就是使用快照信息顯示基于某個時間點的查詢結(jié)果扛或,而不考慮與此同時運行的其他事務所執(zhí)行的更改绵咱。
在MySQL 中,只有READ COMMITTED 和 REPEATABLE READ這兩種事務隔離級別才會使用一致性讀熙兔。
在 RC 中悲伶,每次讀取都會重新生成一個快照,總是讀取行的最新版本住涉。
在 RR 中麸锉,快照會在事務中第一次SELECT語句執(zhí)行時生成,只有在本事務中對數(shù)據(jù)進行更改才會更新快照舆声。
在數(shù)據(jù)庫的RC 這種隔離級別中花沉,還支持"半一致讀",一條update語句媳握,如果 where 條件匹配到的記錄已經(jīng)加鎖碱屁,那么InnoDB會返回記錄最近提交的版本,由MySQL上層判斷此是否需要真的加鎖蛾找。
鎖機制
數(shù)據(jù)庫的鎖娩脾,在不同的事務隔離級別下,是采用了不同的機制的打毛。在 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;
主從同步
在數(shù)據(jù)主從同步時炼彪,不同格式的 binlog 也對事務隔離級別有要求吐根。
MySQL的binlog主要支持三種格式,分別是statement辐马、row以及mixed拷橘,但是,RC 隔離級別只支持row格式的binlog喜爷。如果指定了mixed作為 binlog 格式冗疮,那么如果使用RC,服務器會自動使用基于row 格式的日志記錄檩帐。
而 RR 的隔離級別同時支持statement术幔、row以及mixed三種。
為什么互聯(lián)網(wǎng)公司選擇使用 RC
提升并發(fā)
互聯(lián)網(wǎng)公司和傳統(tǒng)企業(yè)最大的區(qū)別是什么湃密?
高并發(fā)诅挑!
沒錯,互聯(lián)網(wǎng)業(yè)務的并發(fā)度比傳統(tǒng)企業(yè)要高處很多泛源。2020年雙十一當天拔妥,訂單創(chuàng)建峰值達到 58.3 萬筆/秒。
要怎么做才能扛得住這么大的并發(fā)量达箍,要做的没龙、可以做的事情實在是太多了。
而有一個是通過修改數(shù)據(jù)庫的隔離級別來提升并發(fā)度幻梯。
為什么 RC 比 RR 的并發(fā)度要好呢?
首先努释,RC 在加鎖的過程中碘梢,是不需要添加Gap Lock和 Next-Key Lock 的,只對要修改的記錄添加行級鎖就行了伐蒂。
這就使得并發(fā)度要比 RR 高很多煞躬。
另外,因為 RC 還支持"半一致讀",可以大大的減少了更新語句時行鎖的沖突恩沛;對于不滿足更新條件的記錄在扰,可以提前釋放鎖,提升并發(fā)度雷客。
減少死鎖
因為RR這種事務隔離級別會增加Gap Lock和 Next-Key Lock芒珠,這就使得鎖的粒度變大,那么就會使得死鎖的概率增大搅裙。
死鎖:一個事務鎖住了表A皱卓,然后又訪問表B;另一個事務鎖住了表B部逮,然后企圖訪問表A娜汁;這時就會互相等待對方釋放鎖,就導致了死鎖兄朋。
總結(jié)
MySQL數(shù)據(jù)庫的 RR 和 RC 兩種事務隔離級別掐禁,主要在加鎖機制、主從同步以及一致性讀方面存在一些差異颅和。
而很多大廠傅事,為了提升并發(fā)度和降低死鎖發(fā)生的概率,會把數(shù)據(jù)庫的隔離級別從默認的 RR 調(diào)整成 RC融虽。
當然享完,這樣做也不是完全沒有問題,首先使用 RC 之后有额,就需要自己解決幻讀的問題般又,這個很多時候幻讀問題其實是可以忽略的,或者可以用其他手段解決巍佑。
還有就是使用 RC 的時候茴迁,不能使用statement格式的 binlog,這種影響其實可以忽略不計了萤衰,因為MySQL是在5.1.5版本開始支持row的堕义、在5.1.8版本中開始支持mixed,后面這兩種可以代替 statement格式脆栋。
那么我們回答了以下問題倦卖,
1、RR和RC到底有什么區(qū)別椿争?RR是如何解決不可重復讀問題的怕膛?
2、既然MySQL數(shù)據(jù)庫默認選擇了RR秦踪,那么褐捻,為啥大的互聯(lián)網(wǎng)公司會把默認的隔離級別改成RC掸茅?
然而你或許還會有以下問題:
1、row格式和statement有什么區(qū)別柠逞?使用row的情況下昧狮,可以使用RR嗎?
2板壮、文中提到的RC的GAP lock到底是什么逗鸣?Next-key Lock
?數(shù)據(jù)庫使用鎖是為了支持更好的并發(fā),提供數(shù)據(jù)的完整性和一致性个束。InnoDB是一個支持行鎖的存儲引擎慕购,鎖的類型有:共享鎖(S)、排他鎖(X)茬底、意向共享(IS)沪悲、意向排他(IX)。為了提供更好的并發(fā)阱表,InnoDB提供了非鎖定讀:不需要等待訪問行上的鎖釋放殿如,讀取行的一個快照。該方法是通過InnoDB的一個特性:MVCC來實現(xiàn)的最爬。
InnoDB有三種行鎖的算法:
1涉馁,Record Lock:單個行記錄上的鎖。
2爱致,Gap Lock:間隙鎖烤送,鎖定一個范圍,但不包括記錄本身糠悯。GAP鎖的目的帮坚,是為了防止同一事務的兩次當前讀,出現(xiàn)幻讀的情況互艾。
3试和,Next-Key Lock:1+2,鎖定一個范圍纫普,并且鎖定記錄本身阅悍。對于行的查詢,都是采用該方法昨稼,主要目的是解決幻讀的問題节视。
mysql binlog的三種格式簡單概括總結(jié)
1、三種格式:row假栓、statement寻行、mixed
2、區(qū)別:row格式文件比較大但指,statement比較小寡痰,row格式保存的是一行一行的數(shù)據(jù),statement保存的是sql語句棋凳,mixed格式介于二者之間拦坠,statement容易丟數(shù)據(jù),row格式則不會
3剩岳、statement容易丟數(shù)據(jù)原因是贞滨,有時候,SQL語句里面會用到一些函數(shù)拍棕,比如說取當前日期的函數(shù)sysdate晓铆,你要是用statement,binlog里同步過去的就是這個帶有函數(shù)的SQL語句绰播,而主庫的當前日期骄噪,和binlog同步到slave上的當前日期,肯定是有差異的蠢箩,這樣兩條數(shù)據(jù)就不一致了链蕊,所以這樣同步的數(shù)據(jù),就會有問題
4谬泌、row是直接把表插入到備份庫中滔韵,statement是導出主庫語句后,導入到備份庫中掌实,存在時間差陪蜻。
每種格式的概括
STATEMENT
記錄的是執(zhí)行的SQL語句
優(yōu)點:
日志記錄量相對較小, 節(jié)約磁盤及網(wǎng)絡IO
缺點:
可能造成MySQL復制的主備服務器數(shù)據(jù)不一致
必須記錄上下文信息, 以保證語句在從服務器上執(zhí)行結(jié)果相同
對于特定函數(shù)如 UUID(), user() 這種非確定性函數(shù)是無法正確復制
ROW
記錄的是每一行數(shù)據(jù)的修改, MySQL5.7+的默認ROW格式.
優(yōu)點:
可以避免MySQL復制中出現(xiàn)主從不一致的問題
對每一行數(shù)據(jù)的修改比STATEMENT模式高效
可在誤刪改數(shù)據(jù)后, 同時無備份可以恢復時, 通過分析binlog日志進行反向處理達到恢復數(shù)據(jù)目的
缺點:
由于記錄每一行數(shù)據(jù)的修改, 所以日志量比較大
可通過binlog_row_image=FULL | MINIMAL | NOBLOB 設置日志記錄的方式.
FULL: 記錄行中所有列修改前后的數(shù)據(jù).
MINIMAL: 記錄行中所有列修改前的數(shù)據(jù)+被修改列修改后的數(shù)據(jù).
NOBLOB: 記錄行中所有列修改前的數(shù)據(jù)+(未對行中TEXT和BLOB類型列修改時, 記錄TEXT和BLOB類型以外的列的數(shù)據(jù).)
MIXED
混合STATEMENT和ROW兩種格式, MySQL會根據(jù)執(zhí)行的SQL語句自動選擇.
一般的復制使用STATEMENT格式,對于STATEMENT格式無法復制的操作使用ROW格式.
如何選擇binlog日志格式?
在同一個IDC機房中, 建議使用MIXED或ROW格式, 當使用ROW格式時, 建議設置binlog_row_image=MINIMAL
關(guān)于以上幾個問題贱鼻,你對哪個更感興趣呢宴卖?
talk is easy, show me the code