中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

mysql幻讀指的是什么

發布時間:2023-05-10 10:25:20 來源:億速云 閱讀:284 作者:iii 欄目:MySQL數據庫

本篇內容主要講解“mysql幻讀指的是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“mysql幻讀指的是什么”吧!

在mysql中,幻讀指當用戶讀取某一范圍的數據行時,另一個事務又在該范圍內插入了新行,當用戶再讀取該范圍的數據行時,會發現有新的“幻影”行。所謂的幻讀,就是通過SELECT查詢出來的數據集并不是真實存在的數據集,用戶通過SELECT語句查詢出某條記錄是不存在的,但是它有可能在真實的表中是存在的。

什么是幻讀

先來看看事務的隔離級別
mysql幻讀指的是什么
然后,談幻讀之前,我先說說我對幻讀的理解:

所謂幻讀,重點在于“幻”這個詞,很夢幻,很玄乎,真假不定,就像蒙上了一層霧一樣,你不能真真切切的看到對方,給人以幻的感覺,這便是“幻”。而所謂的幻讀,也就是你通過SELECT查詢出來的數據集并不是真實存在的數據集,你通過SELECT語句查詢出某條記錄是不存在的,但是它有可能在真實的表中是存在的。

我是這么理解幻讀與不可重復讀的:

  • 幻讀說的是存不存在的問題:原來不存在的,現在存在了,則是幻讀

  • 不可重復讀說的是變沒變化的問題:原來是A,現在卻變為了B,則為不可重復讀


幻讀,目前我了解的有兩種說法:

說法一:事務 A 根據條件查詢得到了 N 條數據,但此時事務 B 刪除或者增加了 M 條符合事務 A 查詢條件的數據,這樣當事務 A 再次進行查詢的時候真實的數據集已經發生了變化,但是A卻查詢不出來這種變化,因此產生了幻讀。

這一種說法強調幻讀在于某一個范圍內的數據行變多或者是變少了,側重說明的是數據集不一樣導致了產生了幻讀。

說法二:幻讀并不是說兩次讀取獲取的結果集不同,幻讀側重的方面是某一次的 select 操作得到的結果所表征的數據狀態無法支撐后續的業務操作。更為具體一些:A事務select 某記錄是否存在,結果為不存在,準備插入此記錄,但執行 insert 時發現此記錄已存在,無法插入,此時就發生了幻讀。產生這樣的原因是因為有另一個事務往表中插入了數據。


我個人更贊成第一種說法。

說法二這種情況也屬于幻讀,說法二歸根到底還是數據集發生了改變,查詢得到的數據集與真實的數據集不匹配。

對于說法二:當進行INSERT的時候,也需要隱式的讀取,比如插入數據時需要讀取有沒有主鍵沖突,然后再決定是否能執行插入。如果這時發現已經有這個記錄了,就沒法插入。所以,SELECT 顯示不存在,但是INSERT的時候發現已存在,說明符合條件的數據行發生了變化,也就是幻讀的情況,而不可重復讀指的是同一條記錄的內容被修改了。

舉例來說明:說法二說的是如下的情況:
有兩個事務A和B,A事務先開啟,然后A開始查詢數據集中有沒有id = 30的數據,查詢的結果顯示數據中沒有id = 30的數據。緊接著又有一個事務B開啟了,B事務往表中插入了一條id = 30的數據,然后提交了事務。然后A再開始往表中插入id = 30的數據,由于B事務已經插入了id = 30的數據,自然是不能插入,緊接著A又查詢了一次,結果發現表中沒有id = 30的數據呀,A事務就很納悶了,怎么會插入不了數據呢。當A事務提交以后,再次查詢,發現表中的確存在id = 30的數據。但是A事務還沒提交的時候,卻查不出來?
其實,這便是可重復讀的作用。

過程如下圖所示:

mysql幻讀指的是什么

上圖中操作的t表的創建語句如下:

CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `c` int(11) DEFAULT NULL,
  `d` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `c` (`c`) -- 創建索引
) ENGINE=InnoDB;

INSERT INTO t VALUES(0,0,0),(5,5,5),
(10,10,10),(15,15,15),(20,20,20),(25,25,25);

MySQL使用的InnoDB引擎默認的隔離級別是可重復讀,也就是說在同一個事務中,兩次執行同樣的查詢,得到的效果應該是一樣的。因此,盡管B事務在A事務還未結束的時候,增加了表中的數據,但是為了維護可重復讀,A事務中不管怎么查詢,是查詢不了新增的數據的。但是對于真實的表而言,表中的數據是的的確確增加了。

A查詢不到這個數據,不代表這個數據不存在查詢得到了某條數據,不代表它真的存在。這樣是是而非的查詢,就像是幻覺一樣,似真似假,故為幻讀
產生幻讀的原因歸根到底是由于查詢得到的結果與真實的結果不匹配。

