欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Mysql中幻讀的概念以及如何解決

 更新時間:2023年05月22日 08:49:17   作者:ITWords  
這篇文章主要介紹了Mysql中幻讀的概念以及如何解決,幻讀指的是一個事務在前后兩次查詢同一個范圍的時候,后一次查詢看到了前一次查詢沒有看到的行,需要的朋友可以參考下

準備工作

為了便于說明問題,這一篇文章,我們就先使用一個小一點兒的表。

 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);

這個表除了主鍵id外,還有一個索引c,初始化語句在表中插入了6行數(shù)據(jù)。

下面的語句序列,是怎么加鎖的,加的鎖又是什么時候釋放的呢?

begin;
 select * from t where d=5 for update;
 commit;

比較好理解的是,這個語句會命中d=5的這一行,對應的主鍵id=5,因此在select 語句執(zhí)行完成后,id=5這一行會加一個寫鎖,而且由于兩階段鎖協(xié)議,這個寫鎖會在執(zhí)行commit語句的時候釋放。

由于字段d上沒有索引,因此這條查詢語句會做全表掃描。那么,其他被掃描到的,但是不滿足條件的5行記錄上,會不會被加鎖呢?

我們知道,InnoDB的默認事務隔離級別是可重復讀,所以本文接下來沒有特殊說明的部分,都是設定在可重復讀隔離級別下。

幻讀是什么?

現(xiàn)在,我們就來分析一下,如果只在id=5這一行加鎖,而其他行的不加鎖的話,會怎么樣。

下面先來看一下這個場景(注意:這是我假設的一個場景):

可以看到,session A里執(zhí)行了三次查詢,分別是Q1、Q2和Q3。它們的SQL語句相同,都是select * from t where d=5 for update。這個語句的意思你應該很清楚了,查所有d=5的行,而且使用的是當前讀,并且加上寫鎖?,F(xiàn)在,我們來看一下這三條SQL語句,分別會返回什么結果。

  1. Q1只返回id=5這一行;
  2. 在T2時刻,session B把id=0這一行的d值改成了5,因此T3時刻Q2查出來的是id=0和id=5這兩行;
  3. 在T4時刻,session C又插入一行(1,1,5),因此T5時刻Q3查出來的是id=0、id=1和id=5的這三行。

其中,Q3讀到id=1這一行的現(xiàn)象,被稱為“幻讀”。也就是說,幻讀指的是一個事務在前后兩次查詢同一個范圍的時候,后一次查詢看到了前一次查詢沒有看到的行。

這里,我需要對“幻讀”做一個說明:

  1. 在可重復讀隔離級別下,普通的查詢是快照讀,是不會看到別的事務插入的數(shù)據(jù)的。因此,幻讀在“當前讀”下才會出現(xiàn)。
  2. 上面session B的修改結果,被session A之后的select語句用“當前讀”看到,不能稱為幻讀?;米x僅專指“新插入的行”。

因為這三個查詢都是加了for update,都是當前讀。而當前讀的規(guī)則,就是要能讀到所有已經(jīng)提交的記錄的最新值。并且,session B和sessionC的兩條語句,執(zhí)行后就會提交,所以Q2和Q3就是應該看到這兩個事務的操作效果,而且也看到了,這跟事務的可見性規(guī)則并不矛盾。

幻讀有什么問題?

首先是語義上的。session A在T1時刻就聲明了,“我要把所有d=5的行鎖住,不準別的事務進行讀寫操作”。而實際上,這個語義被破壞了。

如果現(xiàn)在這樣看感覺還不明顯的話,我再往session B和session C里面分別加一條SQL語句,你再看看會出現(xiàn)什么現(xiàn)象。

session B的第二條語句update t set c=5 where id=0,語義是“我把id=0、d=5這一行的c值,改成了5”。

由于在T1時刻,session A 還只是給id=5這一行加了行鎖, 并沒有給id=0這行加上鎖。因此,session B在T2時刻,是可以執(zhí)行這兩條update語句的。這樣,就破壞了 session A 里Q1語句要鎖住所有d=5的行的加鎖聲明。

session C也是一樣的道理,對id=1這一行的修改,也是破壞了Q1的加鎖聲明。

數(shù)據(jù)一致性問題

我們知道,鎖的設計是為了保證數(shù)據(jù)的一致性。而這個一致性,不止是數(shù)據(jù)庫內(nèi)部數(shù)據(jù)狀態(tài)在此刻的一致性,還包含了數(shù)據(jù)和日志在邏輯上的一致性。

為了說明這個問題,我給session A在T1時刻再加一個更新語句,即:update t set d=100 where d=5。

