InnoDB鎖機(jī)制

數(shù)據(jù)事務(wù)設(shè)計(jì)遵循ACID的原則: 原子性(Atomicity)慨飘、一致性(Consistency)步势、隔離性(Isolation)窘茁、持久性(Durability)。一個(gè)支持事務(wù)(Transaction)的數(shù)據(jù)庫(kù)壁涎,必須要具有這四種特性凡恍,否則在事務(wù)過(guò)程(Transaction processing)當(dāng)中無(wú)法保證數(shù)據(jù)的正確性。

MySQL數(shù)據(jù)庫(kù)提供了四種默認(rèn)的隔離級(jí)別怔球,讀未提交(read-uncommitted)嚼酝、讀已提交(或不可重復(fù)讀)(read-committed)、可重復(fù)讀(repeatable-read)竟坛、串行化(serializable)闽巩。

MySQL的默認(rèn)隔離級(jí)別是RR。

一流码、鎖基本概念

1又官、共享鎖和排它鎖

InnoDB實(shí)現(xiàn)了兩種標(biāo)準(zhǔn)行級(jí)鎖延刘,一種是共享鎖(shared locks漫试,S鎖),另一種是獨(dú)占鎖碘赖,或者叫排它鎖(exclusive locks驾荣,X鎖)。

S鎖允許當(dāng)前持有該鎖的事務(wù)讀取行普泡。X鎖允許當(dāng)前持有該鎖的事務(wù)更新或刪除行播掷。

S鎖****:如果事務(wù)T1持有了行r上的S鎖,則其他事務(wù)可以同時(shí)持有行r的S鎖撼班,但是不能對(duì)行r加X(jué)鎖歧匈。

X鎖:如果事務(wù)T1持有了行r上的X鎖,則其他任何事務(wù)不能持有行r的X鎖砰嘁,必須等待T1在行r上的X鎖釋放件炉。

如果事務(wù)T1在行r上保持S鎖勘究,則另一個(gè)事務(wù)T2對(duì)行r的鎖的請(qǐng)求按如下方式處理:

  • T2可以同時(shí)持有S鎖;

  • T2如果想在行r上獲取X鎖斟冕,必須等待其他事務(wù)對(duì)該行添加的S鎖或X鎖的釋放口糕。

2、意向鎖-Intention Locks

InnoDB支持多種粒度的鎖磕蛇,允許行級(jí)鎖和表級(jí)鎖的共存景描。例如LOCK TABLES ... WRITE等語(yǔ)句可以在指定的表上加上獨(dú)占鎖。InnoBD使用意向鎖來(lái)實(shí)現(xiàn)多個(gè)粒度級(jí)別的鎖定秀撇。意向鎖是表級(jí)鎖超棺,表示table中的row所需要的鎖(S鎖或X鎖)的類(lèi)型。

意向鎖分為意向共享鎖(IS鎖)和意向排它鎖(IX鎖)呵燕。IS鎖表示當(dāng)前事務(wù)意圖在表中的行上設(shè)置共享鎖说搅,下面語(yǔ)句執(zhí)行時(shí)會(huì)首先獲取IS鎖,因?yàn)檫@個(gè)操作在獲取S鎖:

SELECT ... LOCK IN SHARE MODE

IX鎖表示當(dāng)前事務(wù)意圖在表中的行上設(shè)置排它鎖虏等。下面語(yǔ)句執(zhí)行時(shí)會(huì)首先獲取IX鎖弄唧,因?yàn)檫@個(gè)操作在獲取X鎖:

SELECT ... FOR UPDATE

事務(wù)要獲取某個(gè)表上的S鎖和X鎖之前,必須先分別獲取對(duì)應(yīng)的IS鎖和IX鎖霍衫。

3候引、鎖的兼容性

鎖的兼容矩陣如下:

抱歉,沒(méi)早點(diǎn)把這么全面的InnoDB鎖機(jī)制發(fā)給你

按照上面的兼容性敦跌,如果不同事務(wù)之間的鎖兼容澄干,則當(dāng)前加鎖事務(wù)可以持有鎖,如果有沖突則會(huì)等待其他事務(wù)的鎖釋放柠傍。

如果一個(gè)事務(wù)請(qǐng)求鎖時(shí)麸俘,請(qǐng)求的鎖與已經(jīng)持有的鎖沖突而無(wú)法獲取時(shí),互相等待就可能會(huì)產(chǎn)生死鎖惧笛。

意向鎖不會(huì)阻止除了全表鎖定請(qǐng)求之外的任何鎖請(qǐng)求从媚。 意向鎖的主要目的是顯示事務(wù)正在鎖定某行或者正意圖鎖定某行。

二患整、InnoDB中的鎖

常見(jiàn)的鎖有Record鎖拜效、gap鎖、next-key鎖各谚、插入意向鎖紧憾、自增鎖等。下面會(huì)對(duì)每一種鎖給出一個(gè)查看鎖的示例:

1昌渤、準(zhǔn)備工作

測(cè)試用表結(jié)構(gòu)

示例的基礎(chǔ)是一個(gè)只有兩列的數(shù)據(jù)庫(kù)表:

抱歉赴穗,沒(méi)早點(diǎn)把這么全面的InnoDB鎖機(jī)制發(fā)給你

數(shù)據(jù)表test只有兩列,id是主鍵索引,code是普通的索引(注意般眉,一定不要是唯一索引)加矛,并初始化了兩條記錄,分別是(1,1),(10,10)煤篙。這樣斟览,我們驗(yàn)證唯一鍵索引就可以使用id列,驗(yàn)證普通索引(非唯一鍵二級(jí)索引)時(shí)就使用code列辑奈。

查看鎖狀態(tài)的方式

要看到鎖的情況苛茂,必須手動(dòng)開(kāi)啟多個(gè)事務(wù),其中一些鎖的狀態(tài)的查看則必須使鎖處于waiting狀態(tài)鸠窗,這樣才能在mysql的引擎狀態(tài)日志中看到妓羊。

命令:

mysql> show engine innodb status;

這條命令能顯示最近幾個(gè)事務(wù)的狀態(tài)、查詢(xún)和寫(xiě)入情況等信息。當(dāng)出現(xiàn)死鎖時(shí),命令能給出最近的死鎖明細(xì)矛渴。

