自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

幻讀:聽說我是被MVCC干掉的?

原創(chuàng)
運(yùn)維 數(shù)據(jù)庫運(yùn)維 開發(fā)工具
我是幻讀,聽說有人認(rèn)為我是 MVCC 解決的,為了讓大家更全面地理解我,只能親自來解釋一下。

【51CTO.com原創(chuàng)稿件】我是幻讀,聽說有人認(rèn)為我是 MVCC 解決的,為了讓大家更全面地理解我,只能親自來解釋一下。

[[396015]]

圖片來自 Pexels

我是誰?

先給大家做一個(gè)簡(jiǎn)單的自我介紹,我就是事務(wù)并發(fā)時(shí)會(huì)產(chǎn)生的三大問題之一。

我的其他倆兄弟臟讀、不可重復(fù)讀被 MVCC 在上一個(gè)回合無情的干掉了,至于上個(gè)回合發(fā)生了什么可以去看劇情回顧。

我的由來就是因?yàn)橹魅嗽诓僮饕唤M數(shù)據(jù)時(shí)還有很多人也在對(duì)這組數(shù)據(jù)進(jìn)行操作。

舉一個(gè)簡(jiǎn)單的案例:根據(jù)條件在對(duì)一組數(shù)據(jù)進(jìn)行過濾返回的結(jié)果為 100 個(gè),但是在主人操作的同時(shí)其他人又新增了符合條件的數(shù)據(jù),然后主人再次進(jìn)行查詢時(shí)返回結(jié)果為 101。第二次返回的數(shù)據(jù)跟第一次返回?cái)?shù)據(jù)不一致。

于是我誕生了,大家還給我起了個(gè)很好聽的名字幻讀。為什么會(huì)給我起這個(gè)名字呢!那是因?yàn)槲医o人們的現(xiàn)象好像出了幻覺一樣。

為什么有人會(huì)認(rèn)為我是被 MVCC 干掉的

為了演示方便,就直接使用之前的測(cè)試表來進(jìn)行操作。

同時(shí)大家可以看到此表還有一些測(cè)試數(shù)據(jù),一切從頭開始,清空表。

清空表的命令:

  1. truncate table_name 

執(zhí)行這個(gè)命令會(huì)使表的數(shù)據(jù)清空,并且自增 ID 會(huì)從 1 開始。

從執(zhí)行過程來看,truncate table 類似于 drop table 然后在 create table,這里的環(huán)境都是測(cè)試環(huán)境,千萬不要在線上進(jìn)行操作,因?yàn)樗@過了 DML 方法,是不能回滾的。

進(jìn)行了一點(diǎn)小插曲,進(jìn)入正題。

根據(jù)上圖的執(zhí)行步驟,預(yù)期來說左邊事務(wù)的第一條 select 語句查詢結(jié)果為空。第二個(gè) select 查詢結(jié)果為 1 條數(shù)據(jù),包含右邊事務(wù)提交的數(shù)據(jù)。

但在實(shí)際測(cè)試的情況下,第一次執(zhí)行 select 和第二次執(zhí)行 select 返回結(jié)果一致。

從這個(gè)案例中,可以得出結(jié)論確實(shí)在不可重復(fù)隔離級(jí)別下會(huì)解決幻讀問題(在快照讀的前提下)。

我真的是被 MVCC 解決的?

通過上述測(cè)試案例來看,貌似在 MySQL 中通過 MVCC 就解決我的引來的問題,那既然都解決了我的問題,為什么還有串行化的隔離級(jí)別呢!好疑惑啊!

帶著這個(gè)疑問繼續(xù)進(jìn)行實(shí)驗(yàn),為了方便就不再使用上邊表結(jié)構(gòu)了,建立一個(gè)簡(jiǎn)單的表結(jié)構(gòu)。

再進(jìn)入一個(gè)小插曲你知道在 MySQL 終端如何清屏嗎?執(zhí)行命令 system clear 即可。

接著開始新一輪的測(cè)試:

上圖案例事務(wù) 1 幾次查詢數(shù)據(jù)都是空。此時(shí)事務(wù) 2 已經(jīng)成功將數(shù)據(jù)插入并且提交。但當(dāng)事務(wù) 1 幾次查詢數(shù)據(jù)為空之后進(jìn)行數(shù)據(jù)插入時(shí),提示主鍵重復(fù)。