update的加鎖語義和select …for update 是一致的,所以這時候加上這條update語句也很合理。session A聲明說“要給d=5的語句加上鎖”,就是為了要更新數(shù)據(jù),新加的這條update語句就是把它認為加上了鎖的這一行的d值修改成了100。

現(xiàn)在,我們來分析一下圖3執(zhí)行完成后,數(shù)據(jù)庫里會是什么結果。

  1. 經(jīng)過T1時刻,id=5這一行變成 (5,5,100),當然這個結果最終是在T6時刻正式提交的;
  2. 經(jīng)過T2時刻,id=0這一行變成(0,5,5);
  3. 經(jīng)過T4時刻,表里面多了一行(1,5,5);
  4. 其他行跟這個執(zhí)行序列無關,保持不變。

這樣看,這些數(shù)據(jù)也沒啥問題,但是我們再來看看這時候binlog里面的內(nèi)容。

  1. T2時刻,session B事務提交,寫入了兩條語句;
  2. T4時刻,session C事務提交,寫入了兩條語句;
  3. T6時刻,session A事務提交,寫入了update t set d=100 where d=5 這條語句。

我統(tǒng)一放到一起的話,就是這樣的:

 update t set d=5 where id=0; /*(0,0,5)*/
 update t set c=5 where id=0; /*(0,5,5)*/
 ?
 insert into t values(1,1,5); /*(1,1,5)*/
 update t set c=5 where id=1; /*(1,5,5)*/
 ?
 update t set d=100 where d=5;/*所有d=5的行,d改成100*/

好,你應該看出問題了。這個語句序列,不論是拿到備庫去執(zhí)行,還是以后用binlog來克隆一個庫,這三行的結果,都變成了 (0,5,100)、(1,5,100)和(5,5,100)。

也就是說,id=0和id=1這兩行,發(fā)生了數(shù)據(jù)不一致。這個問題很嚴重,是不行的。

到這里,我們再回顧一下,這個數(shù)據(jù)不一致到底是怎么引入的?

我們分析一下可以知道,這是我們假設“select * from t where d=5 for update這條語句只給d=5這一行,也就是id=5的這一行加鎖”導致的。

所以我們認為,上面的設定不合理,要改。

那怎么改呢?我們把掃描過程中碰到的行,也都加上寫鎖,再來看看執(zhí)行效果。

由于session A把所有的行都加了寫鎖,所以session B在執(zhí)行第一個update語句的時候就被鎖住了。需要等到T6時刻session A提交以后,session B才能繼續(xù)執(zhí)行。

這樣對于id=0這一行,在數(shù)據(jù)庫里的最終結果還是 (0,5,5)。在binlog里面,執(zhí)行序列是這樣的:

 insert into t values(1,1,5); /*(1,1,5)*/
 update t set c=5 where id=1; /*(1,5,5)*/
 ?
 update t set d=100 where d=5;/*所有d=5的行,d改成100*/
 ?
 update t set d=5 where id=0; /*(0,0,5)*/
 update t set c=5 where id=0; /*(0,5,5)*/

可以看到,按照日志順序執(zhí)行,id=0這一行的最終結果也是(0,5,5)。所以,id=0這一行的問題解決了。

但同時你也可以看到,id=1這一行,在數(shù)據(jù)庫里面的結果是(1,5,5),而根據(jù)binlog的執(zhí)行結果是(1,5,100),也就是說幻讀的問題還是沒有解決。為什么我們已經(jīng)這么“兇殘”地,把所有的記錄都上了鎖,還是阻止不了id=1這一行的插入和更新呢?

原因很簡單。在T3時刻,我們給所有行加鎖的時候,id=1這一行還不存在,不存在也就加不上鎖。

也就是說,即使把所有的記錄都加上鎖,還是阻止不了新插入的記錄,這也是為什么“幻讀”會被單獨拿出來解決的原因。

現(xiàn)在你知道了,產(chǎn)生幻讀的原因是,行鎖只能鎖住行,但是新插入記錄這個動作,要更新的是記錄之間的“間隙”。因此,為了解決幻讀問題,InnoDB只好引入新的鎖,也就是間隙鎖(Gap Lock)。

顧名思義,間隙鎖,鎖的就是兩個值之間的空隙。比如文章開頭的表t,初始化插入了6個記錄,這就產(chǎn)生了7個間隙。

這樣,當你執(zhí)行 select * from t where d=5 for update的時候,就不止是給數(shù)據(jù)庫中已有的6個記錄加上了行鎖,還同時加了7個間隙鎖。這樣就確保了無法再插入新的記錄。

也就是說這時候,在一行行掃描的過程中,不僅將給行加上了行鎖,還給行兩邊的空隙,也加上了間隙鎖。

現(xiàn)在你知道了,數(shù)據(jù)行是可以加上鎖的實體,數(shù)據(jù)行之間的間隙,也是可以加上鎖的實體。但是間隙鎖跟我們之前碰到過的鎖都不太一樣。