2、記錄鎖Record Locks

Record鎖

Record Lock是對(duì)索引記錄的鎖定净刮。

記錄鎖有兩種模式:S模式和X模式。例如:SELECT id FROM test WHERE id = 10 FOR UPDATE; 表示防止任何其他事務(wù)插入硅则、更新或者刪除id =10的行淹父。

記錄鎖始終只鎖定索引。即使表沒(méi)有建立索引怎虫,InnoDB也會(huì)創(chuàng)建一個(gè)隱藏的聚簇索引(隱藏的遞增主鍵索引)暑认,并使用此索引進(jìn)行記錄鎖定。

查看記錄鎖

開(kāi)啟第一個(gè)事務(wù)大审,不提交蘸际,測(cè)試完之后回滾。

抱歉徒扶,沒(méi)早點(diǎn)把這么全面的InnoDB鎖機(jī)制發(fā)給你

事務(wù)加鎖情況:

抱歉粮彤,沒(méi)早點(diǎn)把這么全面的InnoDB鎖機(jī)制發(fā)給你

可以看到有一行被加了鎖。由之前對(duì)鎖的描述可以推測(cè)出酷愧,update語(yǔ)句給id=1這一行上加了一個(gè)X鎖驾诈。

注意:X鎖廣義上是一種抽象意義的排它鎖缠诅,即鎖一般分為X模式和S模式溶浴,狹義上指row或者index上的鎖,而Record鎖是索引上的鎖管引。 為了不修改數(shù)據(jù)士败,可以用select ... for update語(yǔ)句,加鎖行為和update、delete是一樣的谅将,insert加鎖機(jī)制較為復(fù)雜漾狼,后面的章節(jié)會(huì)提到。

第一個(gè)事務(wù)保持原狀饥臂,不要提交或者回滾逊躁,現(xiàn)在開(kāi)啟第二個(gè)事務(wù):

抱歉,沒(méi)早點(diǎn)把這么全面的InnoDB鎖機(jī)制發(fā)給你

執(zhí)行update時(shí)隅熙,sql語(yǔ)句的執(zhí)行被阻塞了稽煤。查看下事務(wù)狀態(tài):

抱歉,沒(méi)早點(diǎn)把這么全面的InnoDB鎖機(jī)制發(fā)給你

喜聞樂(lè)見(jiàn)囚戚,我們看到了這個(gè)鎖的狀態(tài)酵熙。狀態(tài)標(biāo)題是'事務(wù)正在等待獲取鎖',描述中的lock_mode X locks rec but not gap就是本章節(jié)中的record記錄鎖驰坊,直譯一下'X鎖模式鎖住了記錄'匾二。后面還有一句but not gap意思是只對(duì)record本身加鎖,并不對(duì)間隙加鎖拳芙,間隙鎖的敘述見(jiàn)下面的內(nèi)容察藐。

3、間隙鎖Gap Locks

間隙鎖

間隙鎖作用在索引記錄之間的間隔舟扎,又或者作用在第一個(gè)索引之前转培,最后一個(gè)索引之后的間隙。不包括索引本身浆竭。

例如:

SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;

這條語(yǔ)句阻止其他事務(wù)插入10和20之間的數(shù)字浸须,無(wú)論這個(gè)數(shù)字是否存在。

間隙可以跨越0個(gè)邦泄,單個(gè)或多個(gè)索引值删窒。

間隙鎖是性能和并發(fā)權(quán)衡的產(chǎn)物,只存在于部分事務(wù)隔離級(jí)別顺囊。

select * from table where id=1;

唯一索引可以鎖定一行肌索,所以不需要間隙鎖鎖定。如果列沒(méi)有索引或者具有非唯一索引特碳,該語(yǔ)句會(huì)鎖定當(dāng)前索引前的間隙诚亚。

在同一個(gè)間隙上,不同的事務(wù)可以持有上述兼容/沖突表中沖突的兩個(gè)鎖午乓。例如站宗,事務(wù)T1現(xiàn)在持有一個(gè)間隙S鎖,T2可以同時(shí)在同一個(gè)間隙上持有間隙X鎖益愈。

允許沖突的鎖在間隙上鎖定的原因是梢灭,如果從索引中清除一條記錄夷家,則由不同事務(wù)在這條索引記錄上的加間隙鎖的動(dòng)作必須被合并。

InnoDB中的間隙鎖的唯一目的是防止其他事務(wù)插入間隙敏释。間隙鎖是可以共存的库快,一個(gè)事務(wù)占用的間隙鎖不會(huì)阻止另一個(gè)事務(wù)獲取同一個(gè)間隙上的間隙鎖。

如果事務(wù)隔離級(jí)別改為RC钥顽,則間隙鎖會(huì)被禁用义屏。

查看間隙鎖

按照官方文檔,where子句查詢(xún)條件是唯一鍵且指定了值時(shí)蜂大,只有record鎖湿蛔,沒(méi)有g(shù)ap鎖。

如果where語(yǔ)句指定了范圍县爬,gap鎖是存在的阳啥。

這里只測(cè)試驗(yàn)證一下當(dāng)指定非唯一鍵索引的時(shí)候,gap鎖的位置财喳,按照文檔的說(shuō)法察迟,會(huì)鎖定當(dāng)前索引及索引之前的間隙。(指定了非唯一鍵索引耳高,例如code=10扎瓶,間隙鎖仍然存在)

開(kāi)啟第一個(gè)事務(wù),鎖定一條非唯一的普通索引記錄:

抱歉泌枪,沒(méi)早點(diǎn)把這么全面的InnoDB鎖機(jī)制發(fā)給你

由于預(yù)存了兩條數(shù)據(jù)概荷,row(1,1)和row(10,10),此時(shí)這個(gè)間隙應(yīng)該是1<gap<10碌燕。我們先插入row(2,2)來(lái)驗(yàn)證下gap鎖的存在误证,再插入row(0,0)來(lái)驗(yàn)證gap的邊界。

按照間隙鎖的官方文檔定義修壕,select * from test where code = 10 for update; 會(huì)鎖定code=10這個(gè)索引愈捅,并且會(huì)鎖定code<10的間隙。