再來看一個(gè)案例:

如上圖:

  • step1:事務(wù) 1 開啟事務(wù)
  • step2:事務(wù) 2 開啟事務(wù)
  • step3:事務(wù) 1 查詢數(shù)據(jù)只有一條數(shù)據(jù)
  • step4:事務(wù) 2 添加一條數(shù)據(jù)
  • step5:事務(wù) 1 查詢數(shù)據(jù)為一條
  • step6:事務(wù) 2 提交事務(wù)
  • step7:事務(wù) 1 查詢數(shù)據(jù)為一條
  • step8:事務(wù) 1 修改 name
  • step9:猜想一下此時(shí)表內(nèi)數(shù)據(jù)會(huì)發(fā)生什么改變

此案例中事務(wù) 1 始終讀取數(shù)據(jù)都是一條數(shù)據(jù),但是在修改數(shù)據(jù)時(shí)影響數(shù)據(jù)行數(shù)卻是 2,再次進(jìn)行查看數(shù)據(jù)時(shí)竟然出現(xiàn)了事務(wù) 2 添加的數(shù)據(jù)。這也可以看作是一種幻讀。

小結(jié):通過以上倆個(gè)案例得知在 MySQL 可重復(fù)讀隔離級(jí)別中并沒有完全解決幻讀問題,而只是解決了快照讀下的幻讀問題。

而對(duì)于當(dāng)前讀的操作依然存在幻讀問題,也就是說 MVCC 對(duì)于幻讀的解決是不徹底的。

再聊當(dāng)前讀、快照讀

在上一回合中快照讀、當(dāng)前讀已經(jīng)被消化了,為了防止消化不良這里再簡(jiǎn)單說明一下。

①當(dāng)前讀

所有操作都加了鎖,并且鎖之間除了共享鎖都是互斥的,如果想要增、刪、改、查時(shí)都需要等待鎖釋放才可以,所以讀取的數(shù)據(jù)都是最新的記錄。

簡(jiǎn)單來說,當(dāng)前讀就是加了鎖的,增、刪、改、查,不管鎖是共享鎖、排它鎖均為當(dāng)前讀。

在 MySQL 的 Innodb 存儲(chǔ)引擎下,增、刪、改操作都會(huì)默認(rèn)加上鎖,所以增、刪、改操作默認(rèn)就為當(dāng)前讀。

②快照讀

快照讀的出現(xiàn)旨在提高事務(wù)并發(fā)性,實(shí)現(xiàn)基于我的敵人 MVCC,簡(jiǎn)單來說快照讀就是不加鎖的非阻塞讀,即簡(jiǎn)單的 select 操作(select * from user)。

在 Innodb 存儲(chǔ)引擎下執(zhí)行簡(jiǎn)單的 select 操作時(shí),會(huì)記錄下當(dāng)前的快照讀數(shù)據(jù),之后的 select 會(huì)沿用第一次快照讀的數(shù)據(jù),即使有其它事務(wù)提交也不會(huì)影響當(dāng)前的 select 結(jié)果,這就解決了不可重復(fù)讀問題。

快照讀讀取的數(shù)據(jù)雖然是一致的,但有可能不是最新的數(shù)據(jù)而是歷史數(shù)據(jù)。

告訴你們吧!當(dāng)前讀的情況下,我是被 next-key locks 干掉的

第二小節(jié)中得知在快照讀下由于我引發(fā)的問題已經(jīng)被 MVCC 消滅了。但是在小節(jié)三進(jìn)行案例測(cè)試發(fā)現(xiàn)在當(dāng)前讀下我又滿血復(fù)活了。

我要是那么容易被干掉還怎么被稱為打不死的小強(qiáng),這不是鬧笑話呢!說歸說,鬧歸鬧如果 MVCC 把它的小弟 next-key locks 帶上那我就完了,就不再像灰太狼說經(jīng)典語錄“我一定會(huì)回來的”。

此時(shí)就要思考一個(gè)問題,在 Innodb 存儲(chǔ)引擎下,是默認(rèn)給快照讀加 next-key locks,還是說需要手動(dòng)加鎖。

通過官方文檔對(duì)于 next-key locks 的解釋:

To prevent phantoms, InnoDB uses an algorithm called next-key locking that combines index-row locking with gap locking. InnoDB performs row-level locking in such a way that when it searches or scans a table index, it sets shared or exclusive locks on the index records it encounters. Thus, the row-level locks are actually index-record locks. In addition, a next-key lock on an index record also affects the “gap” before that index record. That is, a next-key lock is an index-record lock plus a gap lock on the gap preceding the index record. If one session has a shared or exclusive lock on record R in an index, another session cannot insert a new index record in the gap immediately before R in the index order.

大致意思,為了防止幻讀,Innodb 使用 next-key lock 算法,將行鎖(record lock)和間隙鎖(gap lock)結(jié)合在一起。

Innodb 行鎖在搜索或者掃描表索引時(shí),會(huì)在遇到的索引記錄上設(shè)置共享鎖或者排它鎖,因此行鎖實(shí)際是索引記錄鎖。

另外, 在索引記錄上設(shè)置的鎖同樣會(huì)影響索引記錄之前的“間隙(gap)”。即 next-key lock 是索引記錄行加上索引記錄之前的“gap”上的間隙鎖定。

并且還給了一個(gè)案例:

  1. SELECT * FROM child WHERE id > 100 FOR UPDATE

當(dāng) Innodb 掃描索引時(shí),會(huì)將 id 大于 100 地上鎖,阻止任何大于 100 的數(shù)據(jù)添加。

到這里就回答了上邊問題,在 Innodb 下解決當(dāng)前讀產(chǎn)生的幻讀問題需要手動(dòng)加鎖來解決。

再來看一個(gè)案例,下圖為此時(shí)的數(shù)據(jù)情況:

下圖的這個(gè)案例就解決了在第三節(jié)中第一個(gè)案例的幻讀問題。

如上圖:

  • step事務(wù)1:開啟事務(wù)
  • step事務(wù)2:開啟事務(wù)
  • step事務(wù)1:查詢 ID 為 4 的這條數(shù)據(jù)并且加上排它鎖
  • step事務(wù)2:添加 ID 為 4 的數(shù)據(jù),并且等待事務(wù) 1 釋放鎖
  • step事務(wù)1:添加 ID 為 4 的數(shù)據(jù),添加成功
  • step事務(wù)1:查詢當(dāng)前數(shù)據(jù)
  • step事務(wù)1:提交事務(wù)
  • step事務(wù)2:報(bào)錯(cuò),返回主鍵重復(fù)問題

這個(gè)案例查詢的索引列是主鍵并且是唯一的,此時(shí) Innodb 引擎會(huì)對(duì) next-key lock 做降級(jí)處理,也就是只鎖定當(dāng)前查詢的索引記錄行,而不是范圍鎖定。

案例二:還是使用上邊的數(shù)據(jù),但是這次我們進(jìn)行一次范圍查找。

此時(shí)的數(shù)據(jù)為 1,3,5,查找的范圍為大于 3。從下圖可以看出當(dāng)事務(wù) 2 執(zhí)行添加 ID 為 2 的是可以添加成功的。

但是當(dāng)添加 ID 6 時(shí)需要等待。此時(shí)若事務(wù) 1 不提交事務(wù),事務(wù) 2 添加 ID 為 6 的這條數(shù)據(jù)就執(zhí)行不成功。

對(duì)于上述的 SQL 語句 select * from user where id > 3 for update;執(zhí)行返回的只有 5 這一行數(shù)據(jù)。

此時(shí)鎖定的范圍為 (3,5],(5,∞),所以說 id 為 2 的可以插入,ID 為 4 或者大于 5 的都是插入不了的。

以上就是在 Innodb 中解決幻讀問題最終方案。

幻讀解決方案

為了方便大家直觀了解幻讀的解決方案,這里咔咔進(jìn)行簡(jiǎn)單的總結(jié)。

通過 MVCC 解決了快照讀下的幻讀問題,為什么能解決?在第一次執(zhí)行簡(jiǎn)單的 select 語句就生成了一個(gè)快照,并且在后邊的 select 查詢都是沿用第一次快照讀的結(jié)果。所以說快照讀查詢到的數(shù)據(jù)有可能是歷史數(shù)據(jù)。

通過 next-key lock 解決當(dāng)前讀的幻讀問題,next-key lock 是 record lock 和 gap lock 的結(jié)合,鎖定的是一個(gè)范圍,如果查詢數(shù)據(jù)為索引記錄行,則只會(huì)鎖定當(dāng)前行,也就是說降級(jí)為 record lock。