幻讀 VS 不可重復讀

  • 幻讀重點在于數據是否存在。原本不存在的數據卻真實的存在了,這便是幻讀。在同一個事務中,第一次讀取到結果集和第二次讀取到的結果集不同。(對比上面的例子,當B事務INSERT以后,A事務中再進行插入,此次插入相當于一次隱式查詢)。引起幻讀的原因在于另一個事務進行了INSERT操作。

  • 不可重復讀重點在于數據是否被改變了。在一個事務中對同一條記錄進行查詢,第一次讀取到的數據和第二次讀取到的數據不一致,這便是可重復讀。引起不可重復讀的原因在于另一個事務進行了UPDATE或者是DELETE操作。

簡單來說:幻讀是說數據的條數發生了變化,原本不存在的數據存在了。不可重復讀是說數據的內容發生了變化,原本存在的數據的內容發生了改變

可重復讀隔離下為什么會產生幻讀?

在可重復讀隔離級別下,普通的查詢是快照讀,是不會看到別的事務插入的數據的。因此,幻讀在 當前讀 下才會出現。

什么是快照讀,什么是當前讀?

快照讀讀取的是快照數據。不加鎖的簡單的 SELECT都屬于快照讀,比如這樣:

SELECT * FROM player WHERE ...

當前讀就是讀取最新數據,而不是歷史版本的數據。加鎖SELECT,或者對數據進行增刪改都會進行當前讀。這有點像是 Java 中的 volatile 關鍵字,被 volatile 修飾的變量,進行修改時,JVM 會強制將其寫回內存,而不是放在 CPU 緩存中,進行讀取時,JVM 會強制從內存讀取,而不是放在 CPU 緩存中。這樣就能保證其可見行,保證每次讀取到的都是最新的值。如果沒有用 volatile 關鍵字修飾,變量的值可能會被放在 CPU 緩存中,這就導致讀取到的值可能是某次修改的值,不能保證是最新的值。

說多了,我們繼續來看,如下的操作都會進行 當前讀

SELECT * FROM player LOCK IN SHARE MODE;
SELECT * FROM player FOR UPDATE;
INSERT INTO player values ...
DELETE FROM player WHERE ...
UPDATE player SET ...

說白了,快照讀就是普通的讀操作,而當前讀包括了 加鎖的讀取DML(DML只是對表內部的數據操作,不涉及表的定義,結構的修改。主要包括insert、update、deletet) 操作。

比如在可重復讀的隔離條件下,我開啟了兩個事務,在另一個事務中進行了插入操作,當前事務如果使用當前讀 是可以讀到最新的數據的。

mysql幻讀指的是什么

MySQL中如何實現可重復讀

當隔離級別為可重復讀的時候,事務只在第一次 SELECT 的時候會獲取一次 Read View,而后面所有的 SELECT 都會復用這個 Read View。也就是說:對于A事務而言,不管其他事務怎么修改數據,對于A事務而言,它能看到的數據永遠都是第一次SELECT時看到的數據。這顯然不合理,如果其它事務插入了數據,A事務卻只能看到過去的數據,讀取不了當前的數據。

既然都說到 Read View 了,就不得不說 MVCC (多版本并發控制) 機制了。MVCC 其實字面意思還比較好理解,為了防止數據產生沖突,我們可以使用時間戳之類的來進行標識,不同的時間戳對應著不同的版本。比如你現在有1000元,你借給了張三 500 元, 之后李四給了你 500 元,雖然你的錢的總額都是 1000元,但是其實已經和最開始的 1000元不一樣了,為了判斷中途是否有修改,我們就可以采用版本號來區分你的錢的變動。

如下,在數據庫的數據表中,idnametype 這三個字段是我自己建立的,但是除了這些字段,其實還有些隱藏字段是 MySQL 偷偷為我們添加的,我們通常是看不到這樣的隱藏字段的。

mysql幻讀指的是什么

我們重點關注這兩個隱藏的字段:

  • db_trx_id:操作這行數據的事務 ID,也就是最后一個對該數據進行插入或更新的事務 ID。我們每開啟一個事務,都會從數據庫中獲得一個事務 ID(也就是事務版本號),這個事務 ID 是自增長的,通過 ID 大小,我們就可以判斷事務的時間順序。

  • db_roll_ptr:回滾指針,指向這個記錄的 Undo Log 信息。什么是 Undo Log 呢?可以這么理解,當我們需要修改某條記錄時,MySQL 擔心以后可能會撤銷該修改,回退到之前的狀態,所以在修改之前,先把當前的數據存個檔,然后再進行修改,Undo Log 就可以理解為是這個存檔文件。這就像是我們打游戲一樣,打到某個關卡先存個檔,然后繼續往下一關挑戰,如果下一關挑戰失敗,就回到之前的存檔點,不至于從頭開始。