開(kāi)啟第二個(gè)事務(wù)慈鸠,在code=10之前的間隙中插入一條數(shù)據(jù)蓝谨,看下這條數(shù)據(jù)是否能夠插入:

抱歉,沒(méi)早點(diǎn)把這么全面的InnoDB鎖機(jī)制發(fā)給你

插入的時(shí)候青团,執(zhí)行被阻塞譬巫,查看引擎狀態(tài):

抱歉,沒(méi)早點(diǎn)把這么全面的InnoDB鎖機(jī)制發(fā)給你

插入語(yǔ)句被阻塞了督笆,lock_mode X locks gap before rec芦昔,由于第一個(gè)事務(wù)鎖住了1到10之間的gap,需要等待獲取鎖之后才能插入胖腾。

如果再開(kāi)啟一個(gè)事務(wù)烟零,插入(0,0):

抱歉瘪松,沒(méi)早點(diǎn)把這么全面的InnoDB鎖機(jī)制發(fā)給你

可以看到:指定的非唯一建索引的gap鎖的邊界是當(dāng)前索引到上一個(gè)索引之間的gap咸作。

最后給出鎖定區(qū)間的示例锨阿,首先插入一條記錄(5,5):

抱歉,沒(méi)早點(diǎn)把這么全面的InnoDB鎖機(jī)制發(fā)給你

開(kāi)啟第一個(gè)事務(wù):

抱歉记罚,沒(méi)早點(diǎn)把這么全面的InnoDB鎖機(jī)制發(fā)給你

第二個(gè)事務(wù)墅诡,試圖去更新code=5的行:

抱歉,沒(méi)早點(diǎn)把這么全面的InnoDB鎖機(jī)制發(fā)給你

執(zhí)行到這里桐智,如果第一個(gè)事務(wù)不提交或者回滾的話末早,第二個(gè)事務(wù)一直等待直至mysql中設(shè)定的超時(shí)時(shí)間。

4说庭、Next-key Locks

Next-key鎖

Next-key鎖實(shí)際上是Record鎖和gap鎖的組合然磷。Next-key鎖是在下一個(gè)索引記錄本身和索引之前的gap加上S鎖或是X鎖(如果是讀就加上S鎖,如果是寫(xiě)就加X(jué)鎖)刊驴。

默認(rèn)情況下姿搜,InnoDB的事務(wù)隔離級(jí)別為RR,系統(tǒng)參數(shù)
innodb_locks_unsafe_for_binlog的值為false捆憎。InnoDB使用next-key鎖對(duì)索引進(jìn)行掃描和搜索舅柜,這樣就讀取不到幻象行,避免了幻讀的發(fā)生躲惰。

幻讀是指在同一事務(wù)下致份,連續(xù)執(zhí)行兩次同樣的SQL語(yǔ)句,第二次的SQL語(yǔ)句可能會(huì)返回之前不存在的行础拨。

當(dāng)查詢(xún)的索引是唯一索引時(shí)氮块,Next-key lock會(huì)進(jìn)行優(yōu)化,降級(jí)為Record Lock诡宗,此時(shí)Next-key lock僅僅作用在索引本身雇锡,而不會(huì)作用于gap和下一個(gè)索引上。

查看Next-key鎖

  • Next-key鎖的作用范圍

如上述例子僚焦,數(shù)據(jù)表test初始化了row(1,1),row(10,10)锰提,然后插入了row(5,5)。數(shù)據(jù)表如下:

抱歉芳悲,沒(méi)早點(diǎn)把這么全面的InnoDB鎖機(jī)制發(fā)給你

由于id是主鍵立肘、唯一索引,mysql會(huì)做優(yōu)化名扛,因此使用code這個(gè)非唯一鍵的二級(jí)索引來(lái)舉例說(shuō)明谅年。對(duì)于code,可能的next-key鎖的范圍是:

(-∞,1]

(1,5]

(5,10]

(10,+∞)

開(kāi)啟第一個(gè)事務(wù)肮韧,在code=5的索引上請(qǐng)求更新:

抱歉融蹂,沒(méi)早點(diǎn)把這么全面的InnoDB鎖機(jī)制發(fā)給你

之前在gap鎖的章節(jié)中介紹了旺订,code=5 for update會(huì)在code=5的索引上加一個(gè)record鎖,還會(huì)在1<gap<5的間隙上加gap鎖〕迹現(xiàn)在不再驗(yàn)證区拳,直接插入一條(8,8):

抱歉,沒(méi)早點(diǎn)把這么全面的InnoDB鎖機(jī)制發(fā)給你

insert處于等待執(zhí)行的狀態(tài)意乓,這就是next-key鎖生效而導(dǎo)致的結(jié)果樱调。第一個(gè)事務(wù),鎖定了區(qū)間(1,5]届良,由于RR的隔離級(jí)別下next-key鎖處于開(kāi)啟生效狀態(tài)笆凌,又鎖定了(5,10]區(qū)間。所以插入SQL語(yǔ)句的執(zhí)行被阻塞士葫。

解釋?zhuān)涸谶@種情況下乞而,被鎖定的區(qū)域是code=5前一個(gè)索引到它的間隙,以及next-key的區(qū)域慢显。code=5 for update對(duì)索引的鎖定用區(qū)間表示爪模,gap鎖鎖定了(1,5),record鎖鎖定了{(lán)5}索引記錄鳍怨,next-key鎖鎖住了(5,10]呻右,也就是說(shuō)整個(gè)(1,10]的區(qū)間被鎖定了。由于是for update鞋喇,所以這里的鎖都是X鎖声滥,因此阻止了其他事務(wù)中帶有沖突鎖定的操作執(zhí)行。

如果我們?cè)诘谝粋€(gè)事務(wù)中侦香,執(zhí)行了code>8 for update落塑,在掃描過(guò)程中,找到了code=10罐韩,此時(shí)就會(huì)鎖住10之前的間隙(5到10之間的gap)憾赁,10本身(record),和10之后的間隙(next-key)散吵。此時(shí)另一個(gè)事務(wù)插入(6,6),(9,9)和(11,11)都是不被允許的龙考,只有在前一個(gè)索引5及5之前的索引和間隙才能執(zhí)行插入(更新和刪除也會(huì)被阻塞)。

