亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

準(zhǔn)備工作

為了便于說明問題,這一篇文章,我們就先使用一個(gè)小一點(diǎn)兒的表。

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

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

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

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

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

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

我們知道,InnoDB的默認(rèn)事務(wù)隔離級(jí)別是可重復(fù)讀,所以本文接下來沒有特殊說明的部分,都是設(shè)定在可重復(fù)讀隔離級(jí)別下。

幻讀是什么?

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

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

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

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

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

這里,我需要對(duì)“幻讀”做一個(gè)說明:

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

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

幻讀有什么問題?

首先是語義上的。session A在T1時(shí)刻就聲明了,“我要把所有d=5的行鎖住,不準(zhǔn)別的事務(wù)進(jìn)行讀寫操作”。而實(shí)際上,這個(gè)語義被破壞了。

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

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

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

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

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

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

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

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

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

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

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

  1. T2時(shí)刻,session B事務(wù)提交,寫入了兩條語句;
  2. T4時(shí)刻,session C事務(wù)提交,寫入了兩條語句;
  3. T6時(shí)刻,session A事務(wù)提交,寫入了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*/

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

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

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

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

所以我們認(rèn)為,上面的設(shè)定不合理,要改。

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

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

這樣對(duì)于id=0這一行,在數(shù)據(jù)庫里的最終結(jié)果還是 (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這一行的最終結(jié)果也是(0,5,5)。所以,id=0這一行的問題解決了。

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

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

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

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

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

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

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

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

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

也就是說,跟行鎖有沖突關(guān)系的是“另外一個(gè)行鎖”。

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

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

這里session B并不會(huì)被堵住。因?yàn)楸韙里并沒有c=7這個(gè)記錄,因此session A加的是間隙鎖(5,10)。而session B也是在這個(gè)間隙加的間隙鎖。它們有共同的目標(biāo),即:保護(hù)這個(gè)間隙,不允許插入值。但,它們之間是不沖突的。

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

你可能會(huì)問說,這個(gè)supremum從哪兒來的呢?

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

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

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

相關(guān)文章

  • SQL之各種join小結(jié)詳細(xì)講解

    SQL之各種join小結(jié)詳細(xì)講解

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

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

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

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

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

    安裝配置mysql及Navicat prenium的詳細(xì)流程

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

    SQL注入漏洞過程實(shí)例及解決方案

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

    IDEA連接mysql又報(bào)錯(cuò)!Server returns invalid timezone. Go to tab an

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

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

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

    MySQL如何快速修改表的表結(jié)構(gòu)

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

    MySQL存儲(chǔ)引擎InnoDB的配置與使用的講解

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

    MySQL雙Master配置的方法詳解

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

最新評(píng)論