在 MVCC(多版本并發控制) 機制中,多個事務對同一個行記錄進行更新會產生多個歷史快照,這些歷史快照保存在 Undo Log 里。如下圖所示,當前行記錄的 回滾指針 指向的是它的上一個狀態,它的上一個狀態的 回滾指針 又指向了上一個狀態的上一個狀態。這樣,理論上我們通過遍歷 回滾指針,就能找到該行數據的任意一個狀態。

Undo Log 示意圖

mysql幻讀指的是什么

我們沒有想到,我們看到的或許只是一條數據,但是MySQL卻在背后為該條數據存儲多個版本,為這條數據存了非常多的檔。那問題來了,當我們開啟事務時,我們在事務中想要查詢某條數據,但是每一條數據,都對應了非常多的版本,這時,我們需要讀取哪個版本的行記錄呢?

這時就需要用到 Read View 機制了,它幫我們解決了行的可見性問題。Read View 保存了當前事務開啟時所有活躍(還沒有提交)的事務列表。

在 Read VIew 中有幾個重要的屬性:

  • trx_ids,系統當前正在活躍的事務 ID 集合

  • low_limit_id,活躍的事務中最大的事務 ID

  • up_limit_id,活躍的事務中最小的事務 ID

  • creator_trx_id,創建這個 Read View 的事務 ID

在前面我們說過了,在每一行記錄中有一個隱藏字段 db_trx_id,表示操作這行數據的事務 ID ,而且 事務 ID 是自增長的,通過 ID 大小,我們就可以判斷事務的時間順序

當我們開啟事務以后,準備查詢某條記錄,發現該條記錄的 db_trx_id < up_limit_id,這說明什么呢?說明該條記錄一定是在本次事務開啟之前就已經提交的,對于當前事務而言,這屬于歷史數據,可見,因此,我們通過 select 一定能查出這一條記錄。

但是如果發現,要查詢的這條記錄的 db_trx_id > up_limit_id。這說明什么呢,說明我在開啟事務的時候,這條記錄肯定是還沒有的,是在之后這條記錄才被創建的,不應該被當前事務看見,這時候我們就可以通過 回滾指針 + Undo Log 去找一下該記錄的歷史版本,返回給當前事務。在本文 什么是幻讀 ? 這一章節中舉的一個例子。A 事務開啟時,數據庫中還沒有(30, 30, 30)這條記錄。A事務開啟以后,B事務往數據庫中插入了(30, 30, 30)這條記錄,這時候,A事務使用 不加鎖select 進行 快照讀 時是查詢不出這條新插入的記錄的,這符合我們的預期。對于 A事務而言,(30, 30, 30)這條記錄的 db_trx_id 一定大于 A事務開啟時的 up_limit_id,所以這條記錄不應該被A事務看見。

如果需要查詢的這條記錄的 trx_id 滿足 up_limit_id < trx_id < low_limit_id 這個條件,說明該行記錄所在的事務 trx_id 在目前 creator_trx_id 這個事務創建的時候,可能還處于活躍的狀態,因此我們需要在 trx_ids 集合中進行遍歷,如果 trx_id 存在于 trx_ids 集合中,證明這個事務 trx_id 還處于活躍狀態,不可見,如果該記錄有 Undo Log,我們可以通過回滾指針進行遍歷,查詢該記錄的歷史版本數據。如果 trx_id 不存在于 trx_ids 集合中,證明事務 trx_id 已經提交了,該行記錄可見。

從圖中你能看到回滾指針將數據行的所有快照記錄都通過鏈表的結構串聯了起來,每個快照的記錄都保存了當時的 db_trx_id,也是那個時間點操作這個數據的事務 ID。這樣如果我們想要找歷史快照,就可以通過遍歷回滾指針的方式進行查找。

最后,再來強調一遍:事務只在第一次 SELECT 的時候會獲取一次 Read View

因此,如下圖所示,在 可重復讀 的隔離條件下,在該事務中不管進行多少次 以WHERE heigh > 2.08為條件 的查詢,最終結果得到都是一樣的,盡管可能會有其它事務對這個結果集進行了更改。

mysql幻讀指的是什么

如何解決幻讀

即便是給每行數據都加上行鎖,也無法解決幻讀,行鎖只能阻止修改,無法阻止數據的刪除。而且新插入的數據,自然是數據庫中不存在的數據,原本不存在的數據自然無法對其加鎖,因此僅僅使用行鎖是無法阻止別的事務插入數據的。