5矾睦、插入意向鎖

插入意向鎖在行插入之前由INSERT設(shè)置一種間隙鎖晦款,是意向排它鎖的一種。在多事務(wù)同時(shí)寫(xiě)入不同數(shù)據(jù)至同一索引間隙的時(shí)枚冗,不會(huì)發(fā)生鎖等待缓溅,事務(wù)之間互相不影響其他事務(wù)的完成,這和間隙鎖的定義是一致的赁温。

假設(shè)一個(gè)記錄索引包含4和7坛怪,其他不同的事務(wù)分別插入5和6淤齐,此時(shí)只要行不沖突,插入意向鎖不會(huì)互相等待袜匿,可以直接獲取更啄。參照鎖兼容/沖突矩陣。

插入意向鎖的例子不再列舉沉帮,可以查看gap鎖的第一個(gè)例子锈死。

6贫堰、自增鎖

自增鎖(AUTO-INC Locks)是事務(wù)插入時(shí)自增列上特殊的表級(jí)別的鎖穆壕。最簡(jiǎn)單的一種情況:如果一個(gè)事務(wù)正在向表中插入值,則任何其他事務(wù)必須等待其屏,以便第一個(gè)事務(wù)插入的行接收連續(xù)的主鍵值喇勋。

我們一般把主鍵設(shè)置為AUTO_INCREMENT的列,默認(rèn)情況下這個(gè)字段的值為0偎行,InnoDB會(huì)在AUTO_INCREMENT修飾下的數(shù)據(jù)列所關(guān)聯(lián)的索引末尾設(shè)置獨(dú)占鎖川背。

在訪問(wèn)自增計(jì)數(shù)器時(shí),InnoDB使用自增鎖蛤袒,但是鎖定僅僅持續(xù)到當(dāng)前SQL語(yǔ)句的末尾熄云,而不是整個(gè)事務(wù)的結(jié)束,畢竟自增鎖是表級(jí)別的鎖妙真,如果長(zhǎng)期鎖定會(huì)大大降低數(shù)據(jù)庫(kù)的性能缴允。由于是表鎖,在使用期間珍德,其他會(huì)話無(wú)法插入表中练般。

三、幻讀

這一部分锈候,我們將通過(guò)幻讀薄料,逐步展開(kāi)對(duì)InnoDB鎖的探究。

1泵琳、幻讀概念

解釋了不同概念的鎖的作用域摄职,我們來(lái)看一下幻讀到底是什么。

幻讀在RR條件下是不會(huì)出現(xiàn)的获列。因?yàn)镽R是Repeatable Read谷市,它是一種事務(wù)的隔離級(jí)別,直譯過(guò)來(lái)也就是“在同一個(gè)事務(wù)中蛛倦,同樣的查詢(xún)語(yǔ)句的讀取是可重復(fù)”歌懒,也就是說(shuō)他不會(huì)讀到”幻影行”(其他事務(wù)已經(jīng)提交的變更),它讀到的只能是重復(fù)的(無(wú)論在第一次查詢(xún)之后其他事務(wù)做了什么操作溯壶,第二次查詢(xún)結(jié)果與第一次相同)及皂。

上面的例子都是使用for update甫男,這種讀取操作叫做當(dāng)前讀,對(duì)于普通的select語(yǔ)句均為快照讀验烧。

當(dāng)前讀板驳,又叫加鎖讀,或者阻塞讀碍拆。這種讀取操作不再是讀取快照若治,而是讀取最新版本并且加鎖「谢欤快照讀不會(huì)添加任何鎖端幼。

官方文檔對(duì)于幻讀的定義是這樣的:

原文:The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a SELECT is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom”row.

手動(dòng)無(wú)腦翻譯:所謂的幻影行問(wèn)題是指,在同一個(gè)事務(wù)中弧满,同樣的查詢(xún)語(yǔ)句執(zhí)行多次婆跑,得到了不同的結(jié)果,這就是幻讀庭呜。例如:如果同一個(gè)SELECT語(yǔ)句執(zhí)行了兩次滑进,第二次執(zhí)行的時(shí)候比第一次執(zhí)行時(shí)多出一行,則該行就是所謂的幻影行募谎。

其中這一句:

“The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times.”

這看起來(lái)應(yīng)該是不可重復(fù)讀的定義扶关,同樣的查詢(xún)得到了不同的結(jié)果(兩次結(jié)果不是重復(fù)的),但是后面的舉例給出了幻讀真正的定義数冬,第二次比第一次多出了一行节槐。

也就是說(shuō),幻讀的出現(xiàn)有這樣一個(gè)前提吉执,第二次查詢(xún)前其他事務(wù)提交了一個(gè)INSERT插入語(yǔ)句疯淫。而不可重復(fù)讀出現(xiàn)的前提是第二次查詢(xún)前其他事務(wù)提交了UPDATE或者DELETE操作。

mysql的快照讀戳玫,使得在RR的隔離級(jí)別上在next-Key的作用區(qū)間內(nèi)熙掺,制造了一個(gè)快照副本,這個(gè)副本是隔離的咕宿,無(wú)論副本對(duì)應(yīng)的區(qū)間里的數(shù)據(jù)被其他事務(wù)如何修改币绩,在當(dāng)前事務(wù)中,取到的數(shù)據(jù)永遠(yuǎn)是副本中的數(shù)據(jù)府阀。

RR級(jí)別下之所以可以讀到之前版本的數(shù)據(jù)缆镣,是由于數(shù)據(jù)庫(kù)的MVCC(Multi-Version Concurrency Control,多版本并發(fā)控制)试浙。

參見(jiàn)InnoDB Multi-Versioning

https://dev.mysql.com/doc/refman/5.7/en/innodb-multi-versioning.html