若為范圍查找時(shí)就會(huì)鎖定一個(gè)范圍,例如上例中 ID 為 1,3,5 查詢大于 3 的數(shù)據(jù),則會(huì)把 (3,5],(5,∞) 進(jìn)行范圍鎖定,其它事務(wù)在鎖未釋放之前是無法插入的。

從官方文檔還可得知如果需要驗(yàn)證數(shù)據(jù)唯一性只需要給查詢加上共享鎖即可,也就是給 select 語句加上 in lock share mode,如果返回結(jié)果為空,則可以進(jìn)行插入,并且插入的這個(gè)值肯定是唯一的。

同樣也可以添加 next key lock 防止其他人同時(shí)插入相同數(shù)據(jù),小節(jié) 5 的所有案例就是使用的 next-key lock,從這一點(diǎn)可以得知 next-key lock 是可以鎖定表內(nèi)不存在的索引。

根據(jù)上述結(jié)論來看,如果想要檢測(cè)數(shù)據(jù)唯一性使用共享鎖,那么多個(gè)事務(wù)同時(shí)開啟共享鎖,又同時(shí)添加相同的數(shù)據(jù)怎么辦,會(huì)不會(huì)出現(xiàn)問題呢?

明確地說明是不會(huì)的,如果多個(gè)事務(wù)同時(shí)插入相同數(shù)據(jù)只會(huì)有一個(gè)事務(wù)添加成功,其它事務(wù)會(huì)拋出錯(cuò)誤,這個(gè)就是一個(gè)新的概念“死鎖”。

擴(kuò)展

事務(wù) ID 是在何時(shí)分配的?在本文或者其它資料中都能得到一個(gè)信息就是當(dāng)執(zhí)行一條簡(jiǎn)單的 select 語句同時(shí)也會(huì)生成 read-view。

雖然快照讀、read-view 都是基于事務(wù)啟動(dòng)的前提下,但是 read-veiw 是通過未提交事務(wù) ID 組成的。

①那么到底是在何時(shí)分配事務(wù) ID 的呢?

事務(wù)的啟動(dòng)方式有兩種,分別為顯示啟動(dòng)、另一種是設(shè)置 autocommit=0 后執(zhí)行 select 就會(huì)啟動(dòng)事務(wù)。

在顯示啟動(dòng)中最簡(jiǎn)單的就是以 begin 語句開始,也可以使用 start transaction 開啟事務(wù)。

若使用 start trancaction 開啟事務(wù)也可以選擇開始只讀事務(wù)還是讀寫事務(wù)。

看了很多資料都說當(dāng)開啟一個(gè)事務(wù)時(shí)會(huì)分配一個(gè)事務(wù) ID,那么來驗(yàn)證一下是這個(gè)樣子的嗎?

通過上圖可以看到當(dāng)執(zhí)行一個(gè) begin 語句之后查詢事務(wù) ID 是空的,也就說當(dāng)執(zhí)行 begin 后并沒有分配 trx_id。

那么當(dāng)執(zhí)行 begin 后在支持 DML 語句呢!

根據(jù)文檔得知,執(zhí)行 begin 命令并不是真正開啟一個(gè)事務(wù),僅僅是為當(dāng)前線程設(shè)定標(biāo)記,表示為顯式開啟的事務(wù)。

所以要明白對(duì)數(shù)據(jù)進(jìn)行了增、刪、改、查等操作后才算真正開啟了一個(gè)事務(wù),此時(shí)會(huì)去引擎層開啟事務(wù)。

②為什么事務(wù) ID 差異特別大?

上圖中查詢了當(dāng)前活躍的事務(wù) ID,但是兩個(gè)事務(wù) ID 的差異特別大。相信很多小伙伴都遇到過這個(gè)問題,有問題不害怕,害怕的是沒有問題。

事實(shí)上在這兩條數(shù)據(jù)中只有 20841 是真正的事務(wù) ID,那么第二條數(shù)據(jù)中的 ID 是什么呢!

想知道這個(gè)數(shù)字是什么的前提是知道是怎么來的。