為了解決幻讀問題,InnoDB 只好引入新的鎖,也就是間隙鎖 (Gap Lock)。顧名思義,間隙鎖,鎖的就是兩個值之間的空隙。比如文章開頭的表 t,初始化插入了 6 個記錄,這就產生了 7 個間隙。

表 t 主鍵索引上的行鎖和間隙鎖
mysql幻讀指的是什么
也就是說這時候,在一行行掃描的過程中,不僅將給行加上了行鎖,還給行兩邊的空隙,也加上了間隙鎖。現在你知道了,數據行是可以加上鎖的實體,數據行之間的間隙,也是可以加上鎖的實體。但是間隙鎖跟我們之前碰到過的鎖都不太一樣。

  • 間隙鎖和行鎖合稱 next-key lock,每個 next-key lock 是前開后閉區間。也就是說,我們的表 t 初始化以后,如果用 SELECT * FEOM t FOR UPDATE要把整個表所有記錄鎖起來,就形成了 7 個 next-key lock,分別是 (負無窮,0]、(0,5]、(5,10]、(10,15]、(15,20]、(20, 25]、(25, 正無窮]。

  • 間隙鎖是在可重復讀隔離級別下才會生效的

怎么加間隙鎖呢?使用寫鎖(又叫排它鎖,X鎖)時自動生效,也就是說我們執行 SELECT * FEOM t FOR UPDATE時便會自動觸發間隙鎖。會給主鍵加上上圖所示的鎖。

如下圖所示,如果在事務A中執行了SELECT * FROM t WHERE d = 5 FOR UPDATE以后,事務B則無法插入數據了,因此就避免了產生幻讀。

數據表的創建語句如下

CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `c` int(11) DEFAULT NULL,
  `d` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `c` (`c`) -- 創建索引
) ENGINE=InnoDB;

INSERT INTO t VALUES(0,0,0),(5,5,5),
(10,10,10),(15,15,15),(20,20,20),(25,25,25);

需要注意的是,由于創建數據表的時候僅僅只在c字段上創建了索引,因此使用條件WHERE id = 5查找時是會掃描全表的。因此,SELECT * FROM t WHERE d = 5 FOR UPDATE實際上鎖住了整個表,如上圖所示,產生了七個間隙,這七個間隙都不允許數據的插入。

因此當B想插入一條數據(1, 1, 1)時就會被阻塞住,因為它的主鍵位于位于(0, 5]這個區間,被禁止插入。

mysql幻讀指的是什么

還需要注意的一點是,間隙鎖和間隙鎖是不會產生沖突的
讀鎖(又稱共享鎖,S鎖)和寫鎖會沖突,寫鎖和寫鎖也會產生沖突。但是間隙鎖和間隙鎖是不會產生沖突的

如下:
A事務對id = 5的數據加了讀鎖,B事務再對id = 5的數據加寫鎖則會失敗,若B事務加讀鎖則會成功。讀鎖和讀鎖可以兼容,讀鎖和寫鎖則不能兼容。

A事務對id = 5的數據加了寫鎖,B事務再對id = 5的數據加寫鎖則會失敗,若B事務加讀鎖同樣也會失敗。
mysql幻讀指的是什么
在加了間隙鎖以后,當A事務開啟以后,并對(5, 10]這個區間加了間隙鎖,那么B事務則無法插入數據了。

mysql幻讀指的是什么
但是當A事務對(5, 10]加了間隙鎖以后,B事務也可以對這個區間加間隙鎖。

間隙鎖的目的是阻止往這個區間插入數據,因此A事務加了以后B事務繼續加間隙鎖,這并不矛盾。但是對于寫鎖和讀鎖就不一樣了。
寫鎖是不允許其它事務讀,也不允許寫,而讀鎖則是允許寫,語義上就存在沖突。自然無法同時加這兩個鎖。
而寫鎖和寫鎖也是,寫鎖不允許讀,也不允許寫,想想,A事務對數據加了寫鎖,就是完全不想讓其它事務操作該數據,那其它數據若能為這個數據加寫鎖,就相當于是對該數據實施了操作,違背了寫鎖的涵義,自然不被允許。

到此,相信大家對“mysql幻讀指的是什么”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

天长市| 抚松县| 炉霍县| 平潭县| 洪湖市| 黄骅市| 时尚| 蒙山县| 仪征市| 长汀县| 泌阳县| 木兰县| 南开区| 新乡市| 永宁县| 松阳县| 汉寿县| 乌什县| 任丘市| 绩溪县| 汤阴县| 嘉祥县| 伊吾县| 普定县| 台东县| 顺平县| 广宗县| 巴林右旗| 宿松县| 灵石县| 西青区| 浮山县| 上饶县| 蕉岭县| 当阳市| 鹤岗市| 开远市| 弋阳县| 沙田区| 盐城市| 友谊县|