有些文章中提到“RR也不能完全避免幻讀”董瞻,實(shí)際上官方文檔實(shí)際要表達(dá)的意義是“在同一個(gè)事務(wù)內(nèi),多次連續(xù)查詢(xún)的結(jié)果是一樣的,不會(huì)因其他事務(wù)的修改而導(dǎo)致不同的查詢(xún)結(jié)果”钠糊,這里先給出實(shí)驗(yàn)結(jié)論:

  • 當(dāng)前事務(wù)如果未發(fā)生更新操作(增刪改)挟秤,快照版本會(huì)保持不變,多次查詢(xún)讀取的副本是同一個(gè)抄伍;

  • 當(dāng)前事務(wù)如果發(fā)生更新(增刪改)艘刚,再次查詢(xún)時(shí),會(huì)刷新快照版本截珍。

2攀甚、RC級(jí)別下的幻讀

RC情況下會(huì)出現(xiàn)幻讀。首先設(shè)置隔離級(jí)別為RC:

SET SESSION tx_isolation='READ-COMMITTED';

抱歉岗喉,沒(méi)早點(diǎn)把這么全面的InnoDB鎖機(jī)制發(fā)給你

RC(Read Commit)隔離級(jí)別可以避免臟讀秋度,事務(wù)內(nèi)無(wú)法獲取其他事務(wù)未提交的變更,但是由于能夠讀到已經(jīng)提交的事務(wù)沈堡,因此會(huì)出現(xiàn)幻讀和不重復(fù)讀静陈。也就是說(shuō)燕雁,RC的快照讀是讀取最新版本數(shù)據(jù)诞丽,而RR的快照讀是讀取被next-key鎖作用區(qū)域的副本。

3拐格、RR級(jí)別下能否避免幻讀僧免?

我們先來(lái)模擬一下RR隔離級(jí)別下沒(méi)有出現(xiàn)幻讀的情況:

開(kāi)啟第一個(gè)事務(wù)并執(zhí)行一次快照查詢(xún):

抱歉,沒(méi)早點(diǎn)把這么全面的InnoDB鎖機(jī)制發(fā)給你

這兩個(gè)事務(wù)的執(zhí)行捏浊,有兩個(gè)問(wèn)題:

  • 為什么之前的例子中懂衩,在第二個(gè)事務(wù)的INSERT被阻塞了,而這次卻執(zhí)行成功了金踪。

    這是因?yàn)樵瓉?lái)的語(yǔ)句中帶有for update浊洞,這種讀取是當(dāng)前讀,會(huì)加鎖胡岔。而本次第一個(gè)事務(wù)中的SELECT僅僅是快照讀法希,沒(méi)有加任何鎖。所以不會(huì)阻塞其他的插入靶瘸。

  • 數(shù)據(jù)庫(kù)中的數(shù)據(jù)已經(jīng)改變苫亦,為什么會(huì)讀不到?

    這個(gè)就是之前提到的next-key lock鎖定的副本怨咪。RC及以下級(jí)別才會(huì)讀到已經(jīng)提交的事務(wù)屋剑。更多的業(yè)務(wù)邏輯是希望在某段時(shí)間內(nèi)或者某個(gè)特定的邏輯區(qū)間中,前后查詢(xún)到的數(shù)據(jù)是一致的诗眨,當(dāng)前事務(wù)是和其他事務(wù)隔離的唉匾。這也是數(shù)據(jù)庫(kù)在設(shè)計(jì)實(shí)現(xiàn)時(shí)遵循的ACID原則。

再給出RR條件下出現(xiàn)幻讀的情形匠楚,這種情形不需要兩個(gè)事務(wù)巍膘,一個(gè)事務(wù)就已經(jīng)可以說(shuō)明:

抱歉卫病,沒(méi)早點(diǎn)把這么全面的InnoDB鎖機(jī)制發(fā)給你

至于RR隔離級(jí)別下到底會(huì)不會(huì)出現(xiàn)幻讀,就需要看幻讀的定義中的查詢(xún)到底是連續(xù)的查詢(xún)還是不連續(xù)的查詢(xún)典徘。如果認(rèn)為RR級(jí)別下可能會(huì)出現(xiàn)幻讀蟀苛,那該級(jí)別下也會(huì)出現(xiàn)不重復(fù)讀。

RR隔離級(jí)別下逮诲,雖然不會(huì)出現(xiàn)幻讀帜平,但是會(huì)因此產(chǎn)生其他的問(wèn)題。

前提:當(dāng)前數(shù)據(jù)表中只存在(1,1),(5,5),(10,10)三組數(shù)據(jù)梅鹦。

如果數(shù)據(jù)庫(kù)隔離級(jí)別不是默認(rèn)裆甩,可以執(zhí)行SET SESSION tx_isolation='REPEATABLE-READ';(該語(yǔ)句不是全局設(shè)置)更新為RR。

然后執(zhí)行下列操作:

抱歉齐唆,沒(méi)早點(diǎn)把這么全面的InnoDB鎖機(jī)制發(fā)給你

4嗤栓、更新丟失(Lost Update)

更新丟失

除了上述這類(lèi)問(wèn)題外,RR還會(huì)有丟失更新的問(wèn)題箍邮。如下表給出的操作:

抱歉茉帅,沒(méi)早點(diǎn)把這么全面的InnoDB鎖機(jī)制發(fā)給你

這個(gè)例子里,事務(wù)一的更新是無(wú)效的锭弊,盡管在這個(gè)事務(wù)里程序認(rèn)為還存在(10,10)記錄堪澎。事務(wù)一中更新之前的SELECT操作是快照讀,所以讀到了快照里的(10,10)味滞,而UPDATE中的WHERE子句是當(dāng)前讀樱蛤,取得是最新版本的數(shù)據(jù),所以matched: 0 Changed: 0

如果上述例子中的操作是對(duì)同一條記錄做修改剑鞍,就會(huì)引起更新丟失昨凡。例如,事務(wù)一和二同時(shí)開(kāi)啟蚁署,事務(wù)一先執(zhí)行update test set code=100 where id=10;便脊,事務(wù)二再執(zhí)行update test set code=200 where id=10;,事務(wù)一的更新就會(huì)被覆蓋形用。

