-
當(dāng)前位置:首頁(yè) > 創(chuàng)意學(xué)院 > 技術(shù) > 專(zhuān)題列表 > 正文
快照讀和當(dāng)前讀(快照讀和當(dāng)前讀是mvcc技術(shù)嗎)
大家好!今天讓創(chuàng)意嶺的小編來(lái)大家介紹下關(guān)于快照讀和當(dāng)前讀的問(wèn)題,以下是小編對(duì)此問(wèn)題的歸納整理,讓我們一起來(lái)看看吧。
開(kāi)始之前先推薦一個(gè)非常厲害的Ai人工智能工具,一鍵生成原創(chuàng)文章、方案、文案、工作計(jì)劃、工作報(bào)告、論文、代碼、作文、做題和對(duì)話答疑等等
只需要輸入關(guān)鍵詞,就能返回你想要的內(nèi)容,越精準(zhǔn),寫(xiě)出的就越詳細(xì),有微信小程序端、在線網(wǎng)頁(yè)版、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
本文目錄:
一、MySQL innodb引擎深入講解
表空間(ibd文件),一個(gè)MySQL實(shí)例可以對(duì)應(yīng)多個(gè)表空間,用于存儲(chǔ)記錄,索引等數(shù)據(jù)。
段,分為數(shù)據(jù)段、索引段、回滾段,innodb是索引組織表,數(shù)據(jù)段就是B+Tree的葉子節(jié)點(diǎn),索引段為非葉子節(jié)點(diǎn),段用來(lái)管理多個(gè)區(qū)。
區(qū),表空間的單元結(jié)構(gòu),每個(gè)區(qū)的大小為1M,默認(rèn)情況下,innodb存儲(chǔ)引擎頁(yè)大小為16K,即一個(gè)區(qū)中一共有64個(gè)連續(xù)的頁(yè)。
頁(yè),是innodb存儲(chǔ)引擎磁盤(pán)管理的最小單元,每個(gè)頁(yè)的大小為16K,為了保證頁(yè)的連續(xù)性,innodb存儲(chǔ)引擎每次從磁盤(pán)申請(qǐng)4~5個(gè)區(qū)。
行,innodb存儲(chǔ)引擎數(shù)據(jù)是按行進(jìn)行存儲(chǔ)的。Trx_id 最后一次事務(wù)操作的id、roll_pointer滾動(dòng)指針。
i nnodb的內(nèi)存結(jié)構(gòu) ,由Buffer Pool、Change Buffer和Log Buffer組成。
Buffer Pool : 緩沖池是主內(nèi)存中的一個(gè)區(qū)域,里面可以緩存磁盤(pán)上經(jīng)常操作的真實(shí)數(shù)據(jù),在執(zhí)行增刪改查操作時(shí),先操作緩沖池中的數(shù)據(jù)(若緩沖池么有數(shù)據(jù),則從磁盤(pán)加載并緩存),然后再以一定頻率刷新磁盤(pán),從而減少磁盤(pán)IO,加快處理速度。
緩沖池以page頁(yè)為單位,底層采用鏈表數(shù)據(jù)結(jié)構(gòu)管理page,根據(jù)狀態(tài),將page分為三種類(lèi)型:
1、free page 即空閑page,未被使用。
2、clean page 被使用page,數(shù)據(jù)沒(méi)有被修改過(guò)。
3、dirty page 臟頁(yè),被使用page,數(shù)據(jù)被修改過(guò),這個(gè)page當(dāng)中的數(shù)據(jù)和磁盤(pán)當(dāng)中的數(shù)據(jù) 不一致。說(shuō)得簡(jiǎn)單點(diǎn)就是緩沖池中的數(shù)據(jù)改了,磁盤(pán)中的沒(méi)改,因?yàn)檫€沒(méi)刷寫(xiě)到磁盤(pán)。
Change Buffer :更改緩沖區(qū)(針對(duì)于非唯一二級(jí)索引頁(yè)),在執(zhí)行DML語(yǔ)句時(shí),如果這些數(shù)據(jù)page沒(méi)有在Buffer Pool中,不會(huì)直接操作磁盤(pán),而會(huì)將數(shù)據(jù)變更存在更改緩沖區(qū)Change Buffer中,在未來(lái)數(shù)據(jù)被讀取時(shí)。再將數(shù)據(jù)合并恢復(fù)到Buffer Pool中,再將合并后的數(shù)據(jù)刷新到磁盤(pán)中。
二級(jí)索引通常是非唯一的,并且以相對(duì)隨機(jī)的順序插入二級(jí)索引頁(yè),同樣,刪除和更新可能會(huì)影響索引樹(shù)中不相鄰的二級(jí)索引頁(yè)。如果每一次都操作磁盤(pán),會(huì)造成大量磁盤(pán)IO,有了Change Buffer之后,我們可以在緩沖池中進(jìn)行合并處理,減少磁盤(pán)IO。
Adaptive Hash Index: 自適應(yīng)hash索引,用于優(yōu)化對(duì)Buffer Pool數(shù)據(jù)的查詢(xún),InnoDB存儲(chǔ)引擎會(huì)監(jiān)控對(duì)表上各索引頁(yè)的查詢(xún),如果觀察到hash索引可以提升速度,則建立hash索引,稱(chēng)之為自適應(yīng)hash索引。無(wú)需人工干預(yù),系統(tǒng)根據(jù)情況自動(dòng)完成。
參數(shù):innodb_adaptive_hash_index
Log Buffer: 日志緩沖區(qū),用來(lái)保存要寫(xiě)入到磁盤(pán)中的log日志數(shù)據(jù)(redo log、undo log),默認(rèn)大小為16M,日志緩沖區(qū)的日志會(huì)定期刷新到磁盤(pán)中,如果需要更新,插入或刪除許多行的事務(wù),增加日志緩沖區(qū)的大小可以節(jié)省磁盤(pán)IO。
參數(shù): innodb_log_buffer_size 緩沖區(qū)大小
innodb_flush_log_at_trx_commit 日志刷新到磁盤(pán)時(shí)機(jī)
innodb_flush_log_at_trx_commit=1 表示日志在每次事務(wù)提交時(shí)寫(xiě)入并刷新到磁盤(pán)
2 表示日志在每次事務(wù)提交后寫(xiě)入,并每秒刷新到磁盤(pán)一次
0 表示每秒將日志寫(xiě)入并刷新到磁盤(pán)一次。
InnoDB 的磁盤(pán)結(jié)構(gòu),由系統(tǒng)表空間(ibdata1),獨(dú)立表空間(*.ibd),通用表空間,撤銷(xiāo)表空間(undo tablespaces), 臨時(shí)表空間(Temporary Tablespaces), 雙寫(xiě)緩沖區(qū)(Doublewrite Buffer files), 重做日志(Redo Log).
系統(tǒng)表空間(ibdata1): 系統(tǒng)表空間是更改緩沖區(qū)的存儲(chǔ)區(qū)域,如果表是在系統(tǒng)表空間而不是每個(gè)表文件或者通用表空間中創(chuàng)建的,它也可能包含表和索引數(shù)據(jù)。
參數(shù)為: innodb_data_file_path
獨(dú)立表空間(*.ibd): 每個(gè)表的文件表空間包含單個(gè)innodb表的數(shù)據(jù)和索引,并存儲(chǔ)在文件系 統(tǒng)上的單個(gè)數(shù)據(jù)文件中。 參數(shù): innodb_file_per_table
通用表空間: 需要通過(guò)create tablespace 語(yǔ)法創(chuàng)建,創(chuàng)建表時(shí) 可以指定該表空間。
create tablespace xxx add datafile 'file_name' engine=engine_name
create table table_name .... tablespace xxx
撤銷(xiāo)表空間(undo tablespaces): MySQL實(shí)例在初始化時(shí)會(huì)自動(dòng)創(chuàng)建兩個(gè)默認(rèn)的undo表空間(初始大小16K,undo_001,undo_002),用于存儲(chǔ)undo log 日志
臨時(shí)表空間(Temporary Tablespaces): innodb使用會(huì)話臨時(shí)表空和全局表空間,存儲(chǔ)用 戶創(chuàng)建的臨時(shí)表等數(shù)據(jù)。
雙寫(xiě)緩沖區(qū)(Doublewrite Buffer files): innodb引擎將數(shù)據(jù)頁(yè)從Buffer Pool刷新到磁盤(pán)前,先將數(shù)據(jù)頁(yè)寫(xiě)入緩沖區(qū)文件中,便于系統(tǒng)異常時(shí)恢復(fù)數(shù)據(jù)。
重做日志(Redo Log): 是用來(lái)實(shí)現(xiàn)事務(wù)的持久性,該日志文件由兩部分組成,重做日志緩沖區(qū)(redo log buffer)以及重做日志文件(redo log),前者是在內(nèi)存中,后者在磁盤(pán)中,當(dāng)事務(wù)提交之后會(huì)把修改信息都會(huì)存儲(chǔ)到該日志中,用于在刷新臟頁(yè)到磁盤(pán)時(shí),發(fā)送錯(cuò)誤時(shí),進(jìn)行數(shù)據(jù)恢復(fù)使用。以循環(huán)方式寫(xiě)入重做日志文件,涉及兩個(gè)文件ib_logfile0,ib_logfile1。
那內(nèi)存結(jié)構(gòu)中的數(shù)據(jù)是如何刷新到磁盤(pán)中的? 在MySQL中有4個(gè)線程負(fù)責(zé)刷新日志到磁盤(pán)。
1、Master Thread, mysql核心后臺(tái)線程,負(fù)責(zé)調(diào)度其它線程,還負(fù)責(zé)將緩沖池中的數(shù)據(jù)異 步刷新到磁盤(pán)中,保持?jǐn)?shù)據(jù)的一致性,還包括臟頁(yè)的刷新,合并插入緩沖、undo頁(yè)的回 收。
2、IO Thread,在innodb存儲(chǔ)引擎中大量使用了AIO來(lái)處理IO請(qǐng)求,這樣可以極大地提高數(shù) 據(jù)庫(kù)的性能,而IO Thead主要負(fù)責(zé)這些IO請(qǐng)求的回調(diào)。
4個(gè)讀線程 Read thread負(fù)責(zé)讀操作
4個(gè)寫(xiě)線程write thread負(fù)責(zé)寫(xiě)操作
1個(gè)Log thread線程 負(fù)責(zé)將日志緩沖區(qū)刷新到磁盤(pán)
1個(gè)insert buffer線程 負(fù)責(zé)將寫(xiě)入緩沖區(qū)內(nèi)容刷新到磁盤(pán)
3、Purge Thread,主要用于回收事務(wù)已經(jīng)提交了的undo log,在事務(wù)提交之后,undo log 可能不用了,就用它來(lái)回收。
4、Page Cleaner Thread, 協(xié)助Master Thread 刷新臟頁(yè)到磁盤(pán)的線程,它可以減輕主線程 的壓力,減少阻塞。
事務(wù)就是一組操作的集合,它是一個(gè)不可分割的工作單位,事務(wù)會(huì)把所有的操作作為一個(gè)整體一起向系統(tǒng)提交或撤銷(xiāo)操作請(qǐng)求,即這些操作要么同時(shí)成功,要么同時(shí)失效。
事務(wù)的4大特性分為:
如何保證事務(wù)的4大特性,原子性,一致性和持久性是由innodb存儲(chǔ)引擎底層的兩份日志來(lái)保證的,分別是redo log和undo log。對(duì)于隔離性是由鎖機(jī)制和MVCC(多版本并發(fā)控制)來(lái)實(shí)現(xiàn)的。
redo log,稱(chēng)為重做日志,記錄的是事務(wù)提交時(shí)數(shù)據(jù)頁(yè)的物理修改,是用來(lái)實(shí)現(xiàn)事務(wù)的持久性。該日志文件由兩部分組成: 重做日志緩沖redo log buffer及重做日志文件redo log file,前者是在內(nèi)存中,后者是在磁盤(pán)中,當(dāng)事務(wù)提交之后會(huì)把所有修改信息都存到該日志文件中,用于在刷新臟頁(yè)到磁盤(pán),發(fā)送錯(cuò)誤時(shí),進(jìn)行數(shù)據(jù)的恢復(fù)使用,從而保證事務(wù)的持久性。
具體的操作流程是:
1、客戶端發(fā)起事務(wù)操作,包含多條DML語(yǔ)句。首先去innodb中的buffer pool中的數(shù)據(jù)頁(yè)去查找有沒(méi)有我們要更新的這些數(shù)據(jù),如果沒(méi)有則通過(guò)后臺(tái)線程從磁盤(pán)中加載到buffer pool對(duì)應(yīng)的數(shù)據(jù)頁(yè)中,然后就可以在緩沖池中進(jìn)行數(shù)據(jù)操作了。
2、此時(shí)緩沖池中的數(shù)據(jù)頁(yè)發(fā)生了變更,還沒(méi)刷寫(xiě)到磁盤(pán),這個(gè)數(shù)據(jù)頁(yè)稱(chēng)為臟頁(yè)。臟頁(yè)不是實(shí)時(shí)刷新到磁盤(pán)的,而是根據(jù)你配置的刷寫(xiě)策略進(jìn)行刷寫(xiě)到磁盤(pán)的(innodb_flush_log_at_trx_commit,0,1,2三個(gè)值)。如果臟頁(yè)在往磁盤(pán)刷新的時(shí)候出現(xiàn)了故障,會(huì)丟失數(shù)據(jù),導(dǎo)致事務(wù)的持久性得不到保證。為了避免這種現(xiàn)象,當(dāng)對(duì)緩沖池中的數(shù)據(jù)進(jìn)行增刪改操作時(shí),會(huì)把增刪改記錄到redo log buffer當(dāng)中,redo log buffer會(huì)把數(shù)據(jù)頁(yè)的物理變更持久化到磁盤(pán)文件中(ib_logfile0/ib_logfile1)。如果臟頁(yè)刷新失敗,就可以通過(guò)這兩個(gè)日志文件進(jìn)行恢復(fù)。
undo log,它是用來(lái)解決事務(wù)的原子性的,也稱(chēng)為回滾日志。用于記錄數(shù)據(jù)被修改前的信息,作用包括:提供回滾和MVCC多版本并發(fā)控制。
undo log和redo log的記錄物理日志不一樣,它是邏輯日志??梢哉J(rèn)為當(dāng)delete一條記錄時(shí),undo log中會(huì)記錄一條對(duì)應(yīng)的insert記錄,當(dāng)update一條記錄時(shí),它記錄一條對(duì)應(yīng)相反的update記錄,當(dāng)執(zhí)行rollback時(shí),就可以從undo log中的邏輯記錄讀取到相應(yīng)的內(nèi)容并進(jìn)行回滾。
undo log銷(xiāo)毀: undo log 在事務(wù)執(zhí)行時(shí)產(chǎn)生,事務(wù)提交時(shí),并不會(huì)立即刪除undo log,因?yàn)檫@些日子可能用于MVCC。
undo log存儲(chǔ): undo log 采用段的方式進(jìn)行管理和記錄,存放在前面介紹的rollback segment回滾段中,內(nèi)部包含1024個(gè)undo log segment。
mvcc(multi-Version Concurrency Control),多版本并發(fā)控制,指維護(hù)一個(gè)數(shù)據(jù)的多個(gè)版本,使得讀寫(xiě)操作沒(méi)有沖突,快照讀為MySQL實(shí)現(xiàn)MVCC提供了一個(gè)非阻塞讀功能,MVCC的具體實(shí)現(xiàn),還需要依賴(lài)于數(shù)據(jù)庫(kù)記錄中的三個(gè)隱式字段,undo log日志、readView。
read committed 每次select 都生成一個(gè)快照讀
repeatable read 開(kāi)啟事務(wù)后第一個(gè)select語(yǔ)句才是快照讀的地方
serializable 快照讀會(huì)退化為當(dāng)前讀。
mvcc的實(shí)現(xiàn)原理
DB_TRX_ID: 最近修改事務(wù)ID,記錄插入這條記錄或最后一次修改該記錄的事務(wù)ID
DB_ROLL_PTR: 回滾指針,指向這條記錄的上一個(gè)版本,用于配合undo log,指向上一個(gè) 版本
DB_ROW_ID: 隱藏主鍵,如果表結(jié)構(gòu)沒(méi)有指定主鍵,將會(huì)生成該隱藏字段。
m_ids當(dāng)前活躍的事務(wù)ID集合
min_trx_id: 最小活躍事務(wù)id
max_trx_id: 預(yù)分配事務(wù)ID,當(dāng)前最大事務(wù)id+1,因?yàn)槭聞?wù)id是自增的
creator_trx_id: ReadView創(chuàng)建者的事務(wù)ID
版本鏈數(shù)據(jù)訪問(wèn)規(guī)則:
trx_id: 表示當(dāng)前的事務(wù)ID
1、trx_id == creator_trx_id? 可以訪問(wèn)讀版本-->成立的話,說(shuō)明數(shù)據(jù)是當(dāng)前這個(gè)事務(wù)更改的
2、trx_id 成立,說(shuō)明數(shù)據(jù)已經(jīng)提交了。
3、trx_id>max_trx_id?不可用訪問(wèn)讀版本-> 成立的話,說(shuō)明該事務(wù)是在ReadView生成后才開(kāi)啟的。
4、min_trx_id
二、MySQL之快照讀
快照讀 即: snapshot read ,官方叫法是: Consistent Nonlocking Reads ,即: 一致性非鎖定讀 ,官方的解釋是:
即:
即 快照讀 的問(wèn)題在于:在同一個(gè)事務(wù)中,能夠讀取到之前提交的數(shù)據(jù)。表現(xiàn)為:
字面意思:在事務(wù)中,為查詢(xún)創(chuàng)建的快照,并不適用與 DML 語(yǔ)句。
也就是說(shuō):如果事務(wù) A 開(kāi)始時(shí)創(chuàng)建的快照,查詢(xún)不到數(shù)據(jù) col1=1 ,但此時(shí)事務(wù) B 剛剛提交 insert col1=1 和 insert col1=1 ,此時(shí)如果事務(wù) A 執(zhí)行, delete col1=1 ,是能將事務(wù) B 生成的數(shù)據(jù)刪除的。
字面意思:即使事務(wù) A 的快照是在事務(wù) B 提交之前創(chuàng)建的,但事務(wù) A 也只有在事務(wù) A 和事務(wù) B 都提交后,才能看到事務(wù) B 新增的數(shù)據(jù)。
三、事務(wù)/forupdate會(huì)鎖表嗎
如果條件中確定使用了索引,則會(huì)鎖該行,如沒(méi)有索引或沒(méi)使用到索引,則會(huì)鎖表。
是否使用到索引,利用trace工具判斷,這里不做敘述。
建議用主鍵做索引驗(yàn)證
先打開(kāi)兩個(gè)連接session
注:session1此時(shí)未提交
session2修改當(dāng)前數(shù)據(jù)被阻塞,因?yàn)樾薷膶儆谔厥庾x這里會(huì)使用當(dāng)前讀,修改阻塞說(shuō)明session1事務(wù)加了鎖。但此時(shí)不能判斷是行鎖還是表鎖。
將session1提交后,session2隨即成功提交,這里阻塞了20s左右
session2修改其他數(shù)據(jù)正常執(zhí)行,說(shuō)明鎖的是行鎖,不是表鎖。
session2查詢(xún)操作正常,因?yàn)槠胀ㄗx時(shí)由于mysql的mvcc機(jī)制會(huì)使用的是快照度,所以不會(huì)阻塞。
mvcc當(dāng)前讀與快照讀及其相關(guān)原理這里不做敘述
注:session1此時(shí)未提交
session2修改當(dāng)前數(shù)據(jù)被阻塞,因?yàn)樾薷膶儆谔厥庾x這里會(huì)使用當(dāng)前讀,修改阻塞說(shuō)明session1事務(wù)加了鎖。但此時(shí)不能判斷是行鎖還是表鎖。
將session1提交后,session2隨即成功提交,這里阻塞了20s左右
session2修改其他數(shù)據(jù)被阻塞,說(shuō)明鎖的是表鎖,不是行鎖。
將session1提交后,session2隨即成功提交,這里阻塞了20s左右
session2查詢(xún)操作正常,因?yàn)槠胀ㄗx時(shí)由于mysql的mvcc機(jī)制會(huì)使用的是快照度,所以不會(huì)阻塞。
mvcc當(dāng)前讀與快照讀及其相關(guān)原理這里不做敘述
注:for update只有在begin commit,也就是事務(wù)之間才會(huì)起作用,如果發(fā)現(xiàn)兩個(gè)session都成功對(duì)一條數(shù)據(jù)加鎖成功,注意看下是否有沒(méi)有開(kāi)啟事務(wù)。
先打開(kāi)兩個(gè)連接session
注:session1此時(shí)未提交
由于session1加了鎖,session2查詢(xún)加鎖被阻塞,但此時(shí)不能判斷是行鎖還是表鎖。
將session1提交后,session2隨即成功加鎖,這里阻塞了20s左右
session2加鎖其他數(shù)據(jù)正常執(zhí)行,說(shuō)明鎖的是行鎖,不是表鎖。
session2修改當(dāng)前數(shù)據(jù)被阻塞
session2修改其他數(shù)據(jù)正常執(zhí)行
注:session1此時(shí)未提交
由于session1加了鎖,session2查詢(xún)加鎖被阻塞,但此時(shí)不能判斷是行鎖還是表鎖。
將session1提交后,session2隨即加鎖成功,這里阻塞了20s左右
session2加鎖其他數(shù)據(jù)也被阻塞,說(shuō)明鎖的是表鎖,不是行鎖。
將session1提交后,session2隨即加鎖成功,這里阻塞了20s左右
session2修改當(dāng)前數(shù)據(jù)被阻塞,但此時(shí)不能判斷是行鎖還是表鎖。
將session1提交后,session2隨即修改成功,這里阻塞了20s左右
session2修改其他數(shù)據(jù)同樣被阻塞,說(shuō)明鎖的是表鎖,不是行鎖。
將session1提交后,session2隨即修改成功,這里阻塞了20s左右
四、事務(wù)的隔離級(jí)別 全部都是共享鎖嗎
前言: 我們都知道事務(wù)的幾種性質(zhì),數(shù)據(jù)庫(kù)為了維護(hù)這些性質(zhì),尤其是一致性和隔離性,一般使用加鎖這種方式。同時(shí)數(shù)據(jù)庫(kù)又是個(gè)高并發(fā)的應(yīng)用,同一時(shí)間會(huì)有大量的并發(fā)訪問(wèn),如果加鎖過(guò)度,會(huì)極大的降低并發(fā)處理能力。所以對(duì)于加鎖的處理,可以說(shuō)就是數(shù)據(jù)庫(kù)對(duì)于事務(wù)處理的精髓所在。這里通過(guò)分析MySQL中InnoDB引擎的加鎖機(jī)制,來(lái)拋磚引玉,讓讀者更好的理解,在事務(wù)處理中數(shù)據(jù)庫(kù)到底做了什么。 一次封鎖or兩段鎖? 因?yàn)橛写罅康牟l(fā)訪問(wèn),為了預(yù)防死鎖,一般應(yīng)用中推薦使用一次封鎖法,就是在方法的開(kāi)始階段,已經(jīng)預(yù)先知道會(huì)用到哪些數(shù)據(jù),然后全部鎖住,在方法運(yùn)行之后,再全部解鎖。這種方式可以有效的避免循環(huán)死鎖,但在數(shù)據(jù)庫(kù)中卻不適用,因?yàn)樵谑聞?wù)開(kāi)始階段,數(shù)據(jù)庫(kù)并不知道會(huì)用到哪些數(shù)據(jù)。 數(shù)據(jù)庫(kù)遵循的是兩段鎖協(xié)議,將事務(wù)分成兩個(gè)階段,加鎖階段和解鎖階段(所以叫兩段鎖) 加鎖階段:在該階段可以進(jìn)行加鎖操作。在對(duì)任何數(shù)據(jù)進(jìn)行讀操作之前要申請(qǐng)并獲得S鎖(共享鎖,其它事務(wù)可以繼續(xù)加共享鎖,但不能加排它鎖),在進(jìn)行寫(xiě)操作之前要申請(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)的鎖 這種方式雖然無(wú)法避免死鎖,但是兩段鎖協(xié)議可以保證事務(wù)的并發(fā)調(diào)度是串行化(串行化很重要,尤其是在數(shù)據(jù)恢復(fù)和備份的時(shí)候)的。 事務(wù)中的加鎖方式 事務(wù)的四種隔離級(jí)別 在數(shù)據(jù)庫(kù)操作中,為了有效保證并發(fā)讀取數(shù)據(jù)的正確性,提出的事務(wù)隔離級(jí)別。我們的數(shù)據(jù)庫(kù)鎖,也是為了構(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ù)庫(kù)默認(rèn)都是該級(jí)別 (不重復(fù)讀) 可重復(fù)讀(Repeated Read):可重復(fù)讀。在同一個(gè)事務(wù)內(nèi)的查詢(xún)都是事務(wù)開(kāi)始時(shí)刻一致的,InnoDB默認(rèn)級(jí)別。在SQL標(biāo)準(zhǔn)中,該隔離級(jí)別消除了不可重復(fù)讀,但是還存在幻象讀 串行讀(Serializable):完全串行化的讀,每次讀都需要獲得表級(jí)共享鎖,讀寫(xiě)相互都會(huì)阻塞 Read Uncommitted這種級(jí)別,數(shù)據(jù)庫(kù)一般都不會(huì)用,而且任何操作都不會(huì)加鎖,這里就不討論了。 MySQL中鎖的種類(lèi) MySQL中鎖的種類(lèi)很多,有常見(jiàn)的表鎖和行鎖,也有新加入的Metadata Lock等等,表鎖是對(duì)一整張表加鎖,雖然可分為讀鎖和寫(xiě)鎖,但畢竟是鎖住整張表,會(huì)導(dǎo)致并發(fā)能力下降,一般是做ddl處理時(shí)使用。 行鎖則是鎖住數(shù)據(jù)行,這種加鎖方法比較復(fù)雜,但是由于只鎖住有限的數(shù)據(jù),對(duì)于其它數(shù)據(jù)不加限制,所以并發(fā)能力強(qiáng),MySQL一般都是用行鎖來(lái)處理并發(fā)事務(wù)。這里主要討論的也就是行鎖。 Read Committed(讀取提交內(nèi)容) 在RC級(jí)別中,數(shù)據(jù)的讀取都是不加鎖的,但是數(shù)據(jù)的寫(xiě)入、修改和刪除是需要加鎖的。效果如下 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開(kāi)啟成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ā)過(guò)程中的修改沖突,事務(wù)A中MySQL給teacher_id=1的數(shù)據(jù)行加鎖,并一直不commit(釋放鎖),那么事務(wù)B也就一直拿不到該行鎖,wait直到超時(shí)。 這時(shí)我們要注意到,teacher_id是有索引的,如果是沒(méi)有索引的class_name呢?update class_teacher set teacher_id=3 where class_name = '初三一班'; 那么MySQL會(huì)給整張表的所有數(shù)據(jù)行的加行鎖。這里聽(tīng)起來(lái)有點(diǎn)不可思議,但是當(dāng)sql運(yùn)行的過(guò)程中,MySQL并不知道哪些數(shù)據(jù)行是 class_name = '初三一班'的(沒(méi)有索引嘛),如果一個(gè)條件無(wú)法通過(guò)索引快速過(guò)濾,存儲(chǔ)引擎層面就會(huì)將所有記錄加鎖后返回,再由MySQL Server層進(jìn)行過(guò)濾。 但在實(shí)際使用過(guò)程當(dāng)中,MySQL做了一些改進(jìn),在MySQL Server過(guò)濾條件,發(fā)現(xiàn)不滿足后,會(huì)調(diào)用unlock_row方法,把不滿足條件的記錄釋放鎖 (違背了二段鎖協(xié)議的約束)。這樣做,保證了最后只會(huì)持有滿足條件記錄上的鎖,但是每條記錄的加鎖操作還是不能省略的??梢?jiàn)即使是MySQL,為了效率也是會(huì)違反規(guī)范的。(參見(jiàn)《高性能MySQL》中文第三版p181) 這種情況同樣適用于MySQL的默認(rèn)隔離級(jí)別RR。所以對(duì)一個(gè)數(shù)據(jù)量很大的表做批量修改的時(shí)候,如果無(wú)法使用相應(yīng)的索引,MySQL Server過(guò)濾數(shù)據(jù)的的時(shí)候特別慢,就會(huì)出現(xiàn)雖然沒(méi)有修改某些行的數(shù)據(jù),但是它們還是被鎖住了的現(xiàn)象。 Repeatable Read(可重讀) 這是MySQL中InnoDB默認(rèn)的隔離級(jí)別。我們姑且分“讀”和“寫(xiě)”兩個(gè)模塊來(lái)講解。 讀 讀就是可重讀,可重讀這個(gè)概念是一事務(wù)的多個(gè)實(shí)例在并發(fā)讀取數(shù)據(jù)時(shí),會(huì)看到同樣的數(shù)據(jù)行,有點(diǎn)抽象,我們來(lái)看一下效果。 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ù),和第一次查詢(xún)的結(jié)果不一樣,是不可重讀的。 commit; 事務(wù)B修改id=1的數(shù)據(jù)提交之后,事務(wù)A同樣的查詢(xún),后一次和前一次的結(jié)果不一樣,這就是不可重讀(重新讀取產(chǎn)生的結(jié)果不一樣)。這就很可能帶來(lái)一些問(wèn)題,那么我們來(lái)看看在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 沒(méi)有讀到事務(wù)B修改的數(shù)據(jù),和第一次sql讀取的一樣,是可重復(fù)讀的。 沒(méi)有讀到事務(wù)C新添加的數(shù)據(jù)。 commit; 我們注意到,當(dāng)teacher_id=1時(shí),事務(wù)A先做了一次讀取,事務(wù)B中間修改了id=1的數(shù)據(jù),并commit之后,事務(wù)A第二次讀到的數(shù)據(jù)和第一次完全相同。所以說(shuō)它是可重讀的。那么MySQL是怎么做到的呢?這里姑且賣(mài)個(gè)關(guān)子,我們往下看。 不可重復(fù)讀和幻讀的區(qū)別 很多人容易搞混不可重復(fù)讀和幻讀,確實(shí)這兩者有些相似。但不可重復(fù)讀重點(diǎn)在于update和delete,而幻讀的重點(diǎn)在于insert。 如果使用鎖機(jī)制來(lái)實(shí)現(xiàn)這兩種隔離級(jí)別,在可重復(fù)讀中,該sql第一次讀取到數(shù)據(jù)后,就將這些數(shù)據(jù)加鎖,其它事務(wù)無(wú)法修改這些數(shù)據(jù),就可以實(shí)現(xiàn)可重復(fù)讀了。但這種方法卻無(wú)法鎖住insert的數(shù)據(jù),所以當(dāng)事務(wù)A先前讀取了數(shù)據(jù),或者修改了全部數(shù)據(jù),事務(wù)B還是可以insert數(shù)據(jù)提交,這時(shí)事務(wù)A就會(huì)發(fā)現(xiàn)莫名其妙多了一條之前沒(méi)有的數(shù)據(jù),這就是幻讀,不能通過(guò)行鎖來(lái)避免。需要Serializable隔離級(jí)別 ,讀用讀鎖,寫(xiě)用寫(xiě)鎖,讀鎖和寫(xiě)鎖互斥,這么做可以有效的避免幻讀、不可重復(fù)讀、臟讀等問(wèn)題,但會(huì)極大的降低數(shù)據(jù)庫(kù)的并發(fā)能力。 所以說(shuō)不可重復(fù)讀和幻讀最大的區(qū)別,就在于如何通過(guò)鎖機(jī)制來(lái)解決他們產(chǎn)生的問(wèn)題。 上文說(shuō)的,是使用悲觀鎖機(jī)制來(lái)處理這兩種問(wèn)題,但是MySQL、ORACLE、PostgreSQL等成熟的數(shù)據(jù)庫(kù),出于性能考慮,都是使用了以樂(lè)觀鎖為理論基礎(chǔ)的MVCC(多版本并發(fā)控制)來(lái)避免這兩種問(wèn)題。 悲觀鎖和樂(lè)觀鎖 悲觀鎖 正如其名,它指的是對(duì)數(shù)據(jù)被外界(包括本系統(tǒng)當(dāng)前的其他事務(wù),以及來(lái)自外部系統(tǒng)的事務(wù)處理)修改持保守態(tài)度,因此,在整個(gè)數(shù)據(jù)處理過(guò)程中,將數(shù)據(jù)處于鎖定狀態(tài)。悲觀鎖的實(shí)現(xiàn),往往依靠數(shù)據(jù)庫(kù)提供的鎖機(jī)制(也只有數(shù)據(jù)庫(kù)層提供的鎖機(jī)制才能真正保證數(shù)據(jù)訪問(wèn)的排他性,否則,即使在本系統(tǒng)中實(shí)現(xiàn)了加鎖機(jī)制,也無(wú)法保證外部系統(tǒng)不會(huì)修改數(shù)據(jù))。 在悲觀鎖的情況下,為了保證事務(wù)的隔離性,就需要一致性鎖定讀。讀取數(shù)據(jù)時(shí)給加鎖,其它事務(wù)無(wú)法修改這些數(shù)據(jù)。修改刪除數(shù)據(jù)時(shí)也要加鎖,其它事務(wù)無(wú)法讀取這些數(shù)據(jù)。 樂(lè)觀鎖 相對(duì)悲觀鎖而言,樂(lè)觀鎖機(jī)制采取了更加寬松的加鎖機(jī)制。悲觀鎖大多數(shù)情況下依靠數(shù)據(jù)庫(kù)的鎖機(jī)制實(shí)現(xiàn),以保證操作最大程度的獨(dú)占性。但隨之而來(lái)的就是數(shù)據(jù)庫(kù)性能的大量開(kāi)銷(xiāo),特別是對(duì)長(zhǎng)事務(wù)而言,這樣的開(kāi)銷(xiāo)往往無(wú)法承受。 而樂(lè)觀鎖機(jī)制在一定程度上解決了這個(gè)問(wèn)題。樂(lè)觀鎖,大多是基于數(shù)據(jù)版本( Version )記錄機(jī)制實(shí)現(xiàn)。何謂數(shù)據(jù)版本?即為數(shù)據(jù)增加一個(gè)版本標(biāo)識(shí),在基于數(shù)據(jù)庫(kù)表的版本解決方案中,一般是通過(guò)為數(shù)據(jù)庫(kù)表增加一個(gè) “version” 字段來(lái)實(shí)現(xiàn)。讀取出數(shù)據(jù)時(shí),將此版本號(hào)一同讀出,之后更新時(shí),對(duì)此版本號(hào)加一。此時(shí),將提交數(shù)據(jù)的版本數(shù)據(jù)與數(shù)據(jù)庫(kù)表對(duì)應(yīng)記錄的當(dāng)前版本信息進(jìn)行比對(duì),如果提交的數(shù)據(jù)版本號(hào)大于數(shù)據(jù)庫(kù)表當(dāng)前版本號(hào),則予以更新,否則認(rèn)為是過(guò)期數(shù)據(jù)。 要說(shuō)明的是,MVCC的實(shí)現(xiàn)沒(méi)有固定的規(guī)范,每個(gè)數(shù)據(jù)庫(kù)都會(huì)有不同的實(shí)現(xiàn)方式,這里討論的是InnoDB的MVCC。 MVCC在MySQL的InnoDB中的實(shí)現(xiàn) 在InnoDB中,會(huì)在每行數(shù)據(jù)后添加兩個(gè)額外的隱藏的值來(lái)實(shí)現(xiàn)MVCC,這兩個(gè)值一個(gè)記錄這行數(shù)據(jù)何時(shí)被創(chuàng)建,另外一個(gè)記錄這行數(shù)據(jù)何時(shí)過(guò)期(或者被刪除)。 在實(shí)際操作中,存儲(chǔ)的并不是時(shí)間,而是事務(wù)的版本號(hào),每開(kāi)啟一個(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)到原來(lái)刪除的行 通過(guò)MVCC,雖然每行記錄都需要額外的存儲(chǔ)空間,更多的行檢查工作以及一些額外的維護(hù)工作,但可以減少鎖的使用,大多數(shù)讀操作都不用加鎖,讀數(shù)據(jù)操作很簡(jiǎn)單,性能很好,并且也能保證只會(huì)讀取到符合標(biāo)準(zhǔn)的行,也只鎖住必要行。 我們不管從數(shù)據(jù)庫(kù)方面的教課書(shū)中學(xué)到,還是從網(wǎng)絡(luò)上看到,大都是上文中事務(wù)的四種隔離級(jí)別這一模塊列出的意思,RR級(jí)別是可重復(fù)讀的,但無(wú)法解決幻讀,而只有在Serializable級(jí)別才能解決幻讀。于是我就加了一個(gè)事務(wù)C來(lái)展示效果。在事務(wù)C中添加了一條teacher_id=1的數(shù)據(jù)commit,RR級(jí)別中應(yīng)該會(huì)有幻讀現(xiàn)象,事務(wù)A在查詢(xún)teacher_id=1的數(shù)據(jù)時(shí)會(huì)讀到事務(wù)C新加的數(shù)據(jù)。但是測(cè)試后發(fā)現(xiàn),在MySQL中是不存在這種情況的,在事務(wù)C提交后,事務(wù)A還是不會(huì)讀到這條數(shù)據(jù)??梢?jiàn)在MySQL的RR級(jí)別中,是解決了幻讀的讀問(wèn)題的。參見(jiàn)下圖 讀問(wèn)題解決了,根據(jù)MVCC的定義,并發(fā)提交數(shù)據(jù)時(shí)會(huì)出現(xiàn)沖突,那么沖突時(shí)如何解決呢?我們?cè)賮?lái)看看InnoDB中RR級(jí)別對(duì)于寫(xiě)數(shù)據(jù)的處理。 “讀”與“讀”的區(qū)別 可能有讀者會(huì)疑惑,事務(wù)的隔離級(jí)別其實(shí)都是對(duì)于讀數(shù)據(jù)的定義,但到了這里,就被拆成了讀和寫(xiě)兩個(gè)模塊來(lái)講解。這主要是因?yàn)镸ySQL中的讀,和事務(wù)隔離級(jí)別中的讀,是不一樣的。 我們且看,在RR級(jí)別中,通過(guò)MVCC機(jī)制,雖然讓數(shù)據(jù)變得可重復(fù)讀,但我們讀到的數(shù)據(jù)可能是歷史數(shù)據(jù),是不及時(shí)的數(shù)據(jù),不是數(shù)據(jù)庫(kù)當(dāng)前的數(shù)據(jù)!這在一些對(duì)于數(shù)據(jù)的時(shí)效特別敏感的業(yè)務(wù)中,就很可能出問(wèn)題。 對(duì)于這種讀取歷史數(shù)據(jù)的方式,我們叫它快照讀 (snapshot read),而讀取數(shù)據(jù)庫(kù)當(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)前讀”,就需要另外的模塊來(lái)解決了。 寫(xiě)("當(dāng)前讀") 事務(wù)的隔離級(jí)別中雖然只定義了讀數(shù)據(jù)的要求,實(shí)際上這也可以說(shuō)是寫(xiě)數(shù)據(jù)的要求。上文的“讀”,實(shí)際是講的快照讀;而這里說(shuō)的“寫(xiě)”就是當(dāng)前讀了。 為了解決當(dāng)前讀中的幻讀問(wèn)題,MySQL事務(wù)使用了Next-Key鎖。 Next-Key鎖 Next-Key鎖是行鎖和GAP(間隙鎖)的合并,行鎖上文已經(jīng)介紹了,接下來(lái)說(shuō)下GAP間隙鎖。 行鎖可以防止不同事務(wù)版本的數(shù)據(jù)修改提交時(shí)造成數(shù)據(jù)沖突的情況。但如何避免別的事務(wù)插入數(shù)據(jù)就成了問(wèn)題。我們可以看看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í)行。 通過(guò)對(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ù),而且沒(méi)有被之前的update語(yǔ)句所修改,這就是“當(dāng)前讀”的幻讀。 RR級(jí)別中,事務(wù)A在update后加鎖,事務(wù)B無(wú)法插入新數(shù)據(jù),這樣事務(wù)A在update前后讀的數(shù)據(jù)保持一致,避免了幻讀。這個(gè)鎖,就是Gap鎖。 MySQL是這么實(shí)現(xiàn)的: 在class_teacher這張表中,teacher_id是個(gè)索引,那么它就會(huì)維護(hù)一套B+樹(shù)的數(shù)據(jù)關(guān)系,為了簡(jiǎn)化,我們用鏈表結(jié)構(gòu)來(lái)表達(dá)(實(shí)際上是個(gè)樹(shù)形結(jié)構(gòu),但原理相同) 如圖所示,InnoDB使用的是聚集索引,teacher_id身為二級(jí)索引,就要維護(hù)一個(gè)索引字段和主鍵id的樹(shù)狀結(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就無(wú)法在這個(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之后,這條語(yǔ)句才插入成功 commit; commit; update的teacher_id=20是在(5,30]區(qū)間,即使沒(méi)有修改任何數(shù)據(jù),Innodb也會(huì)在這個(gè)區(qū)間加gap鎖,而其它區(qū)間不會(huì)影響,事務(wù)C正常插入。 如果使用的是沒(méi)有索引的字段,比如update class_teacher set teacher_id=7 where class_name='初三八班(即使沒(méi)有匹配到任何數(shù)據(jù))',那么會(huì)給全表加入gap鎖。同時(shí),它不能像上文中行鎖一樣經(jīng)過(guò)MySQL Server過(guò)濾自動(dòng)解除不滿足條件的鎖,因?yàn)闆](méi)有索引,則這些字段也就沒(méi)有排序,也就沒(méi)有區(qū)間。除非該事務(wù)提交,否則其它事務(wù)無(wú)法插入任何數(shù)據(jù)。 行鎖防止別的事務(wù)修改或刪除,GAP鎖防止別的事務(wù)新增,行鎖和GAP鎖結(jié)合形成的的Next-Key鎖共同解決了RR級(jí)別在寫(xiě)數(shù)據(jù)時(shí)的幻讀問(wèn)題。 Serializable 這個(gè)級(jí)別很簡(jiǎn)單,讀加共享鎖,寫(xiě)加排他鎖,讀寫(xiě)互斥。使用的悲觀鎖的理論,實(shí)現(xiàn)簡(jiǎn)單,數(shù)據(jù)更加安全,但是并發(fā)能力非常差。如果你的業(yè)務(wù)并發(fā)的特別少或者沒(méi)有并發(fā),同時(shí)又要求數(shù)據(jù)及時(shí)可靠的話,可以使用這種模式。 這里要吐槽一句,不要看到select就說(shuō)不會(huì)加鎖了,在Serializable這個(gè)級(jí)別,還是會(huì)加鎖的!
以上就是關(guān)于快照讀和當(dāng)前讀相關(guān)問(wèn)題的回答。希望能幫到你,如有更多相關(guān)問(wèn)題,您也可以聯(lián)系我們的客服進(jìn)行咨詢(xún),客服也會(huì)為您講解更多精彩的知識(shí)和內(nèi)容。
推薦閱讀:
數(shù)據(jù)庫(kù)快照作用(數(shù)據(jù)庫(kù)快照作用有哪些)
vm快照刪除磁盤(pán)過(guò)滿(vm刪除快照是否可以釋放空間)
內(nèi)存快照與磁盤(pán)快照(內(nèi)存快照與磁盤(pán)快照哪個(gè)好)
華為手機(jī)調(diào)震動(dòng)微信不震動(dòng)(華為手機(jī)調(diào)震動(dòng)微信不震動(dòng)了怎么辦)