從上圖可以看出,當(dāng)執(zhí)行 select 語句后會(huì)產(chǎn)生一個(gè)非常大的事務(wù) ID,那能不能理解為這種差異非常大的事務(wù) ID 是通過快照讀的方式才會(huì)生成的。

接著再這個(gè)事務(wù)下面在執(zhí)行一個(gè) insert 語句,然后再查看一下事務(wù) ID 的狀態(tài)。

不可思議的是在事務(wù)中先執(zhí)行 select 語句,然后執(zhí)行 insert 語句,事務(wù) ID 發(fā)生了變化,這是什么原因呢?

經(jīng)過資料查詢得知當(dāng)執(zhí)行一個(gè)簡(jiǎn)單的 select 語句時(shí),被稱之為只讀事務(wù),為了避免給只讀事務(wù)分配 trx_id 帶來不必要的開銷就沒有對(duì)其分配事務(wù) ID。

只讀事務(wù)沒有分配 undo segment 也不會(huì)分配 LOCK 鎖結(jié)構(gòu),本質(zhì)上只讀事務(wù)的 trx_id 的值就是 0。

但是為了執(zhí)行 select * from information_schema.INNODB_TRX 或者 show engine innodb status 時(shí)。

就會(huì)通過 reinterpret_cast(trx) | (max_trx_id + 1) 將指針轉(zhuǎn)換為一個(gè) 64 字節(jié)非負(fù)整數(shù)然后位或 (max_trx_id + 1) 就是這么個(gè)值。

關(guān)于這個(gè)值的生成過程就不用再去深究了,只需要知道在只讀事務(wù)下是不會(huì)分配事務(wù) ID,而查詢出來的這個(gè)值只是為了顯示而存在的沒有實(shí)際意義。

但是當(dāng)你執(zhí)行 select * from information_schema.INNODB_TRX 查詢出來的事務(wù) ID,再通過 show engine innodb status 查詢是查不到的。

在 Innodb 下如果事務(wù)為只讀事務(wù)則不會(huì)在 Innodb 數(shù)據(jù)結(jié)構(gòu)中顯示,因此你是看不到的。

作者:咔咔

簡(jiǎn)介:堅(jiān)持學(xué)習(xí)、堅(jiān)持寫博、堅(jiān)持分享是咔咔從業(yè)以來一直所秉持的信念。希望在偌大互聯(lián)網(wǎng)中咔咔的文章能帶給你一絲絲幫助。我是咔咔,下期見。

編輯:陶家龍 

征稿:有投稿、尋求報(bào)道意向技術(shù)人請(qǐng)?zhí)砑有【幬⑿?gordonlonglong

【51CTO原創(chuàng)稿件,合作站點(diǎn)轉(zhuǎn)載請(qǐng)注明原文作者和出處為51CTO.com】

 

責(zé)任編輯:武曉燕 來源: 51CTO技術(shù)棧
相關(guān)推薦

2023-08-09 17:22:30

MVCCMySQL數(shù)據(jù)

2019-05-28 13:50:27

MySQL幻讀數(shù)據(jù)庫

2022-10-19 11:17:35

2022-09-08 13:56:49

MySQL事務(wù)記錄鎖

2022-06-30 08:00:00

MySQL關(guān)系數(shù)據(jù)庫開發(fā)

2022-02-17 21:13:08

MySQL架構(gòu)體系

2021-04-20 19:21:50

臟讀MySQL幻讀

2024-04-19 08:18:47

MySQLSQL隔離

2024-03-11 00:00:00

mysqlInnoDB幻讀

2024-09-02 00:00:00

MySQL幻讀數(shù)據(jù)

2021-04-20 08:02:08

業(yè)務(wù)數(shù)據(jù)用戶

2021-02-23 09:06:00

MVCC版本并發(fā)

2024-04-24 08:26:35

事務(wù)數(shù)據(jù)InnoDB

2024-04-25 08:16:06

InnodbReadMVCC

2022-03-29 10:52:08

MySQL數(shù)據(jù)庫

2019-03-21 09:06:00

數(shù)據(jù)庫復(fù)讀幻讀

2022-04-27 07:32:02

臟讀幻讀不可重復(fù)讀

2021-12-02 08:19:06

MVCC面試數(shù)據(jù)庫

2020-04-09 08:29:50

編程語言事件驅(qū)動(dòng)

2016-09-30 01:04:45

數(shù)據(jù)分析數(shù)據(jù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)