這就是經(jīng)典的丟失更新問(wèn)題就轧,英文叫Lost Update,又叫提交覆蓋田度,因?yàn)槭亲詈髨?zhí)行更新的事務(wù)提交導(dǎo)致的覆蓋妒御。還有一種更新丟失叫做回滾覆蓋,即一個(gè)事務(wù)的回滾把另一個(gè)事務(wù)提交的數(shù)據(jù)給回滾覆蓋了镇饺,但是目前市面上所有的數(shù)據(jù)庫(kù)都不支持這種stupid的操作乎莉,因此不再詳述。

樂(lè)觀鎖與悲觀鎖

這種情況下,引入我們常見(jiàn)的兩種方式來(lái)解決該問(wèn)題:

  • 樂(lè)觀鎖:在UPDATE的WHERE子句中加入版本號(hào)信息來(lái)確定修改是否生效惋啃;

  • 悲觀鎖:在UPDATE執(zhí)行前哼鬓,SELECT后面加上FOR UPDATE來(lái)給記錄加鎖,保證記錄在UPDATE前不被修改边灭。SELECT ... FOR UPDATE是加上了X鎖异希,也可以通過(guò)SELECT ... LOCK IN SHARE MODE加上S鎖,來(lái)防止其他事務(wù)對(duì)該行的修改绒瘦。

無(wú)論是樂(lè)觀鎖還是悲觀鎖称簿,使用的思想都是一致的,那就是當(dāng)前讀惰帽。樂(lè)觀鎖利用當(dāng)前讀判斷是否是最新版本憨降,悲觀鎖利用當(dāng)前讀鎖定行。

但是使用樂(lè)觀鎖時(shí)仍然需要非常謹(jǐn)慎该酗,因?yàn)镽R是可重復(fù)讀的授药,一定不能在UPDATE之前先把版本號(hào)使用快照讀獲取出來(lái)。

四呜魄、InnoDB對(duì)不同語(yǔ)句執(zhí)行時(shí)

的加鎖狀況

如果一個(gè)SQL語(yǔ)句要對(duì)二級(jí)索引(非主鍵索引)設(shè)置X模式的Record鎖悔叽,InnoDB還會(huì)檢索出相應(yīng)的聚簇索引(主鍵索引)并對(duì)它們?cè)O(shè)置鎖定。

1耕赘、SELECT ... FROM...不加鎖

SELECT ... FROM是快照讀取骄蝇,除了SERIALIZABLE的事務(wù)隔離級(jí)別,該SQL語(yǔ)句執(zhí)行時(shí)不會(huì)加任何鎖操骡。

SERIALIZABLE級(jí)別下,SELECT語(yǔ)句的執(zhí)行會(huì)在遇到的索引記錄上設(shè)置S模式的next-key鎖赚窃。但是對(duì)于唯一索引册招,只鎖定索引記錄,而不會(huì)鎖定gap勒极。

2是掰、UPDATE系列

S鎖讀取(SELECT ... LOCK IN SHARE MODE),X鎖讀取(SELECT ... FOR UPDATE)辱匿、更新UPDATE和刪除DELETE這四類(lèi)語(yǔ)句键痛,采用的鎖取決于搜索條件中使用的索引類(lèi)型。

  • 如果使用唯一索引匾七,InnoDB僅鎖定索引記錄本身絮短,不鎖定間隙;

  • 如果使用非唯一索引昨忆,或者未命中索引丁频,InnoDB使用間隙鎖或者next-key鎖來(lái)鎖定索引范圍,這樣就可以阻止其他事務(wù)插入鎖定范圍。

UPDATE語(yǔ)句

UPDATE ... WHERE ... 在搜索遇到的每條記錄上設(shè)置一個(gè)獨(dú)占的next-key鎖席里,如果是唯一索引只鎖定記錄叔磷。

當(dāng)UPDATE修改聚簇索引時(shí),將對(duì)受影響的二級(jí)索引采用隱式鎖奖磁,隱式鎖是在索引中對(duì)二級(jí)索引的記錄邏輯加鎖改基,實(shí)際上不產(chǎn)生鎖對(duì)象,不占用內(nèi)存空間咖为。

例如update test set code=100 where id=10;執(zhí)行的時(shí)候code=10的索引(code是二級(jí)索引寥裂,見(jiàn)文中給出的建表語(yǔ)句)會(huì)被加隱式鎖,只有隱式鎖產(chǎn)生沖突時(shí)才會(huì)變成顯式鎖(如S鎖案疲、X鎖)封恰。即此時(shí)另一個(gè)事務(wù)也去更新id=10這條記錄,隱式鎖就會(huì)升級(jí)為顯示鎖褐啡。

這樣做的好處是降低了鎖的開(kāi)銷(xiāo)诺舔。

UPDATE可能會(huì)導(dǎo)致新的普通索引的插入。當(dāng)新的索引插入之前备畦,會(huì)首先執(zhí)行一次重復(fù)索引檢查低飒。在重復(fù)檢查和插入時(shí),更新操作會(huì)對(duì)受影響的二級(jí)索引記錄采用共享鎖定(S鎖)懂盐。

DELETE語(yǔ)句

DELETE FROM ... WHERE ... 在搜索遇到的每條記錄上設(shè)置一個(gè)獨(dú)占的next-key鎖褥赊,如果是唯一索引只鎖定記錄。

3莉恼、INSERT

INSERT區(qū)別于UPDATE系列單獨(dú)列出拌喉,是因?yàn)樗奶幚矸绞捷^為特別癞谒。

插入行之前夏志,會(huì)設(shè)置一種插入意向鎖梢什,插入意向鎖表示插入的意圖窿侈。如果其它事務(wù)在要插入的位置上設(shè)置了X鎖逗物,則無(wú)法獲取插入意向鎖括勺,插入操作也因此阻塞宇植。

INSERT在插入的行上設(shè)置X鎖兑徘。該鎖是一個(gè)Record鎖吱七,并不是next-key鎖汽久,即只鎖定記錄本身,不鎖定間隙踊餐,因此不會(huì)阻止其他會(huì)話在這行記錄前的間隙中插入新的記錄景醇。具體的加鎖過(guò)程見(jiàn)下文。

五市袖、可能的死鎖場(chǎng)景

1啡直、Duplicate key error引發(fā)的死鎖

并發(fā)條件下烁涌,唯一鍵索引沖突可能會(huì)導(dǎo)致死鎖,這種死鎖一般分為兩種:一種是rollback引發(fā)酒觅,另一種是commit引發(fā)撮执。

