HOME 首頁
SERVICE 服務(wù)產(chǎn)品
XINMEITI 新媒體代運(yùn)營(yíng)
CASE 服務(wù)案例
NEWS 熱點(diǎn)資訊
ABOUT 關(guān)于我們
CONTACT 聯(lián)系我們
創(chuàng)意嶺
讓品牌有溫度、有情感
專注品牌策劃15年

    快照讀會(huì)加鎖嗎(快照讀 rr rc)

    發(fā)布時(shí)間:2023-04-19 09:26:24     稿源: 創(chuàng)意嶺    閱讀: 101        

    大家好!今天讓創(chuàng)意嶺的小編來大家介紹下關(guān)于快照讀會(huì)加鎖嗎的問題,以下是小編對(duì)此問題的歸納整理,讓我們一起來看看吧。

    開始之前先推薦一個(gè)非常厲害的Ai人工智能工具,一鍵生成原創(chuàng)文章、方案、文案、工作計(jì)劃、工作報(bào)告、論文、代碼、作文、做題和對(duì)話答疑等等

    只需要輸入關(guān)鍵詞,就能返回你想要的內(nèi)容,越精準(zhǔn),寫出的就越詳細(xì),有微信小程序端、在線網(wǎng)頁版、PC客戶端

    官網(wǎng):https://ai.de1919.com。

    創(chuàng)意嶺作為行業(yè)內(nèi)優(yōu)秀的企業(yè),服務(wù)客戶遍布全球各地,如需了解SEO相關(guān)業(yè)務(wù)請(qǐng)撥打電話175-8598-2043,或添加微信:1454722008

    本文目錄:

    快照讀會(huì)加鎖嗎(快照讀 rr rc)

    一、Innodb事務(wù)--隔離級(jí)別

    innodb 事務(wù)有四個(gè)隔離級(jí)別,分別為:未提交讀、提交讀、重復(fù)讀與序列化

    由于隔離級(jí)別的不同,會(huì)導(dǎo)致如下問題:臟讀、不可重復(fù)讀、幻讀。

    臟讀 :指當(dāng)前事務(wù)能看到其他事務(wù)還沒Commit的內(nèi)容。

    不可重復(fù)讀 :同一個(gè)事務(wù)中,分別兩次查詢相同的一行數(shù)據(jù),看到的結(jié)果不一致。

    幻讀 :幻讀指的是一個(gè)事務(wù)在前后兩次查詢同一個(gè)范圍的時(shí)候,后一次查詢看到了前一次查詢沒有看到的行。

    不可重復(fù)讀和幻讀最大區(qū)別 :不可重復(fù)讀重點(diǎn)在于update和delete,而幻讀的重點(diǎn)在于insert,也有說法是幻讀 側(cè)重行發(fā)生了變化;不可重復(fù)讀側(cè)重某行數(shù)據(jù)的修改。不可重復(fù)讀是修改了存在的數(shù)據(jù),導(dǎo)致兩次查看不一致,幻讀是新增了之前不存在的數(shù)據(jù)。

    下面一一解釋各個(gè)隔離級(jí)別會(huì)產(chǎn)生的問題與如何解決臟讀、不可重復(fù)讀、幻讀

    事務(wù)中的修改,即使沒有提交,其他事務(wù)也可以看得到,會(huì) 導(dǎo)致“臟讀”、“幻讀”和“不可重復(fù)讀取”

    一個(gè)事務(wù)不會(huì)讀到另一個(gè)并行事務(wù)已修改但未提交的數(shù)據(jù),避免了“臟讀取”,但不能避免“幻讀”和“不可重復(fù)讀取”。

    為什么無不能避免幻讀和不可重復(fù)讀?

    因?yàn)槊看巫x取都會(huì)重新生成一個(gè)快照,所以每次快照都是最新的,也因此事務(wù)中每次SELECT也可以看到其它已commit事務(wù)所作的更改;

    這是MySQL中InnoDB默認(rèn)的隔離級(jí)別。從讀的角度看, 快照會(huì)在事務(wù)中第一次SELECT語句執(zhí)行時(shí)生成,只有在本事務(wù)中對(duì)數(shù)據(jù)進(jìn)行更改才會(huì)更新快照 ;

    RR級(jí)別的事務(wù)隔離可以解決臟讀和不可重復(fù)讀,他通過MVVC解決了 快照讀情況下的幻讀問題 ,當(dāng)前讀下的幻讀是以來Innodb的鎖機(jī)制實(shí)現(xiàn)的。所以總結(jié)起來就是:

    1.在快照讀情況下,Mysql通過MVVC來避免幻讀。

    2.在當(dāng)前讀的情況下,Mysql通過鎖機(jī)制來避免幻讀。

    可以通過下面SQL進(jìn)行測(cè)試

    上述例子,事務(wù)2插入語句并且提交了,事務(wù)1通過update語句能更新到,那是因?yàn)閡pdate語句是當(dāng)前都,他能讀到現(xiàn)在最新的數(shù)據(jù)并加鎖。下面語句要達(dá)到可重復(fù)度的級(jí)別,需要這樣修改:

    所以,網(wǎng)上很多說可重復(fù)讀通過MVVC機(jī)制解決了幻讀問題,其實(shí)是不正確的,鎖機(jī)制才是解決可重復(fù)讀下幻讀問題的重大功臣

    這里補(bǔ)充說明下當(dāng)前讀與快照讀是什么!

    快照讀 :讀取專門的快照 (對(duì)于RC,快照(ReadView)會(huì)在每個(gè)語句中創(chuàng)建。對(duì)于RR,快照是在事務(wù)啟動(dòng)時(shí)創(chuàng)建的)。簡(jiǎn)單的select操作即可。

    當(dāng)前讀 :讀取最新版本的記錄, 沒有快照。 在InnoDB中,當(dāng)前讀取根本不會(huì)創(chuàng)建任何快照。語句包括:select ... lock in share mode、select ... for update、inset、update、delete。當(dāng)前讀是通過手動(dòng)加record lock(記錄鎖)和gap lock(間隙鎖)來實(shí)現(xiàn)的。

    待續(xù)-后續(xù)講下事務(wù)的原理

    二、mysql 解決可提交讀、可重復(fù)讀、幻讀

    這張圖本人覺得總結(jié)得挺好的,在一般的互聯(lián)網(wǎng)項(xiàng)目中,基本上用的都是Innodb引擎,一般只涉及到的都是行級(jí)鎖,但是如果sql語句中不帶索引進(jìn)行操作,可能會(huì)導(dǎo)致鎖表,這是不推薦的,性能非常低,可能會(huì)導(dǎo)致全表掃描等,行鎖的具體實(shí)現(xiàn)算法有以下幾種mysql特有的鎖:

    Record Lock(記錄鎖):單個(gè)行記錄的鎖,一般是唯一索引或者主鍵上的加鎖

    Gap Lock(間隙鎖):鎖定一個(gè)區(qū)間,但是不包括自身,開區(qū)間的鎖,RR級(jí)別才會(huì)有間隙鎖,間隙鎖的唯一目的是防止區(qū)間數(shù)據(jù)的插入,所以間隙鎖與間隙鎖之間是不會(huì)相互阻塞的

    Next-key Lock(臨鍵鎖):與間隙鎖的區(qū)別是包括自身,是左開右閉區(qū)間,RR級(jí)別才會(huì)有

    加鎖規(guī)則里面,包含了兩個(gè)“原則”、兩個(gè)“優(yōu)化”和一個(gè)“bug”。

    原則 1:加鎖的基本單位是 next-key lock,希望你還記得,next-key lock 是前開后閉區(qū)間。

    原則 2:查找過程中訪問到的對(duì)象才會(huì)加鎖。

    優(yōu)化 1:索引上的等值查詢,給唯一索引加鎖的時(shí)候,next-key lock 退化為行鎖。

    優(yōu)化 2:索引上的等值查詢,向右遍歷時(shí)且最后一個(gè)值不滿足等值條件的時(shí)候,next-key lock 退化為間隙鎖。

    一個(gè) bug:唯一索引上的范圍查詢會(huì)訪問到不滿足條件的第一個(gè)值為止。

    舉例來說明上述的原則:

    建表

    插入數(shù)據(jù):

    INSERT INTO t ( id , c , d ) VALUES (0, 0, 0);

    INSERT INTO t ( id , c , d ) VALUES (5, 5, 10);

    INSERT INTO t ( id , c , d ) VALUES (10, 10, 10);

    INSERT INTO t ( id , c , d ) VALUES (15, 15, 15);

    INSERT INTO t ( id , c , d ) VALUES (20, 20, 20);

    INSERT INTO t ( id , c , d ) VALUES (25, 25, 25);

    例子1:鎖表

    因?yàn)閐字段上沒有建索引,所以涉及該字段的查詢加鎖會(huì)鎖住整個(gè)表

    因?yàn)閐字段上面沒有建立索引,所以事務(wù)1執(zhí)行后會(huì)導(dǎo)致整個(gè)表被鎖,后面所有的操作都會(huì)在等待整個(gè)表鎖被釋放

    例子2:主鍵/唯一索引 記錄鎖

    id字段為主鍵,而且事務(wù)1查詢命中了唯一的記錄,默認(rèn)是加Next-key Lock,區(qū)間是(0,5],但是根據(jù)優(yōu)化1,唯一索引/主鍵上的等值查詢,會(huì)退化為行鎖,所以只會(huì)鎖5這個(gè)記錄。

    例子3:主鍵/唯一索引上的間隙鎖

    由于表 t 中沒有 id=7 的記錄,所以用我們上面提到的加鎖規(guī)則判斷一下的話:根據(jù)原則 1,加鎖單位是 next-key lock,事務(wù)1加鎖范圍就是 (5,10];同時(shí)根據(jù)優(yōu)化 2,這是一個(gè)等值查詢 (id=7),而 id=10 不滿足查詢條件,next-key lock 退化成間隙鎖,因此最終加鎖的范圍是 (5,10),所以事務(wù)2會(huì)阻塞,事務(wù)3執(zhí)行成功。

    例子4:普通索引上的間隙鎖

    c字段是普通索引,事務(wù)1執(zhí)行時(shí)默認(rèn)是對(duì)區(qū)間(0,5]加間隙鎖,根據(jù)優(yōu)化2,非唯一索引/主鍵會(huì)繼續(xù)向右遍歷,找到10,所以最終的加鎖為(0,5]的Next-Key鎖+(5,10)的間隙鎖,所以事務(wù)2阻塞,事務(wù)3成功。

    例子5:間隙鎖與行鎖

    事務(wù)1默認(rèn)的Next-Key鎖區(qū)間是(0,5],根據(jù)優(yōu)化2會(huì)向右遍歷,找到不滿足查詢條件的10,退化成間隙鎖,所以事務(wù)1的鎖是(0,5]的Next-Key鎖+(5,10)的間隙鎖,這兩個(gè)鎖與行鎖是沖突的,而事務(wù)2申請(qǐng)的Next-Key鎖是和事務(wù)1一樣,但是c=5的行鎖與事務(wù)1沖突,所以產(chǎn)生了阻塞,如果改為update t set d=1000 where c=6;因?yàn)榇藭r(shí)產(chǎn)生的間隙鎖為(5,10),而間隙鎖與間隙鎖是不沖突的,不會(huì)產(chǎn)生阻塞

    例子6:lock in share mode鎖覆蓋索引

    事務(wù)1存在覆蓋索引的情況,不會(huì)去回表,lock in share mode這種情況下只會(huì)鎖c字段索引,而事務(wù)2是對(duì)主鍵加行鎖,所以兩者不存在沖突。

    例子7:主鍵/唯一索引上的范圍查詢

    開始執(zhí)行的時(shí)候,要找到第一個(gè) id=10 的行,因此本該是 Next-Key Lock(5,10],根據(jù)優(yōu)化 1, 主鍵 id 上的等值條件,退化成行鎖,只加了 id=10 這一行的行鎖。范圍查找就往后繼續(xù)找,找到 id=15 這一行停下來,因此需要加 Next-Key Lock(10,15],所以事務(wù)3是沖突的。

    例子8:普通索引上的范圍查詢

    開始執(zhí)行時(shí),找到第一個(gè)滿足條件的行10,加鎖Next-Key Lock(5,10],因?yàn)椴皇俏ㄒ凰饕?,所以不?huì)退化,繼續(xù)向后面找,找到15這一行停下來,因此需要加 Next-Key Lock(10,15],因?yàn)槭欠秶樵?,所以鎖不會(huì)退化。

    快照讀: 通過MVCC實(shí)現(xiàn),該技術(shù)不僅可以保證innodb的可重復(fù)讀,而且可以防止幻讀,但是他讀取的數(shù)據(jù)雖然是一致的,但是數(shù)據(jù)是歷史數(shù)據(jù)。

    簡(jiǎn)單的select操作(不包括 select … lock in share mode, select … for update)

    當(dāng)前讀: 要做到保證數(shù)據(jù)是一致的,同時(shí)讀取的數(shù)據(jù)是最新的數(shù)據(jù),innodb提供了next-key lock,即gap鎖與行鎖結(jié)合來實(shí)現(xiàn)。

    select … lock in share mode

    select … for update

    insert

    update

    delete

    自己理解:

    簡(jiǎn)單的select是快照讀,快照讀實(shí)現(xiàn)可提交讀,可重復(fù)讀和幻讀是通過MVCC+ReadView實(shí)現(xiàn)的,而當(dāng)前讀實(shí)現(xiàn)這幾種是通過鎖來實(shí)現(xiàn)的,為了說明具體原理,下面介紹下MVCC和ReadView概念,所以簡(jiǎn)單的select是通過樂觀鎖實(shí)現(xiàn)的,當(dāng)前讀是通過悲觀鎖實(shí)現(xiàn)的。

    參考文章:

    https://www.sohu.com/a/302045871_411876

    https://www.jianshu.com/p/d1aba64b5c03

    https://www.jianshu.com/p/32904ee07e56

    三、事務(wù)的隔離級(jí)別 全部都是共享鎖嗎

    前言: 我們都知道事務(wù)的幾種性質(zhì),數(shù)據(jù)庫為了維護(hù)這些性質(zhì),尤其是一致性和隔離性,一般使用加鎖這種方式。同時(shí)數(shù)據(jù)庫又是個(gè)高并發(fā)的應(yīng)用,同一時(shí)間會(huì)有大量的并發(fā)訪問,如果加鎖過度,會(huì)極大的降低并發(fā)處理能力。所以對(duì)于加鎖的處理,可以說就是數(shù)據(jù)庫對(duì)于事務(wù)處理的精髓所在。這里通過分析MySQL中InnoDB引擎的加鎖機(jī)制,來拋磚引玉,讓讀者更好的理解,在事務(wù)處理中數(shù)據(jù)庫到底做了什么。 一次封鎖or兩段鎖? 因?yàn)橛写罅康牟l(fā)訪問,為了預(yù)防死鎖,一般應(yīng)用中推薦使用一次封鎖法,就是在方法的開始階段,已經(jīng)預(yù)先知道會(huì)用到哪些數(shù)據(jù),然后全部鎖住,在方法運(yùn)行之后,再全部解鎖。這種方式可以有效的避免循環(huán)死鎖,但在數(shù)據(jù)庫中卻不適用,因?yàn)樵谑聞?wù)開始階段,數(shù)據(jù)庫并不知道會(huì)用到哪些數(shù)據(jù)。 數(shù)據(jù)庫遵循的是兩段鎖協(xié)議,將事務(wù)分成兩個(gè)階段,加鎖階段和解鎖階段(所以叫兩段鎖) 加鎖階段:在該階段可以進(jìn)行加鎖操作。在對(duì)任何數(shù)據(jù)進(jìn)行讀操作之前要申請(qǐng)并獲得S鎖(共享鎖,其它事務(wù)可以繼續(xù)加共享鎖,但不能加排它鎖),在進(jìn)行寫操作之前要申請(qǐng)并獲得X鎖(排它鎖,其它事務(wù)不能再獲得任何鎖)。加鎖不成功,則事務(wù)進(jìn)入等待狀態(tài),直到加鎖成功才繼續(xù)執(zhí)行。 解鎖階段:當(dāng)事務(wù)釋放了一個(gè)封鎖以后,事務(wù)進(jìn)入解鎖階段,在該階段只能進(jìn)行解鎖操作不能再進(jìn)行加鎖操作。 事務(wù) 加鎖/解鎖處理 begin; insert into test ..... 加insert對(duì)應(yīng)的鎖 update test set... 加update對(duì)應(yīng)的鎖 delete from test .... 加delete對(duì)應(yīng)的鎖 commit; 事務(wù)提交時(shí),同時(shí)釋放insert、update、delete對(duì)應(yīng)的鎖 這種方式雖然無法避免死鎖,但是兩段鎖協(xié)議可以保證事務(wù)的并發(fā)調(diào)度是串行化(串行化很重要,尤其是在數(shù)據(jù)恢復(fù)和備份的時(shí)候)的。 事務(wù)中的加鎖方式 事務(wù)的四種隔離級(jí)別 在數(shù)據(jù)庫操作中,為了有效保證并發(fā)讀取數(shù)據(jù)的正確性,提出的事務(wù)隔離級(jí)別。我們的數(shù)據(jù)庫鎖,也是為了構(gòu)建這些隔離級(jí)別存在的。 隔離級(jí)別 臟讀(Dirty Read) 不可重復(fù)讀(NonRepeatable Read) 幻讀(Phantom Read) 未提交讀(Read uncommitted) 可能 可能 可能 已提交讀(Read committed) 不可能 可能 可能 可重復(fù)讀(Repeatable read) 不可能 不可能 可能 可串行化(Serializable ) 不可能 不可能 不可能 未提交讀(Read Uncommitted):允許臟讀,也就是可能讀取到其他會(huì)話中未提交事務(wù)修改的數(shù)據(jù) 提交讀(Read Committed):只能讀取到已經(jīng)提交的數(shù)據(jù)。Oracle等多數(shù)數(shù)據(jù)庫默認(rèn)都是該級(jí)別 (不重復(fù)讀) 可重復(fù)讀(Repeated Read):可重復(fù)讀。在同一個(gè)事務(wù)內(nèi)的查詢都是事務(wù)開始時(shí)刻一致的,InnoDB默認(rèn)級(jí)別。在SQL標(biāo)準(zhǔn)中,該隔離級(jí)別消除了不可重復(fù)讀,但是還存在幻象讀 串行讀(Serializable):完全串行化的讀,每次讀都需要獲得表級(jí)共享鎖,讀寫相互都會(huì)阻塞 Read Uncommitted這種級(jí)別,數(shù)據(jù)庫一般都不會(huì)用,而且任何操作都不會(huì)加鎖,這里就不討論了。 MySQL中鎖的種類 MySQL中鎖的種類很多,有常見的表鎖和行鎖,也有新加入的Metadata Lock等等,表鎖是對(duì)一整張表加鎖,雖然可分為讀鎖和寫鎖,但畢竟是鎖住整張表,會(huì)導(dǎo)致并發(fā)能力下降,一般是做ddl處理時(shí)使用。 行鎖則是鎖住數(shù)據(jù)行,這種加鎖方法比較復(fù)雜,但是由于只鎖住有限的數(shù)據(jù),對(duì)于其它數(shù)據(jù)不加限制,所以并發(fā)能力強(qiáng),MySQL一般都是用行鎖來處理并發(fā)事務(wù)。這里主要討論的也就是行鎖。 Read Committed(讀取提交內(nèi)容) 在RC級(jí)別中,數(shù)據(jù)的讀取都是不加鎖的,但是數(shù)據(jù)的寫入、修改和刪除是需要加鎖的。效果如下 MySQL> show create table class_teacher \G\ Table: class_teacher Create Table: CREATE TABLE `class_teacher` ( `id` int(11) NOT NULL AUTO_INCREMENT, `class_name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, `teacher_id` int(11) NOT NULL, PRIMARY KEY (`id`), KEY `idx_teacher_id` (`teacher_id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci 1 row in set (0.02 sec) MySQL> select * from class_teacher; +----+--------------+------------+ id class_name teacher_id +----+--------------+------------+ 1 初三一班 1 3 初二一班 2 4 初二二班 2 +----+--------------+------------+ 由于MySQL的InnoDB默認(rèn)是使用的RR級(jí)別,所以我們先要將該session開啟成RC級(jí)別,并且設(shè)置binlog的模式 SET session transaction isolation level read committed; SET SESSION binlog_format = 'ROW'; (或者是MIXED) 事務(wù)A 事務(wù)B begin; begin; update class_teacher set class_name='初三二班' where teacher_id=1; update class_teacher set class_name='初三三班' where teacher_id=1; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction commit; 為了防止并發(fā)過程中的修改沖突,事務(wù)A中MySQL給teacher_id=1的數(shù)據(jù)行加鎖,并一直不commit(釋放鎖),那么事務(wù)B也就一直拿不到該行鎖,wait直到超時(shí)。 這時(shí)我們要注意到,teacher_id是有索引的,如果是沒有索引的class_name呢?update class_teacher set teacher_id=3 where class_name = '初三一班'; 那么MySQL會(huì)給整張表的所有數(shù)據(jù)行的加行鎖。這里聽起來有點(diǎn)不可思議,但是當(dāng)sql運(yùn)行的過程中,MySQL并不知道哪些數(shù)據(jù)行是 class_name = '初三一班'的(沒有索引嘛),如果一個(gè)條件無法通過索引快速過濾,存儲(chǔ)引擎層面就會(huì)將所有記錄加鎖后返回,再由MySQL Server層進(jìn)行過濾。 但在實(shí)際使用過程當(dāng)中,MySQL做了一些改進(jìn),在MySQL Server過濾條件,發(fā)現(xiàn)不滿足后,會(huì)調(diào)用unlock_row方法,把不滿足條件的記錄釋放鎖 (違背了二段鎖協(xié)議的約束)。這樣做,保證了最后只會(huì)持有滿足條件記錄上的鎖,但是每條記錄的加鎖操作還是不能省略的??梢娂词故荕ySQL,為了效率也是會(huì)違反規(guī)范的。(參見《高性能MySQL》中文第三版p181) 這種情況同樣適用于MySQL的默認(rèn)隔離級(jí)別RR。所以對(duì)一個(gè)數(shù)據(jù)量很大的表做批量修改的時(shí)候,如果無法使用相應(yīng)的索引,MySQL Server過濾數(shù)據(jù)的的時(shí)候特別慢,就會(huì)出現(xiàn)雖然沒有修改某些行的數(shù)據(jù),但是它們還是被鎖住了的現(xiàn)象。 Repeatable Read(可重讀) 這是MySQL中InnoDB默認(rèn)的隔離級(jí)別。我們姑且分“讀”和“寫”兩個(gè)模塊來講解。 讀 讀就是可重讀,可重讀這個(gè)概念是一事務(wù)的多個(gè)實(shí)例在并發(fā)讀取數(shù)據(jù)時(shí),會(huì)看到同樣的數(shù)據(jù)行,有點(diǎn)抽象,我們來看一下效果。 RC(不可重讀)模式下的展現(xiàn) 事務(wù)A 事務(wù)B begin; begin; select id,class_name,teacher_id from class_teacher where teacher_id=1; id class_name teacher_id 1 初三二班 1 2 初三一班 1 update class_teacher set class_name='初三三班' where id=1; commit; select id,class_name,teacher_id from class_teacher where teacher_id=1; id class_name teacher_id 1 初三三班 1 2 初三一班 1 讀到了事務(wù)B修改的數(shù)據(jù),和第一次查詢的結(jié)果不一樣,是不可重讀的。 commit; 事務(wù)B修改id=1的數(shù)據(jù)提交之后,事務(wù)A同樣的查詢,后一次和前一次的結(jié)果不一樣,這就是不可重讀(重新讀取產(chǎn)生的結(jié)果不一樣)。這就很可能帶來一些問題,那么我們來看看在RR級(jí)別中MySQL的表現(xiàn): 事務(wù)A 事務(wù)B 事務(wù)C begin; begin; begin; select id,class_name,teacher_id from class_teacher where teacher_id=1; id class_name teacher_id 1 初三二班 1 2 初三一班 1 update class_teacher set class_name='初三三班' where id=1; commit; insert into class_teacher values (null,'初三三班',1); commit; select id,class_name,teacher_id from class_teacher where teacher_id=1; id class_name teacher_id 1 初三二班 1 2 初三一班 1 沒有讀到事務(wù)B修改的數(shù)據(jù),和第一次sql讀取的一樣,是可重復(fù)讀的。 沒有讀到事務(wù)C新添加的數(shù)據(jù)。 commit; 我們注意到,當(dāng)teacher_id=1時(shí),事務(wù)A先做了一次讀取,事務(wù)B中間修改了id=1的數(shù)據(jù),并commit之后,事務(wù)A第二次讀到的數(shù)據(jù)和第一次完全相同。所以說它是可重讀的。那么MySQL是怎么做到的呢?這里姑且賣個(gè)關(guān)子,我們往下看。 不可重復(fù)讀和幻讀的區(qū)別 很多人容易搞混不可重復(fù)讀和幻讀,確實(shí)這兩者有些相似。但不可重復(fù)讀重點(diǎn)在于update和delete,而幻讀的重點(diǎn)在于insert。 如果使用鎖機(jī)制來實(shí)現(xiàn)這兩種隔離級(jí)別,在可重復(fù)讀中,該sql第一次讀取到數(shù)據(jù)后,就將這些數(shù)據(jù)加鎖,其它事務(wù)無法修改這些數(shù)據(jù),就可以實(shí)現(xiàn)可重復(fù)讀了。但這種方法卻無法鎖住insert的數(shù)據(jù),所以當(dāng)事務(wù)A先前讀取了數(shù)據(jù),或者修改了全部數(shù)據(jù),事務(wù)B還是可以insert數(shù)據(jù)提交,這時(shí)事務(wù)A就會(huì)發(fā)現(xiàn)莫名其妙多了一條之前沒有的數(shù)據(jù),這就是幻讀,不能通過行鎖來避免。需要Serializable隔離級(jí)別 ,讀用讀鎖,寫用寫鎖,讀鎖和寫鎖互斥,這么做可以有效的避免幻讀、不可重復(fù)讀、臟讀等問題,但會(huì)極大的降低數(shù)據(jù)庫的并發(fā)能力。 所以說不可重復(fù)讀和幻讀最大的區(qū)別,就在于如何通過鎖機(jī)制來解決他們產(chǎn)生的問題。 上文說的,是使用悲觀鎖機(jī)制來處理這兩種問題,但是MySQL、ORACLE、PostgreSQL等成熟的數(shù)據(jù)庫,出于性能考慮,都是使用了以樂觀鎖為理論基礎(chǔ)的MVCC(多版本并發(fā)控制)來避免這兩種問題。 悲觀鎖和樂觀鎖 悲觀鎖 正如其名,它指的是對(duì)數(shù)據(jù)被外界(包括本系統(tǒng)當(dāng)前的其他事務(wù),以及來自外部系統(tǒng)的事務(wù)處理)修改持保守態(tài)度,因此,在整個(gè)數(shù)據(jù)處理過程中,將數(shù)據(jù)處于鎖定狀態(tài)。悲觀鎖的實(shí)現(xiàn),往往依靠數(shù)據(jù)庫提供的鎖機(jī)制(也只有數(shù)據(jù)庫層提供的鎖機(jī)制才能真正保證數(shù)據(jù)訪問的排他性,否則,即使在本系統(tǒng)中實(shí)現(xiàn)了加鎖機(jī)制,也無法保證外部系統(tǒng)不會(huì)修改數(shù)據(jù))。 在悲觀鎖的情況下,為了保證事務(wù)的隔離性,就需要一致性鎖定讀。讀取數(shù)據(jù)時(shí)給加鎖,其它事務(wù)無法修改這些數(shù)據(jù)。修改刪除數(shù)據(jù)時(shí)也要加鎖,其它事務(wù)無法讀取這些數(shù)據(jù)。 樂觀鎖 相對(duì)悲觀鎖而言,樂觀鎖機(jī)制采取了更加寬松的加鎖機(jī)制。悲觀鎖大多數(shù)情況下依靠數(shù)據(jù)庫的鎖機(jī)制實(shí)現(xiàn),以保證操作最大程度的獨(dú)占性。但隨之而來的就是數(shù)據(jù)庫性能的大量開銷,特別是對(duì)長(zhǎng)事務(wù)而言,這樣的開銷往往無法承受。 而樂觀鎖機(jī)制在一定程度上解決了這個(gè)問題。樂觀鎖,大多是基于數(shù)據(jù)版本( Version )記錄機(jī)制實(shí)現(xiàn)。何謂數(shù)據(jù)版本?即為數(shù)據(jù)增加一個(gè)版本標(biāo)識(shí),在基于數(shù)據(jù)庫表的版本解決方案中,一般是通過為數(shù)據(jù)庫表增加一個(gè) “version” 字段來實(shí)現(xiàn)。讀取出數(shù)據(jù)時(shí),將此版本號(hào)一同讀出,之后更新時(shí),對(duì)此版本號(hào)加一。此時(shí),將提交數(shù)據(jù)的版本數(shù)據(jù)與數(shù)據(jù)庫表對(duì)應(yīng)記錄的當(dāng)前版本信息進(jìn)行比對(duì),如果提交的數(shù)據(jù)版本號(hào)大于數(shù)據(jù)庫表當(dāng)前版本號(hào),則予以更新,否則認(rèn)為是過期數(shù)據(jù)。 要說明的是,MVCC的實(shí)現(xiàn)沒有固定的規(guī)范,每個(gè)數(shù)據(jù)庫都會(huì)有不同的實(shí)現(xiàn)方式,這里討論的是InnoDB的MVCC。 MVCC在MySQL的InnoDB中的實(shí)現(xiàn) 在InnoDB中,會(huì)在每行數(shù)據(jù)后添加兩個(gè)額外的隱藏的值來實(shí)現(xiàn)MVCC,這兩個(gè)值一個(gè)記錄這行數(shù)據(jù)何時(shí)被創(chuàng)建,另外一個(gè)記錄這行數(shù)據(jù)何時(shí)過期(或者被刪除)。 在實(shí)際操作中,存儲(chǔ)的并不是時(shí)間,而是事務(wù)的版本號(hào),每開啟一個(gè)新事務(wù),事務(wù)的版本號(hào)就會(huì)遞增。 在可重讀Repeatable reads事務(wù)隔離級(jí)別下: SELECT時(shí),讀取創(chuàng)建版本號(hào)<=當(dāng)前事務(wù)版本號(hào),刪除版本號(hào)為空或>當(dāng)前事務(wù)版本號(hào)。 INSERT時(shí),保存當(dāng)前事務(wù)版本號(hào)為行的創(chuàng)建版本號(hào) DELETE時(shí),保存當(dāng)前事務(wù)版本號(hào)為行的刪除版本號(hào) UPDATE時(shí),插入一條新紀(jì)錄,保存當(dāng)前事務(wù)版本號(hào)為行創(chuàng)建版本號(hào),同時(shí)保存當(dāng)前事務(wù)版本號(hào)到原來刪除的行 通過MVCC,雖然每行記錄都需要額外的存儲(chǔ)空間,更多的行檢查工作以及一些額外的維護(hù)工作,但可以減少鎖的使用,大多數(shù)讀操作都不用加鎖,讀數(shù)據(jù)操作很簡(jiǎn)單,性能很好,并且也能保證只會(huì)讀取到符合標(biāo)準(zhǔn)的行,也只鎖住必要行。 我們不管從數(shù)據(jù)庫方面的教課書中學(xué)到,還是從網(wǎng)絡(luò)上看到,大都是上文中事務(wù)的四種隔離級(jí)別這一模塊列出的意思,RR級(jí)別是可重復(fù)讀的,但無法解決幻讀,而只有在Serializable級(jí)別才能解決幻讀。于是我就加了一個(gè)事務(wù)C來展示效果。在事務(wù)C中添加了一條teacher_id=1的數(shù)據(jù)commit,RR級(jí)別中應(yīng)該會(huì)有幻讀現(xiàn)象,事務(wù)A在查詢teacher_id=1的數(shù)據(jù)時(shí)會(huì)讀到事務(wù)C新加的數(shù)據(jù)。但是測(cè)試后發(fā)現(xiàn),在MySQL中是不存在這種情況的,在事務(wù)C提交后,事務(wù)A還是不會(huì)讀到這條數(shù)據(jù)??梢娫贛ySQL的RR級(jí)別中,是解決了幻讀的讀問題的。參見下圖 讀問題解決了,根據(jù)MVCC的定義,并發(fā)提交數(shù)據(jù)時(shí)會(huì)出現(xiàn)沖突,那么沖突時(shí)如何解決呢?我們?cè)賮砜纯碔nnoDB中RR級(jí)別對(duì)于寫數(shù)據(jù)的處理。 “讀”與“讀”的區(qū)別 可能有讀者會(huì)疑惑,事務(wù)的隔離級(jí)別其實(shí)都是對(duì)于讀數(shù)據(jù)的定義,但到了這里,就被拆成了讀和寫兩個(gè)模塊來講解。這主要是因?yàn)镸ySQL中的讀,和事務(wù)隔離級(jí)別中的讀,是不一樣的。 我們且看,在RR級(jí)別中,通過MVCC機(jī)制,雖然讓數(shù)據(jù)變得可重復(fù)讀,但我們讀到的數(shù)據(jù)可能是歷史數(shù)據(jù),是不及時(shí)的數(shù)據(jù),不是數(shù)據(jù)庫當(dāng)前的數(shù)據(jù)!這在一些對(duì)于數(shù)據(jù)的時(shí)效特別敏感的業(yè)務(wù)中,就很可能出問題。 對(duì)于這種讀取歷史數(shù)據(jù)的方式,我們叫它快照讀 (snapshot read),而讀取數(shù)據(jù)庫當(dāng)前版本數(shù)據(jù)的方式,叫當(dāng)前讀 (current read)。很顯然,在MVCC中: 快照讀:就是select select * from table ....; 當(dāng)前讀:特殊的讀操作,插入/更新/刪除操作,屬于當(dāng)前讀,處理的都是當(dāng)前的數(shù)據(jù),需要加鎖。 select * from table where ? lock in share mode; select * from table where ? for update; insert; update ; delete; 事務(wù)的隔離級(jí)別實(shí)際上都是定義了當(dāng)前讀的級(jí)別,MySQL為了減少鎖處理(包括等待其它鎖)的時(shí)間,提升并發(fā)能力,引入了快照讀的概念,使得select不用加鎖。而update、insert這些“當(dāng)前讀”,就需要另外的模塊來解決了。 寫("當(dāng)前讀") 事務(wù)的隔離級(jí)別中雖然只定義了讀數(shù)據(jù)的要求,實(shí)際上這也可以說是寫數(shù)據(jù)的要求。上文的“讀”,實(shí)際是講的快照讀;而這里說的“寫”就是當(dāng)前讀了。 為了解決當(dāng)前讀中的幻讀問題,MySQL事務(wù)使用了Next-Key鎖。 Next-Key鎖 Next-Key鎖是行鎖和GAP(間隙鎖)的合并,行鎖上文已經(jīng)介紹了,接下來說下GAP間隙鎖。 行鎖可以防止不同事務(wù)版本的數(shù)據(jù)修改提交時(shí)造成數(shù)據(jù)沖突的情況。但如何避免別的事務(wù)插入數(shù)據(jù)就成了問題。我們可以看看RR級(jí)別和RC級(jí)別的對(duì)比 RC級(jí)別: 事務(wù)A 事務(wù)B begin; begin; select id,class_name,teacher_id from class_teacher where teacher_id=30; id class_name teacher_id 2 初三二班 30 update class_teacher set class_name='初三四班' where teacher_id=30; insert into class_teacher values (null,'初三二班',30); commit; select id,class_name,teacher_id from class_teacher where teacher_id=30; id class_name teacher_id 2 初三四班 30 10 初三二班 30 RR級(jí)別: 事務(wù)A 事務(wù)B begin; begin; select id,class_name,teacher_id from class_teacher where teacher_id=30; id class_name teacher_id 2 初三二班 30 update class_teacher set class_name='初三四班' where teacher_id=30; insert into class_teacher values (null,'初三二班',30); waiting.... select id,class_name,teacher_id from class_teacher where teacher_id=30; id class_name teacher_id 2 初三四班 30 commit; 事務(wù)Acommit后,事務(wù)B的insert執(zhí)行。 通過對(duì)比我們可以發(fā)現(xiàn),在RC級(jí)別中,事務(wù)A修改了所有teacher_id=30的數(shù)據(jù),但是當(dāng)事務(wù)Binsert進(jìn)新數(shù)據(jù)后,事務(wù)A發(fā)現(xiàn)莫名其妙多了一行teacher_id=30的數(shù)據(jù),而且沒有被之前的update語句所修改,這就是“當(dāng)前讀”的幻讀。 RR級(jí)別中,事務(wù)A在update后加鎖,事務(wù)B無法插入新數(shù)據(jù),這樣事務(wù)A在update前后讀的數(shù)據(jù)保持一致,避免了幻讀。這個(gè)鎖,就是Gap鎖。 MySQL是這么實(shí)現(xiàn)的: 在class_teacher這張表中,teacher_id是個(gè)索引,那么它就會(huì)維護(hù)一套B+樹的數(shù)據(jù)關(guān)系,為了簡(jiǎn)化,我們用鏈表結(jié)構(gòu)來表達(dá)(實(shí)際上是個(gè)樹形結(jié)構(gòu),但原理相同) 如圖所示,InnoDB使用的是聚集索引,teacher_id身為二級(jí)索引,就要維護(hù)一個(gè)索引字段和主鍵id的樹狀結(jié)構(gòu)(這里用鏈表形式表現(xiàn)),并保持順序排列。 Innodb將這段數(shù)據(jù)分成幾個(gè)個(gè)區(qū)間 (negative infinity, 5], (5,30], (30,positive infinity); update class_teacher set class_name='初三四班' where teacher_id=30; 不僅用行鎖,鎖住了相應(yīng)的數(shù)據(jù)行;同時(shí)也在兩邊的區(qū)間,(5,30]和(30,positive infinity),都加入了gap鎖。這樣事務(wù)B就無法在這個(gè)兩個(gè)區(qū)間insert進(jìn)新數(shù)據(jù)。 受限于這種實(shí)現(xiàn)方式,Innodb很多時(shí)候會(huì)鎖住不需要鎖的區(qū)間。如下所示: 事務(wù)A 事務(wù)B 事務(wù)C begin; begin; begin; select id,class_name,teacher_id from class_teacher; id class_name teacher_id 1 初三一班 5 2 初三二班 30 update class_teacher set class_name='初一一班' where teacher_id=20; insert into class_teacher values (null,'初三五班',10); waiting ..... insert into class_teacher values (null,'初三五班',40); commit; 事務(wù)A commit之后,這條語句才插入成功 commit; commit; update的teacher_id=20是在(5,30]區(qū)間,即使沒有修改任何數(shù)據(jù),Innodb也會(huì)在這個(gè)區(qū)間加gap鎖,而其它區(qū)間不會(huì)影響,事務(wù)C正常插入。 如果使用的是沒有索引的字段,比如update class_teacher set teacher_id=7 where class_name='初三八班(即使沒有匹配到任何數(shù)據(jù))',那么會(huì)給全表加入gap鎖。同時(shí),它不能像上文中行鎖一樣經(jīng)過MySQL Server過濾自動(dòng)解除不滿足條件的鎖,因?yàn)闆]有索引,則這些字段也就沒有排序,也就沒有區(qū)間。除非該事務(wù)提交,否則其它事務(wù)無法插入任何數(shù)據(jù)。 行鎖防止別的事務(wù)修改或刪除,GAP鎖防止別的事務(wù)新增,行鎖和GAP鎖結(jié)合形成的的Next-Key鎖共同解決了RR級(jí)別在寫數(shù)據(jù)時(shí)的幻讀問題。 Serializable 這個(gè)級(jí)別很簡(jiǎn)單,讀加共享鎖,寫加排他鎖,讀寫互斥。使用的悲觀鎖的理論,實(shí)現(xiàn)簡(jiǎn)單,數(shù)據(jù)更加安全,但是并發(fā)能力非常差。如果你的業(yè)務(wù)并發(fā)的特別少或者沒有并發(fā),同時(shí)又要求數(shù)據(jù)及時(shí)可靠的話,可以使用這種模式。 這里要吐槽一句,不要看到select就說不會(huì)加鎖了,在Serializable這個(gè)級(jí)別,還是會(huì)加鎖的!

    四、正確理解MYSQL的幻讀

    一、定義

    1、幻讀MYSQL官方叫法是Phantom Rows,意為鬼影行或者幻影行,請(qǐng)看官方定義:

    The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a [ SELECT ] is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row.

    翻譯一下:

    所謂的幻影行問題是指,在同一個(gè)事務(wù)中,同樣的查詢語句執(zhí)行多次,得到了不同的行結(jié)果集。

    例如,如果同一個(gè)SELECT語句執(zhí)行了兩次,第二次執(zhí)行的時(shí)候比第一次執(zhí)行時(shí)多出一行,則該行就是所謂的幻影行。

    2、幻讀與不可重復(fù)讀的區(qū)別

    從官方的定義來看,幻讀的定義側(cè)重于多條記錄,就是記錄條數(shù)的變化,而不可重復(fù)讀側(cè)重于單條記錄數(shù)據(jù)的變化,這樣區(qū)分原因在于解決幻讀需要范圍鎖,解決不可重復(fù)讀只需要單條記錄加鎖

    二、InnoDB的REPEATABLE READ級(jí)別

    InnoDB支持由SQL1992標(biāo)準(zhǔn)描述的所有四個(gè)事務(wù)隔離級(jí)別,默認(rèn)隔離級(jí)別是 REPEATABLE READ。

    1、快照讀:

    在RR模式下,第一次讀取會(huì)建立快照,后續(xù)查詢會(huì)讀取快照。

    這意味著,如果在同一事務(wù)中發(fā)出多個(gè)普通[ SELECT ](非鎖定)語句,則這些 [ SELECT ]語句的結(jié)果也是一致的。

    2、[locking reads](鎖定讀取,又叫當(dāng)前讀)

    [ SELECT ]語句中使用 FOR UPDATE 或 FOR SHARE

    3、行鎖

    在RR模式下,使用當(dāng)前讀以及 [ UPDATE ]和 [ DELETE ]語句會(huì)對(duì)數(shù)據(jù)記錄加行鎖,鎖定范圍取決于該語句使用的是具有唯一搜索條件的唯一索引還是范圍類型搜索條件。

    三、InnoDB的READ COMMITTED級(jí)別

    1、在RC模式下,每次讀取都會(huì)刷新快照,因此不能保證可重復(fù)讀

    2、在RC模式下,使用當(dāng)前讀以及 [ UPDATE ]和 [ DELETE ]語句會(huì)對(duì)數(shù)據(jù)記錄加行鎖,但是不會(huì)加范圍鎖,間隙鎖定僅用于外鍵約束檢查和重復(fù)鍵檢查。

    3、由于禁用了間隙鎖定,因此可能會(huì)產(chǎn)生幻影行問題,因?yàn)槠渌麜?huì)話可以在間隙中插入新行。

    4、 對(duì)于[ UPDATE ]或 [ DELETE ]語句, InnoDB 僅對(duì)其更新或刪除的行持有鎖。MySQL評(píng)估 WHERE 條件后,將釋放不匹配行的記錄鎖 。這大大降低了死鎖的可能性,但是仍然可以發(fā)生。

    5、對(duì)于[ UPDATE ]語句,如果某行已被鎖定,則 InnoDB 執(zhí)行“半一致”讀取,將最新提交版本的數(shù)據(jù)返回給MySQL,以便MySQL可以確定該行是否符合 WHERE 條件。如果該行匹配(必須更新),則MySQL會(huì)再次讀取該行,這一次 InnoDB 會(huì)將其鎖定或等待獲取鎖。

    6、注意

    從MySQL 8.0.22開始,DML操作(增刪改,通過聯(lián)接列表或子查詢)從MySQL授權(quán)表中讀取數(shù)據(jù),但不對(duì)其進(jìn)行修改,無論隔離級(jí)別如何,都不會(huì)在MySQL授權(quán)表上獲得讀取鎖。

    有關(guān)更多信息,請(qǐng)參見 Grant Table Concurrency 。

    四、樂觀鎖與悲觀鎖

    1、樂觀鎖

    在UPDATE的WHERE子句中加入版本信息來確定修改是否生效

    使用樂觀鎖時(shí)仍然需要非常謹(jǐn)慎,因?yàn)镽R是可重復(fù)讀的,在UPDATE之前讀取版本號(hào),應(yīng)該使用[當(dāng)前讀],不能使用[快照讀]

    2、悲觀鎖

    在UPDATE執(zhí)行前,SELECT后面加上FOR UPDATE來給記錄加鎖,保證記錄在UPDATE前不被修改。SELECT ... FOR UPDATE是加上了X鎖,也可以通過SELECT ... LOCK IN SHARE MODE加上S鎖,來防止其他事務(wù)對(duì)該行的修改。

    3、無論是樂觀鎖還是悲觀鎖,使用的思想都是一致的,那就是當(dāng)前讀。樂觀鎖利用當(dāng)前讀判斷是否是最新版本,悲觀鎖利用當(dāng)前讀鎖定行。

    五、總結(jié)

    1、RC級(jí)別沒有范圍鎖一定會(huì)導(dǎo)致不可重復(fù)讀和幻影行

    2、RR級(jí)別安全性更高,實(shí)現(xiàn)可重復(fù)讀的方式為快照,如果需要最新數(shù)據(jù)可以選擇[當(dāng)前讀],因此RR級(jí)別是首選

    3、不論RR還是RC級(jí)別,增、刪、改的操作都會(huì)進(jìn)行一次[當(dāng)前讀]操作,以此獲取最新版本的數(shù)據(jù),并檢測(cè)是否有重復(fù)的索引。

    4、RR級(jí)別下,當(dāng)前事務(wù)如果未發(fā)生更新操作(增刪改),快照版本會(huì)保持不變,多次查詢讀取的快照是同一個(gè)

    5、RR級(jí)別下,當(dāng)前事務(wù)如果發(fā)生更新(增刪改),會(huì)刷新快照,會(huì)導(dǎo)致不可重復(fù)讀和幻影行

    6、RR級(jí)別下,使用當(dāng)前讀,會(huì)刷新快照,會(huì)導(dǎo)致不可重復(fù)讀和幻影行

    7、RR級(jí)別下,可以通過提交當(dāng)前事務(wù)并在此之后發(fā)出新查詢來為查詢獲取更新的快照。

    8、RR級(jí)別可以部分解決不可重復(fù)讀和幻讀問題

    9、其實(shí)問題的關(guān)鍵是你的業(yè)務(wù)邏輯需要可重復(fù)讀還是最新數(shù)據(jù)

    以上就是關(guān)于快照讀會(huì)加鎖嗎相關(guān)問題的回答。希望能幫到你,如有更多相關(guān)問題,您也可以聯(lián)系我們的客服進(jìn)行咨詢,客服也會(huì)為您講解更多精彩的知識(shí)和內(nèi)容。


    推薦閱讀:

    磁盤快照是什么意思(磁盤快照任務(wù)可以禁止嗎)

    蘋果11屏幕快照怎么弄(蘋果11屏幕快照怎么弄出來)

    vm快照刪除磁盤過滿(vm刪除快照是否可以釋放空間)

    負(fù)責(zé)拍視頻的人叫什么(負(fù)責(zé)拍視頻的人叫什么名字)

    杭州四大垃圾天坑(杭州四大垃圾天坑是哪四個(gè))