比如行鎖,分成讀鎖和寫鎖。下圖就是這兩種類型行鎖的沖突關系。

也就是說,跟行鎖有沖突關系的是“另外一個行鎖”。

但是間隙鎖不一樣,跟間隙鎖存在沖突關系的,是“往這個間隙中插入一個記錄”這個操作。間隙鎖之間都不存在沖突關系。

這句話不太好理解,我給你舉個例子:

這里session B并不會被堵住。因為表t里并沒有c=7這個記錄,因此session A加的是間隙鎖(5,10)。而session B也是在這個間隙加的間隙鎖。它們有共同的目標,即:保護這個間隙,不允許插入值。但,它們之間是不沖突的。

間隙鎖和行鎖合稱next-key lock,每個next-key lock是前開后閉區(qū)間。也就是說,我們的表t初始化以后,如果用select * from t for update要把整個表所有記錄鎖起來,就形成了7個next-key lock,分別是 (-∞,0]、(0,5]、(5,10]、(10,15]、(15,20]、(20, 25]、(25, +supremum]。

你可能會問說,這個supremum從哪兒來的呢?

這是因為+∞是開區(qū)間。實現(xiàn)上,InnoDB給每個索引加了一個不存在的最大值supremum,這樣才符合我們前面說的“都是前開后閉區(qū)間”。

間隙鎖和next-key lock的引入,幫我們解決了幻讀的問題,但同時也帶來了一些“困擾”。

到此這篇關于Mysql中幻讀的概念以及如何解決的文章就介紹到這了,更多相關Mysql 幻讀概念內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • SQL之各種join小結詳細講解

    SQL之各種join小結詳細講解

    這篇文章主要介紹了SQL之各種join小結詳細講解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • MySQL數(shù)據(jù)庫的觸發(fā)器和事務

    MySQL數(shù)據(jù)庫的觸發(fā)器和事務

    這篇文章主要介紹了MySQL數(shù)據(jù)庫的觸發(fā)器和事務,觸發(fā)器是SQL?server提供給程序員和數(shù)據(jù)分析員來保證數(shù)據(jù)完整性的一種方法,它是與表事件相關的特殊的存儲過程,是由事件來觸發(fā)
    2022-08-08
  • mysql5.7.20 安裝配置方法圖文教程(mac)

    mysql5.7.20 安裝配置方法圖文教程(mac)

    這篇文章主要為大家詳細介紹了mac下mysql5.7.20 安裝配置方法圖文教程,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-11-11
  • 安裝配置mysql及Navicat prenium的詳細流程

    安裝配置mysql及Navicat prenium的詳細流程

    這篇文章主要介紹了安裝配置mysql及Navicat Premium的詳細流程,配置方法也真的很簡單,本文給大家詳細介紹mysql Navicat Premium安裝配置相關知識感興趣的朋友,一起學習吧
    2021-06-06
  • SQL注入漏洞過程實例及解決方案

    SQL注入漏洞過程實例及解決方案

    這篇文章主要介紹了SQL注入漏洞過程實例及解決方案,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-03-03
  • IDEA連接mysql又報錯!Server returns invalid timezone. Go to tab and set serverTimezone  prope的問題

    IDEA連接mysql又報錯!Server returns invalid timezone. Go to tab an

    這篇文章主要介紹了IDEA連接mysql又報錯!Server returns invalid timezone. Go to 'Advanced' tab and set 'serverTimezone' prope問題,本文通過圖文并茂的形式給大家介紹的非常詳細,需要的朋友可以參考下
    2020-05-05
  • mysql獲取group by總記錄行數(shù)的方法

    mysql獲取group by總記錄行數(shù)的方法

    這篇文章主要介紹了mysql獲取group by總記錄行數(shù)的方法,主要通過group by SQL_CALC_FOUND_ROWS語句來實現(xiàn)該功能,具有一定的實用價值,需要的朋友可以參考下
    2014-10-10
  • MySQL如何快速修改表的表結構

    MySQL如何快速修改表的表結構

    這篇文章主要介紹了MySQL如何快速修改表的表結構,幫助大家更好的理解和學習MySQL,感興趣的朋友可以了解下
    2020-08-08
  • MySQL存儲引擎InnoDB的配置與使用的講解

    MySQL存儲引擎InnoDB的配置與使用的講解

    今天小編就為大家分享一篇關于MySQL存儲引擎InnoDB的配置與使用的講解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-03-03
  • MySQL雙Master配置的方法詳解

    MySQL雙Master配置的方法詳解

    本篇文章是對MySQL雙Master配置進行了詳細的分析介紹,需要的朋友參考下
    2013-06-06

最新評論