rollback引發(fā)的Duplicate key死鎖

我命名為
insert-insert-insert-rollback死鎖:

抱歉,沒(méi)早點(diǎn)把這么全面的InnoDB鎖機(jī)制發(fā)給你

當(dāng)事務(wù)一執(zhí)行回滾時(shí)舷丹,事務(wù)二和事務(wù)三發(fā)生了死鎖抒钱。InnoDB的死鎖檢測(cè)一旦檢測(cè)到死鎖發(fā)生,會(huì)自動(dòng)失敗其中一個(gè)事務(wù)颜凯,因此看到的結(jié)果是一個(gè)失敗另一個(gè)成功谋币。

  • 為什么會(huì)死鎖?

死鎖產(chǎn)生的原因是事務(wù)一插入記錄時(shí)症概,對(duì)(2,2)記錄加X(jué)鎖蕾额,此時(shí)事務(wù)二和事務(wù)三插入數(shù)據(jù)時(shí)檢測(cè)到了重復(fù)鍵錯(cuò)誤,事務(wù)二和事務(wù)三要在這條索引記錄上設(shè)置S鎖彼城,由于X鎖的存在诅蝶,S鎖的獲取被阻塞。

事務(wù)一回滾募壕,由于S鎖和S鎖是可以兼容的调炬,因此事務(wù)二和事務(wù)三都獲得了這條記錄的S鎖。此時(shí)其中一個(gè)事務(wù)希望插入舱馅,則該事務(wù)期望在這條記錄上加上X鎖缰泡,然而另一個(gè)事務(wù)持有S鎖,S鎖和X鎖互相是不兼容的代嗤,兩個(gè)事務(wù)就開(kāi)始互相等待對(duì)方的鎖釋放棘钞,造成了死鎖。

  • 事務(wù)二和事務(wù)三為什么會(huì)加S鎖资溃,而不是直接等待X鎖武翎?

事務(wù)一的insert語(yǔ)句加的是隱式鎖(隱式的Record鎖、X鎖)溶锭,但是其他事務(wù)插入同一行記錄時(shí),出現(xiàn)了唯一鍵沖突符隙,事務(wù)一的隱式鎖升級(jí)為顯示鎖趴捅。

事務(wù)二和事務(wù)三在插入之前判斷到了唯一鍵沖突,是因?yàn)椴迦肭暗闹貜?fù)索引檢查霹疫,這次檢查必須進(jìn)行一次當(dāng)前讀拱绑,于是非唯一索引就會(huì)被加上S模式的next-key鎖,唯一索引就被加上了S模式的Record鎖丽蝎。

因?yàn)椴迦牒透轮岸家M(jìn)行重復(fù)索引檢查而執(zhí)行當(dāng)前讀操作猎拨,所以RR隔離級(jí)別下膀藐,同一個(gè)事務(wù)內(nèi)不連續(xù)的查詢(xún),可能也會(huì)出現(xiàn)幻讀的效果(但個(gè)人并不認(rèn)為RR級(jí)別下也會(huì)出現(xiàn)幻讀红省,幻讀的定義應(yīng)該是連續(xù)的讀取)额各。而連續(xù)的查詢(xún)由于都是讀取快照,中間沒(méi)有當(dāng)前讀的操作吧恃,所以不會(huì)出現(xiàn)幻讀虾啦。

commit引發(fā)的Duplicate key死鎖

delete-insert-insert-commit死鎖:

抱歉,沒(méi)早點(diǎn)把這么全面的InnoDB鎖機(jī)制發(fā)給你

這種情況下產(chǎn)生的死鎖和
insert-insert-insert-rollback死鎖產(chǎn)生的原理一致痕寓。

2傲醉、數(shù)據(jù)插入的過(guò)程

經(jīng)過(guò)以上分析,一條數(shù)據(jù)在插入時(shí)經(jīng)過(guò)以下幾個(gè)過(guò)程:

假設(shè)數(shù)據(jù)表test.test中存在(1,1)呻率、(5,5)和(10,10)三條記錄硬毕。

  • 事務(wù)開(kāi)啟,嘗試獲取插入意向鎖礼仗。例如吐咳,事務(wù)一執(zhí)行了select * from test where id>8 for update,事務(wù)二要插入(9,9)藐守,此時(shí)先要獲取插入意向鎖挪丢,由于事務(wù)一已經(jīng)在對(duì)應(yīng)的記錄和間隙上加了X鎖,因此事務(wù)二被阻塞卢厂,并且阻塞的原因是獲取插入意向鎖時(shí)被事務(wù)一的X鎖阻塞乾蓬。

  • 獲取意向鎖之后,插入之前進(jìn)行重復(fù)索引檢查慎恒。重復(fù)索引檢查為當(dāng)前讀任内,需要添加S鎖。

  • 如果是已經(jīng)存在唯一索引融柬,且索引未加鎖死嗦。直接拋出Duplicate key的錯(cuò)誤。如果存在唯一索引粒氧,且索引加鎖越除,等待鎖釋放。

  • 重復(fù)檢查通過(guò)之后外盯,加入X鎖摘盆,插入記錄

3、GAP與Insert Intention沖突引發(fā)死鎖

update-insert死鎖

仍然是表test饱苟,當(dāng)前表中的記錄如下:

抱歉孩擂,沒(méi)早點(diǎn)把這么全面的InnoDB鎖機(jī)制發(fā)給你
抱歉,沒(méi)早點(diǎn)把這么全面的InnoDB鎖機(jī)制發(fā)給你

使用show engine innodb status查看死鎖狀態(tài)箱熬。先后出現(xiàn)lock_mode X locks gap before rec insert intention waiting和lock_mode X locks gap before rec字眼类垦,是gap鎖和插入意向鎖的沖突導(dǎo)致的死鎖狈邑。

回顧select...for update的加鎖范圍

首先回顧一下兩個(gè)事務(wù)中的select ... for update做了哪些加鎖操作:

