數(shù)據(jù)庫(kù)一般都會(huì)并發(fā)執(zhí)行多個(gè)事務(wù)匪燕,多個(gè)事務(wù)可能會(huì)并發(fā)的對(duì)相同的一批數(shù)據(jù)進(jìn)行增刪改查操作蕾羊,可能就會(huì)導(dǎo)致我們說(shuō)的臟寫、臟讀帽驯、不可重復(fù)讀龟再、幻讀這些問(wèn)題。
這些問(wèn)題的本質(zhì)都是數(shù)據(jù)庫(kù)的多事務(wù)并發(fā)問(wèn)題尼变,為了解決多事務(wù)并發(fā)問(wèn)題利凑,數(shù)據(jù)庫(kù)設(shè)計(jì)了事務(wù)隔離機(jī)制浆劲、鎖機(jī)制、MVCC多版本并發(fā)控制隔離機(jī)制哀澈,用一整套機(jī)制來(lái)解決多事務(wù)并發(fā)問(wèn)題牌借。
1.并發(fā)事務(wù)帶來(lái)的問(wèn)題以及事務(wù)隔離級(jí)別
并發(fā)事務(wù)處理帶來(lái)的問(wèn)題:
- 更新丟失(Lost Update)或臟寫
當(dāng)兩個(gè)或多個(gè)事務(wù)選擇同一行,然后基于最初選定的值更新該行時(shí)割按,由于每個(gè)事務(wù)都不知道其他事務(wù)的存在膨报,就會(huì)發(fā)生丟失更新問(wèn)題–最后的更新覆蓋了由其他事務(wù)所做的更新。 - 臟讀(Dirty Reads)
一個(gè)事務(wù)正在對(duì)一條記錄做修改适荣,在這個(gè)事務(wù)完成并提交前现柠,這條記錄的數(shù)據(jù)就處于不一致的狀態(tài);這時(shí)弛矛,另一個(gè)事務(wù)也來(lái)讀取同一條記錄够吩,如果不加控制,第二個(gè)事務(wù)讀取了這些“臟”數(shù)據(jù)丈氓,并據(jù)此作進(jìn)一步的處理周循,就會(huì)產(chǎn)生未提交的數(shù)據(jù)依賴關(guān)系。這種現(xiàn)象被形象的叫做“臟讀”扒寄。
一句話:事務(wù)A讀取到了事務(wù)B已經(jīng)修改但尚未提交的數(shù)據(jù)鱼鼓,還在這個(gè)數(shù)據(jù)基礎(chǔ)上做了操作。此時(shí)该编,如果B事務(wù)回滾迄本,A讀取的數(shù)據(jù)無(wú)效,不符合一致性要求课竣。 - 不可重讀(Non-Repeatable Reads)
一個(gè)事務(wù)在讀取某些數(shù)據(jù)后的某個(gè)時(shí)間嘉赎,再次讀取以前讀過(guò)的數(shù)據(jù),卻發(fā)現(xiàn)其讀出的數(shù)據(jù)已經(jīng)發(fā)生了改變于樟、或某些記錄已經(jīng)被刪除了公条!這種現(xiàn)象就叫做“不可重復(fù)讀”。
一句話:事務(wù)A內(nèi)部的相同查詢語(yǔ)句在不同時(shí)刻讀出的結(jié)果不一致迂曲,不符合隔離性 - 幻讀(Phantom Reads)
一個(gè)事務(wù)按相同的查詢條件重新讀取以前檢索過(guò)的數(shù)據(jù)靶橱,卻發(fā)現(xiàn)其他事務(wù)插入了滿足其查詢條件的新數(shù)據(jù),這種現(xiàn)象就稱為“幻讀”路捧。
一句話:事務(wù)A讀取到了事務(wù)B提交的新增數(shù)據(jù)关霸,不符合隔離性
上述“臟讀”、“不可重復(fù)讀”和“幻讀”可以通過(guò)不同的事務(wù)隔離級(jí)別來(lái)解決:
2.RC RR隔離級(jí)別是怎樣實(shí)現(xiàn)的杰扫?
在可重復(fù)讀隔離級(jí)別Repeatable read队寇,當(dāng)事務(wù)開啟,執(zhí)行任何查詢sql時(shí)會(huì)生成當(dāng)前事務(wù)的一致性視圖read-view章姓,該視圖在事務(wù)結(jié)束之前都不會(huì)變化佳遣,這個(gè)視圖由執(zhí)行查詢時(shí)所有未提交事務(wù)id數(shù)組(數(shù)組里最小的id為min_id)和已創(chuàng)建的最大事務(wù)id(max_id)組成识埋,事務(wù)里的任何sql查詢結(jié)果需要從對(duì)應(yīng)版本鏈里的最新數(shù)據(jù)開始逐條跟read-view做比對(duì)從而得到最終的快照結(jié)果。
如果是讀已提交隔離級(jí)別Read committed在每次執(zhí)行查詢sql時(shí)都會(huì)重新生成窒舟。
2.1 MVCC
Mysql在可重復(fù)讀隔離級(jí)別下如何保證事務(wù)較高的隔離性,我們上節(jié)課給大家演示過(guò)相恃,同樣的sql查詢語(yǔ)句在一個(gè)事務(wù)里多次執(zhí)行查詢結(jié)果相同辜纲,就算其它事務(wù)對(duì)數(shù)據(jù)有修改也不會(huì)影響當(dāng)前事務(wù)sql語(yǔ)句的查詢結(jié)果。
這個(gè)隔離性就是靠MVCC(Multi-Version Concurrency Control)機(jī)制來(lái)保證的拦耐,對(duì)一行數(shù)據(jù)的讀和寫兩個(gè)操作默認(rèn)是不會(huì)通過(guò)加鎖互斥來(lái)保證隔離性耕腾,避免了頻繁加鎖互斥,而在串行化隔離級(jí)別為了保證較高的隔離性是通過(guò)將所有操作加鎖互斥來(lái)實(shí)現(xiàn)的杀糯。
undo日志版本鏈?zhǔn)侵敢恍袛?shù)據(jù)被多個(gè)事務(wù)依次修改過(guò)后扫俺,在每個(gè)事務(wù)修改完后,Mysql會(huì)保留修改前的數(shù)據(jù)undo回滾日志固翰,并且用兩個(gè)隱藏字段trx_id和roll_pointer把這些undo日志串聯(lián)起來(lái)形成一個(gè)歷史記錄版本鏈狼纬。
版本鏈比對(duì)規(guī)則:
- 1)如果 row 的 trx_id 落在綠色部分( trx_id<min_id ),表示這個(gè)版本是已提交的事務(wù)生成的骂际,這個(gè)數(shù)據(jù)是可見(jiàn)的疗琉;
- 2)如果 row 的 trx_id 落在紅色部分( trx_id>max_id ),表示這個(gè)版本是由將來(lái)啟動(dòng)的事務(wù)生成的歉铝,是不可見(jiàn)的(若 row 的 trx_id 就是當(dāng)前自己的事務(wù)是可見(jiàn)的)盈简;
- 3)如果 row 的 trx_id 落在黃色部分(min_id <=trx_id<= max_id),那就包括兩種情況
a. 若 row 的 trx_id 在視圖數(shù)組中太示,表示這個(gè)版本是由還沒(méi)提交的事務(wù)生成的柠贤,不可見(jiàn)(若 row 的 trx_id 就是當(dāng)前自己的事務(wù)是可見(jiàn)的);
b. 若 row 的 trx_id 不在視圖數(shù)組中类缤,表示這個(gè)版本是已經(jīng)提交了的事務(wù)生成的臼勉,可見(jiàn)。
3.鎖機(jī)制
InnoDB使用不同的鎖策略(Locking Strategy)以及MVCC機(jī)制來(lái)實(shí)現(xiàn)不同的隔離級(jí)別餐弱。
- 讀未提交(Read Uncommitted)
事務(wù)在讀數(shù)據(jù)的時(shí)候不對(duì)數(shù)據(jù)加鎖宴霸。
事務(wù)在修改數(shù)據(jù)的時(shí)候只對(duì)數(shù)據(jù)增加行級(jí)共享鎖。 - 讀已提交(Read Committed)
普通讀是快照讀膏蚓,這是一種不加鎖的一致性讀猖败,底層使用MVCC實(shí)現(xiàn)。
加鎖的select, update, delete等語(yǔ)句降允,除了在外鍵約束檢查(foreign-key constraint checking)以及重復(fù)鍵檢查(duplicate-key checking)時(shí)會(huì)封鎖區(qū)間,其他時(shí)刻都只使用記錄鎖艺糜。 - 可重復(fù)讀(Repeatable Read)
普通的select使用快照讀剧董。
加鎖的select(select…in share mode/select…for update)幢尚,update,delete等語(yǔ)句翅楼,它們的鎖尉剩,依賴于它們是否在唯一索引上使用了唯一的查詢條件,或者范圍查詢條件:
1)在唯一索引上使用唯一的查詢條件毅臊,會(huì)使用記錄鎖,而不會(huì)封鎖記錄之間的間隔,即不會(huì)使用間隙鎖與臨鍵鎖稍走。
2)范圍查詢條件為非唯一值時(shí)原探,會(huì)使用臨鍵鎖,鎖住索引記錄之間的范圍蚯撩,避免范圍間插入記錄础倍,以避免產(chǎn)生幻讀,以及避免不可重復(fù)讀胎挎。 - 串行化(Serializable)
這種事務(wù)隔離級(jí)別下沟启,所有select語(yǔ)句會(huì)被隱式的轉(zhuǎn)化為select…in share mode。
這會(huì)導(dǎo)致犹菇,如果有未提交的事務(wù)正在修改某些行德迹,所有讀取這些行的操作都會(huì)被阻塞。
3.1 鎖分類
- 從性能上分為樂(lè)觀鎖(用版本對(duì)比來(lái)實(shí)現(xiàn))和悲觀鎖
- 從對(duì)數(shù)據(jù)庫(kù)操作的類型分揭芍,分為讀鎖和寫鎖(都屬于悲觀鎖)
讀鎖(共享鎖胳搞,S鎖(Shared)):針對(duì)同一份數(shù)據(jù),多個(gè)讀操作可以同時(shí)進(jìn)行而不會(huì)互相影響
寫鎖(排它鎖沼沈,X鎖(eXclusive)):當(dāng)前寫操作沒(méi)有完成前流酬,它會(huì)阻斷其他寫鎖和讀鎖 - 從對(duì)數(shù)據(jù)操作的粒度分,分為表鎖和行鎖
表鎖:每次操作鎖住整張表列另。開銷小芽腾,加鎖快;不會(huì)出現(xiàn)死鎖页衙;鎖定粒度大摊滔,發(fā)生鎖沖突的概率最高,并發(fā)度最低店乐;一般用在整表數(shù)據(jù)遷移的場(chǎng)景艰躺。
無(wú)索引行鎖會(huì)升級(jí)為表鎖(RR級(jí)別會(huì)升級(jí)為表鎖,RC級(jí)別不會(huì)升級(jí)為表鎖)眨八。
3.2 行鎖
每次操作鎖住一行數(shù)據(jù)腺兴。開銷大,加鎖慢廉侧;會(huì)出現(xiàn)死鎖页响;鎖定粒度最小篓足,發(fā)生鎖沖突的概率最低,并發(fā)度最高闰蚕。
間隙鎖栈拖,鎖的就是兩個(gè)值之間的空隙。間隙鎖是在可重復(fù)讀隔離級(jí)別下才會(huì)生效没陡。
臨鍵鎖(Next-key Locks)是行鎖與間隙鎖的組合涩哟。
3.3 鎖優(yōu)化建議
- 盡可能讓所有數(shù)據(jù)檢索都通過(guò)索引來(lái)完成,避免無(wú)索引行鎖升級(jí)為表鎖
- 合理設(shè)計(jì)索引盼玄,盡量縮小鎖的范圍
- 盡可能減少檢索條件范圍贴彼,避免間隙鎖
- 盡量控制事務(wù)大小,減少鎖定資源量和時(shí)間長(zhǎng)度强岸,涉及事務(wù)加鎖的sql盡量放在事務(wù)最后執(zhí)行
- 盡可能低級(jí)別事務(wù)隔離