code=5時(shí),首先會(huì)獲取code=5的索引記錄鎖(Record鎖)蚤认,根據(jù)之前gap鎖的介紹米苹,會(huì)在前一個(gè)索引和當(dāng)前索引之間的間隙加鎖,于是區(qū)間(1,5)之間被加上了X模式的gap鎖烙懦。除此之外RR模式下驱入,還會(huì)加next-key鎖,于是區(qū)間(5,10]被加了next-key鎖氯析。

  • 因此亏较,code=5的加鎖范圍是,區(qū)間(1,5)的gap鎖掩缓,{5}索引Record鎖雪情,(5,10]的next-key鎖。即區(qū)間(1,10)上都被加上了X模式的鎖你辣。

  • 同理巡通,code=10的加鎖范圍是,區(qū)間(5,10)的gap鎖舍哄,{10}索引Record鎖宴凉,(10,+∞)的next-key鎖。

由gap鎖的特性表悬,兼容矩陣中沖突的鎖也可以被不同的事務(wù)同時(shí)加在一個(gè)間隙上弥锄。上述兩個(gè)select ... for update語(yǔ)句出現(xiàn)了間隙鎖的交集,code=5的next-key鎖和code=10的gap鎖有重疊的區(qū)域——(5,10)蟆沫。

死鎖的成因

當(dāng)事務(wù)一執(zhí)行插入語(yǔ)句時(shí)籽暇,會(huì)先加X(jué)模式的插入意向鎖,即兼容矩陣中的IX鎖饭庞。但是由于插入意向鎖要鎖定的位置存在X模式的gap鎖戒悠。兼容矩陣中IX和X鎖是不兼容的,因此事務(wù)一的IX鎖會(huì)等待事務(wù)二的gap鎖釋放舟山。

事務(wù)二也執(zhí)行插入語(yǔ)句绸狐,與事務(wù)一同樣,事務(wù)二的插入意向鎖IX鎖會(huì)等待事務(wù)一的gap鎖釋放累盗。

兩個(gè)事務(wù)互相等待對(duì)方先釋放鎖六孵,因此出現(xiàn)死鎖。

六幅骄、總結(jié)

除了以上給出的幾種死鎖模式,還有很多其他死鎖的場(chǎng)景本今。

無(wú)論是哪種場(chǎng)景拆座,萬(wàn)變不離其宗主巍,都是由于某個(gè)區(qū)間上或者某一個(gè)記錄上可以同時(shí)持有鎖,例如不同事務(wù)在同一個(gè)間隙gap上的鎖不沖突挪凑;不同事務(wù)中孕索,S鎖可以阻塞X鎖的獲取,但是不會(huì)阻塞另一個(gè)事務(wù)獲取該S鎖躏碳。這樣才會(huì)出現(xiàn)兩個(gè)事務(wù)同時(shí)持有鎖搞旭,并互相等待,最終導(dǎo)致死鎖菇绵。

其中需要注意的點(diǎn)是肄渗,增、刪咬最、改的操作都會(huì)進(jìn)行一次當(dāng)前讀操作翎嫡,以此獲取最新版本的數(shù)據(jù),并檢測(cè)是否有重復(fù)的索引永乌。這個(gè)過(guò)程除了會(huì)導(dǎo)致RR隔離級(jí)別下出現(xiàn)死鎖之外還會(huì)導(dǎo)致其他兩個(gè)問(wèn)題:

  • 第一個(gè)是可重復(fù)讀可能會(huì)因?yàn)檫@次的當(dāng)前讀操作而中斷惑申,(同樣,幻讀可能也會(huì)因此產(chǎn)生)翅雏;

  • 第二個(gè)是其他事務(wù)的更新可能會(huì)丟失(解決方式:悲觀鎖圈驼、樂(lè)觀鎖)。

轉(zhuǎn)自:https://www.toutiao.com/i6669505644367708680/?tt_from=mobile_qq&utm_campaign=client_share&timestamp=1589933295&app=news_article&utm_source=mobile_qq&utm_medium=toutiao_android&use_new_style=1&req_id=20200520080815010026076082031ABF7D&group_id=6669505644367708680

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末望几,一起剝皮案震驚了整個(gè)濱河市绩脆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌橄妆,老刑警劉巖衙伶,帶你破解...
    沈念sama閱讀 222,627評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異害碾,居然都是意外死亡矢劲,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)慌随,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)芬沉,“玉大人,你說(shuō)我怎么就攤上這事阁猜⊥枰荩” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,346評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵剃袍,是天一觀的道長(zhǎng)黄刚。 經(jīng)常有香客問(wèn)我,道長(zhǎng)民效,這世上最難降的妖魔是什么憔维? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,097評(píng)論 1 300
  • 正文 為了忘掉前任涛救,我火速辦了婚禮,結(jié)果婚禮上业扒,老公的妹妹穿的比我還像新娘检吆。我一直安慰自己,他們只是感情好程储,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,100評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布蹭沛。 她就那樣靜靜地躺著,像睡著了一般章鲤。 火紅的嫁衣襯著肌膚如雪摊灭。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,696評(píng)論 1 312
  • 那天咏窿,我揣著相機(jī)與錄音斟或,去河邊找鬼。 笑死集嵌,一個(gè)胖子當(dāng)著我的面吹牛萝挤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播根欧,決...
    沈念sama閱讀 41,165評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼怜珍,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了凤粗?” 一聲冷哼從身側(cè)響起酥泛,我...
    開(kāi)封第一講書(shū)人閱讀 40,108評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嫌拣,沒(méi)想到半個(gè)月后柔袁,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,646評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡异逐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,709評(píng)論 3 342
  • 正文 我和宋清朗相戀三年捶索,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片灰瞻。...
    茶點(diǎn)故事閱讀 40,861評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡腥例,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出酝润,到底是詐尸還是另有隱情燎竖,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布要销,位于F島的核電站构回,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜捐凭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,196評(píng)論 3 336
  • 文/蒙蒙 一拨扶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧茁肠,春花似錦、人聲如沸缩举。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,698評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)仅孩。三九已至托猩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間辽慕,已是汗流浹背京腥。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,804評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留溅蛉,地道東北人公浪。 一個(gè)月前我還...
    沈念sama閱讀 49,287評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像船侧,于是被迫代替她去往敵國(guó)和親欠气。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,860評(píng)論 2 361