重新學(xué)習(xí)Mysql數(shù)據(jù)庫(kù)2:『淺入淺出』MySQL 和 InnoDB

本文轉(zhuǎn)自互聯(lián)網(wǎng)

本系列文章將整理到我在GitHub上的《Java面試指南》倉(cāng)庫(kù),更多精彩內(nèi)容請(qǐng)到我的倉(cāng)庫(kù)里查看

https://github.com/h2pl/Java-Tutorial

喜歡的話麻煩點(diǎn)下Star哈

文章首發(fā)于我的個(gè)人博客:

www.how2playlife.com

本文是微信公眾號(hào)【Java技術(shù)江湖】的《重新學(xué)習(xí)MySQL數(shù)據(jù)庫(kù)》其中一篇,本文部分內(nèi)容來(lái)源于網(wǎng)絡(luò)梯浪,為了把本文主題講得清晰透徹耍攘,也整合了很多我認(rèn)為不錯(cuò)的技術(shù)博客內(nèi)容贯被,引用其中了一些比較好的博客文章媚朦,如有侵權(quán),請(qǐng)聯(lián)系作者啰脚。

該系列博文會(huì)告訴你如何從入門(mén)到進(jìn)階,從sql基本的使用方法实夹,從MySQL執(zhí)行引擎再到索引橄浓、事務(wù)等知識(shí)粒梦,一步步地學(xué)習(xí)MySQL相關(guān)技術(shù)的實(shí)現(xiàn)原理,更好地了解如何基于這些知識(shí)來(lái)優(yōu)化sql贮配,減少SQL執(zhí)行時(shí)間谍倦,通過(guò)執(zhí)行計(jì)劃對(duì)SQL性能進(jìn)行分析,再到MySQL的主從復(fù)制泪勒、主備部署等內(nèi)容昼蛀,以便讓你更完整地了解整個(gè)MySQL方面的技術(shù)體系,形成自己的知識(shí)框架圆存。

如果對(duì)本系列文章有什么建議叼旋,或者是有什么疑問(wèn)的話,也可以關(guān)注公眾號(hào)【Java技術(shù)江湖】聯(lián)系作者沦辙,歡迎你參與本系列博文的創(chuàng)作和修訂夫植。

作為一名開(kāi)發(fā)人員,在日常的工作中會(huì)難以避免地接觸到數(shù)據(jù)庫(kù)油讯,無(wú)論是基于文件的 sqlite 還是工程上使用非常廣泛的 MySQL详民、PostgreSQL,但是一直以來(lái)也沒(méi)有對(duì)數(shù)據(jù)庫(kù)有一個(gè)非常清晰并且成體系的認(rèn)知陌兑,所以最近兩個(gè)月的時(shí)間看了幾本數(shù)據(jù)庫(kù)相關(guān)的書(shū)籍并且閱讀了 MySQL 的官方文檔沈跨,希望對(duì)各位了解數(shù)據(jù)庫(kù)的、不了解數(shù)據(jù)庫(kù)的有所幫助兔综。

mysql

本文中對(duì)于數(shù)據(jù)庫(kù)的介紹以及研究都是在 MySQL 上進(jìn)行的饿凛,如果涉及到了其他數(shù)據(jù)庫(kù)的內(nèi)容或者實(shí)現(xiàn)會(huì)在文中單獨(dú)指出。

數(shù)據(jù)庫(kù)的定義

很多開(kāi)發(fā)者在最開(kāi)始時(shí)其實(shí)都對(duì)數(shù)據(jù)庫(kù)有一個(gè)比較模糊的認(rèn)識(shí)软驰,覺(jué)得數(shù)據(jù)庫(kù)就是一堆數(shù)據(jù)的集合涧窒,但是實(shí)際卻比這復(fù)雜的多,數(shù)據(jù)庫(kù)領(lǐng)域中有兩個(gè)詞非常容易混淆锭亏,也就是數(shù)據(jù)庫(kù)實(shí)例

  • 數(shù)據(jù)庫(kù):物理操作文件系統(tǒng)或其他形式文件類(lèi)型的集合纠吴;
  • 實(shí)例:MySQL 數(shù)據(jù)庫(kù)由后臺(tái)線程以及一個(gè)共享內(nèi)存區(qū)組成;

對(duì)于數(shù)據(jù)庫(kù)和實(shí)例的定義都來(lái)自于 MySQL 技術(shù)內(nèi)幕:InnoDB 存儲(chǔ)引擎 一書(shū)贰镣,想要了解 InnoDB 存儲(chǔ)引擎的讀者可以閱讀這本書(shū)籍呜象。

數(shù)據(jù)庫(kù)和實(shí)例

在 MySQL 中,實(shí)例和數(shù)據(jù)庫(kù)往往都是一一對(duì)應(yīng)的碑隆,而我們也無(wú)法直接操作數(shù)據(jù)庫(kù)恭陡,而是要通過(guò)數(shù)據(jù)庫(kù)實(shí)例來(lái)操作數(shù)據(jù)庫(kù)文件,可以理解為數(shù)據(jù)庫(kù)實(shí)例是數(shù)據(jù)庫(kù)為上層提供的一個(gè)專(zhuān)門(mén)用于操作的接口上煤。

Database - Instance

在 Unix 上休玩,啟動(dòng)一個(gè) MySQL 實(shí)例往往會(huì)產(chǎn)生兩個(gè)進(jìn)程,mysqld 就是真正的數(shù)據(jù)庫(kù)服務(wù)守護(hù)進(jìn)程,而 mysqld_safe 是一個(gè)用于檢查和設(shè)置 mysqld 啟動(dòng)的控制程序拴疤,它負(fù)責(zé)監(jiān)控 MySQL 進(jìn)程的執(zhí)行永部,當(dāng) mysqld 發(fā)生錯(cuò)誤時(shí),mysqld_safe 會(huì)對(duì)其狀態(tài)進(jìn)行檢查并在合適的條件下重啟呐矾。

MySQL 的架構(gòu)

MySQL 從第一個(gè)版本發(fā)布到現(xiàn)在已經(jīng)有了 20 多年的歷史苔埋,在這么多年的發(fā)展和演變中,整個(gè)應(yīng)用的體系結(jié)構(gòu)變得越來(lái)越復(fù)雜:

Logical-View-of-MySQL-Architecture

最上層用于連接蜒犯、線程處理的部分并不是 MySQL 『發(fā)明』的组橄,很多服務(wù)都有類(lèi)似的組成部分;第二層中包含了大多數(shù) MySQL 的核心服務(wù)罚随,包括了對(duì) SQL 的解析玉工、分析、優(yōu)化和緩存等功能淘菩,存儲(chǔ)過(guò)程遵班、觸發(fā)器和視圖都是在這里實(shí)現(xiàn)的;而第三層就是 MySQL 中真正負(fù)責(zé)數(shù)據(jù)的存儲(chǔ)和提取的存儲(chǔ)引擎潮改,例如:InnoDB狭郑、MyISAM 等,文中對(duì)存儲(chǔ)引擎的介紹都是對(duì) InnoDB 實(shí)現(xiàn)的分析汇在。

數(shù)據(jù)的存儲(chǔ)

在整個(gè)數(shù)據(jù)庫(kù)體系結(jié)構(gòu)中愿阐,我們可以使用不同的存儲(chǔ)引擎來(lái)存儲(chǔ)數(shù)據(jù),而絕大多數(shù)存儲(chǔ)引擎都以二進(jìn)制的形式存儲(chǔ)數(shù)據(jù)趾疚;這一節(jié)會(huì)介紹 InnoDB 中對(duì)數(shù)據(jù)是如何存儲(chǔ)的。

在 InnoDB 存儲(chǔ)引擎中以蕴,所有的數(shù)據(jù)都被邏輯地存放在表空間中糙麦,表空間(tablespace)是存儲(chǔ)引擎中最高的存儲(chǔ)邏輯單位,在表空間的下面又包括段(segment)丛肮、區(qū)(extent)赡磅、頁(yè)(page):

Tablespace-segment-extent-page-row

同一個(gè)數(shù)據(jù)庫(kù)實(shí)例的所有表空間都有相同的頁(yè)大小宝与;默認(rèn)情況下焚廊,表空間中的頁(yè)大小都為 16KB,當(dāng)然也可以通過(guò)改變 innodb_page_size 選項(xiàng)對(duì)默認(rèn)大小進(jìn)行修改习劫,需要注意的是不同的頁(yè)大小最終也會(huì)導(dǎo)致區(qū)大小的不同:

Relation Between Page Size - Extent Size

從圖中可以看出咆瘟,在 InnoDB 存儲(chǔ)引擎中,一個(gè)區(qū)的大小最小為 1MB诽里,頁(yè)的數(shù)量最少為 64 個(gè)袒餐。

如何存儲(chǔ)表

MySQL 使用 InnoDB 存儲(chǔ)表時(shí),會(huì)將表的定義數(shù)據(jù)索引等信息分開(kāi)存儲(chǔ),其中前者存儲(chǔ)在 .frm 文件中灸眼,后者存儲(chǔ)在 .ibd 文件中卧檐,這一節(jié)就會(huì)對(duì)這兩種不同的文件分別進(jìn)行介紹。

frm-and-ibd-file

.frm 文件

無(wú)論在 MySQL 中選擇了哪個(gè)存儲(chǔ)引擎焰宣,所有的 MySQL 表都會(huì)在硬盤(pán)上創(chuàng)建一個(gè) .frm 文件用來(lái)描述表的格式或者說(shuō)定義霉囚;.frm 文件的格式在不同的平臺(tái)上都是相同的。

 CREATE TABLE test_frm(    column1 CHAR(5),    column2 INTEGER);

當(dāng)我們使用上面的代碼創(chuàng)建表時(shí)匕积,會(huì)在磁盤(pán)上的 datadir 文件夾中生成一個(gè) test_frm.frm 的文件盈罐,這個(gè)文件中就包含了表結(jié)構(gòu)相關(guān)的信息:

frm-file-hex

MySQL 官方文檔中的 11.1 MySQL .frm File Format 一文對(duì)于 .frm文件格式中的二進(jìn)制的內(nèi)容有著非常詳細(xì)的表述,在這里就不展開(kāi)介紹了闸天。

.ibd 文件

InnoDB 中用于存儲(chǔ)數(shù)據(jù)的文件總共有兩個(gè)部分暖呕,一是系統(tǒng)表空間文件,包括 ibdata1苞氮、ibdata2 等文件湾揽,其中存儲(chǔ)了 InnoDB 系統(tǒng)信息和用戶(hù)數(shù)據(jù)庫(kù)表數(shù)據(jù)和索引,是所有表公用的笼吟。

當(dāng)打開(kāi) innodb_file_per_table 選項(xiàng)時(shí)库物,.ibd 文件就是每一個(gè)表獨(dú)有的表空間,文件存儲(chǔ)了當(dāng)前表的數(shù)據(jù)和相關(guān)的索引數(shù)據(jù)贷帮。

如何存儲(chǔ)記錄

與現(xiàn)有的大多數(shù)存儲(chǔ)引擎一樣戚揭,InnoDB 使用頁(yè)作為磁盤(pán)管理的最小單位;數(shù)據(jù)在 InnoDB 存儲(chǔ)引擎中都是按行存儲(chǔ)的撵枢,每個(gè) 16KB 大小的頁(yè)中可以存放 2-200 行的記錄民晒。

當(dāng) InnoDB 存儲(chǔ)數(shù)據(jù)時(shí),它可以使用不同的行格式進(jìn)行存儲(chǔ)锄禽;MySQL 5.7 版本支持以下格式的行存儲(chǔ)方式:

Antelope-Barracuda-Row-Format

Antelope 是 InnoDB 最開(kāi)始支持的文件格式潜必,它包含兩種行格式 Compact 和 Redundant,它最開(kāi)始并沒(méi)有名字沃但;Antelope 的名字是在新的文件格式 Barracuda 出現(xiàn)后才起的磁滚,Barracuda 的出現(xiàn)引入了兩種新的行格式 Compressed 和 Dynamic;InnoDB 對(duì)于文件格式都會(huì)向前兼容宵晚,而官方文檔中也對(duì)之后會(huì)出現(xiàn)的新文件格式預(yù)先定義好了名字:Cheetah垂攘、Dragon、Elk 等等淤刃。

兩種行記錄格式 Compact 和 Redundant 在磁盤(pán)上按照以下方式存儲(chǔ):

COMPACT-And-REDUNDANT-Row-Format

Compact 和 Redundant 格式最大的不同就是記錄格式的第一個(gè)部分晒他;在 Compact 中,行記錄的第一部分倒序存放了一行數(shù)據(jù)中列的長(zhǎng)度(Length)钝凶,而 Redundant 中存的是每一列的偏移量(Offset)仪芒,從總體上上看唁影,Compact 行記錄格式相比 Redundant 格式能夠減少 20% 的存儲(chǔ)空間。

行溢出數(shù)據(jù)

當(dāng) InnoDB 使用 Compact 或者 Redundant 格式存儲(chǔ)極長(zhǎng)的 VARCHAR 或者 BLOB 這類(lèi)大對(duì)象時(shí)掂名,我們并不會(huì)直接將所有的內(nèi)容都存放在數(shù)據(jù)頁(yè)節(jié)點(diǎn)中据沈,而是將行數(shù)據(jù)中的前 768 個(gè)字節(jié)存儲(chǔ)在數(shù)據(jù)頁(yè)中,后面會(huì)通過(guò)偏移量指向溢出頁(yè)饺蔑。

Row-Overflo

但是當(dāng)我們使用新的行記錄格式 Compressed 或者 Dynamic 時(shí)都只會(huì)在行記錄中保存 20 個(gè)字節(jié)的指針锌介,實(shí)際的數(shù)據(jù)都會(huì)存放在溢出頁(yè)面中。

Row-Overflow-in-Barracuda

當(dāng)然在實(shí)際存儲(chǔ)中猾警,可能會(huì)對(duì)不同長(zhǎng)度的 TEXT 和 BLOB 列進(jìn)行優(yōu)化孔祸,不過(guò)這就不是本文關(guān)注的重點(diǎn)了。

想要了解更多與 InnoDB 存儲(chǔ)引擎中記錄的數(shù)據(jù)格式的相關(guān)信息发皿,可以閱讀 InnoDB Record Structure

數(shù)據(jù)頁(yè)結(jié)構(gòu)

頁(yè)是 InnoDB 存儲(chǔ)引擎管理數(shù)據(jù)的最小磁盤(pán)單位崔慧,而 B-Tree 節(jié)點(diǎn)就是實(shí)際存放表中數(shù)據(jù)的頁(yè)面,我們?cè)谶@里將要介紹頁(yè)是如何組織和存儲(chǔ)記錄的穴墅;首先惶室,一個(gè) InnoDB 頁(yè)有以下七個(gè)部分:

InnoDB-B-Tree-Node

每一個(gè)頁(yè)中包含了兩對(duì) header/trailer:內(nèi)部的 Page Header/Page Directory 關(guān)心的是頁(yè)的狀態(tài)信息,而 Fil Header/Fil Trailer 關(guān)心的是記錄頁(yè)的頭信息玄货。

在頁(yè)的頭部和尾部之間就是用戶(hù)記錄和空閑空間了皇钞,每一個(gè)數(shù)據(jù)頁(yè)中都包含 Infimum 和 Supremum 這兩個(gè)虛擬的記錄(可以理解為占位符),Infimum 記錄是比該頁(yè)中任何主鍵值都要小的值松捉,Supremum 是該頁(yè)中的最大值:

Infimum-Rows-Supremum

User Records 就是整個(gè)頁(yè)面中真正用于存放行記錄的部分夹界,而 Free Space 就是空余空間了,它是一個(gè)鏈表的數(shù)據(jù)結(jié)構(gòu)隘世,為了保證插入和刪除的效率可柿,整個(gè)頁(yè)面并不會(huì)按照主鍵順序?qū)λ杏涗涍M(jìn)行排序,它會(huì)自動(dòng)從左側(cè)向右尋找空白節(jié)點(diǎn)進(jìn)行插入丙者,行記錄在物理存儲(chǔ)上并不是按照順序的趾痘,它們之間的順序是由 next_record 這一指針控制的。

B+ 樹(shù)在查找對(duì)應(yīng)的記錄時(shí)蔓钟,并不會(huì)直接從樹(shù)中找出對(duì)應(yīng)的行記錄,它只能獲取記錄所在的頁(yè)卵贱,將整個(gè)頁(yè)加載到內(nèi)存中滥沫,再通過(guò) Page Directory 中存儲(chǔ)的稀疏索引和 n_ownednext_record 屬性取出對(duì)應(yīng)的記錄键俱,不過(guò)因?yàn)檫@一操作是在內(nèi)存中進(jìn)行的兰绣,所以通常會(huì)忽略這部分查找的耗時(shí)。

InnoDB 存儲(chǔ)引擎中對(duì)數(shù)據(jù)的存儲(chǔ)是一個(gè)非常復(fù)雜的話題编振,這一節(jié)中也只是對(duì)表缀辩、行記錄以及頁(yè)面的存儲(chǔ)進(jìn)行一定的分析和介紹,雖然作者相信這部分知識(shí)對(duì)于大部分開(kāi)發(fā)者已經(jīng)足夠了,但是想要真正消化這部分內(nèi)容還需要很多的努力和實(shí)踐臀玄。

索引

索引是數(shù)據(jù)庫(kù)中非常非常重要的概念瓢阴,它是存儲(chǔ)引擎能夠快速定位記錄的秘密武器,對(duì)于提升數(shù)據(jù)庫(kù)的性能健无、減輕數(shù)據(jù)庫(kù)服務(wù)器的負(fù)擔(dān)有著非常重要的作用荣恐;索引優(yōu)化是對(duì)查詢(xún)性能優(yōu)化的最有效手段,它能夠輕松地將查詢(xún)的性能提高幾個(gè)數(shù)量級(jí)累贤。

索引的數(shù)據(jù)結(jié)構(gòu)

在上一節(jié)中叠穆,我們談了行記錄的存儲(chǔ)和頁(yè)的存儲(chǔ),在這里我們就要從更高的層面看 InnoDB 中對(duì)于數(shù)據(jù)是如何存儲(chǔ)的臼膏;InnoDB 存儲(chǔ)引擎在絕大多數(shù)情況下使用 B+ 樹(shù)建立索引硼被,這是關(guān)系型數(shù)據(jù)庫(kù)中查找最為常用和有效的索引,但是 B+ 樹(shù)索引并不能找到一個(gè)給定鍵對(duì)應(yīng)的具體值渗磅,它只能找到數(shù)據(jù)行對(duì)應(yīng)的頁(yè)嚷硫,然后正如上一節(jié)所提到的,數(shù)據(jù)庫(kù)把整個(gè)頁(yè)讀入到內(nèi)存中夺溢,并在內(nèi)存中查找具體的數(shù)據(jù)行论巍。

B+Tree

B+ 樹(shù)是平衡樹(shù),它查找任意節(jié)點(diǎn)所耗費(fèi)的時(shí)間都是完全相同的风响,比較的次數(shù)就是 B+ 樹(shù)的高度嘉汰;在這里,我們并不會(huì)深入分析或者動(dòng)手實(shí)現(xiàn)一個(gè) B+ 樹(shù)状勤,只是對(duì)它的特性進(jìn)行簡(jiǎn)單的介紹鞋怀。

聚集索引和輔助索引

數(shù)據(jù)庫(kù)中的 B+ 樹(shù)索引可以分為聚集索引(clustered index)和輔助索引(secondary index),它們之間的最大區(qū)別就是持搜,聚集索引中存放著一條行記錄的全部信息密似,而輔助索引中只包含索引列和一個(gè)用于查找對(duì)應(yīng)行記錄的『書(shū)簽』。

聚集索引

InnoDB 存儲(chǔ)引擎中的表都是使用索引組織的葫盼,也就是按照鍵的順序存放残腌;聚集索引就是按照表中主鍵的順序構(gòu)建一顆 B+ 樹(shù),并在葉節(jié)點(diǎn)中存放表中的行記錄數(shù)據(jù)贫导。

 CREATE TABLE users(    id INT NOT NULL,    first_name VARCHAR(20) NOT NULL,    last_name VARCHAR(20) NOT NULL,    age INT NOT NULL,    PRIMARY KEY(id),    KEY(last_name, first_name, age)    KEY(first_name));

如果使用上面的 SQL 在數(shù)據(jù)庫(kù)中創(chuàng)建一張表抛猫,B+ 樹(shù)就會(huì)使用 id 作為索引的鍵,并在葉子節(jié)點(diǎn)中存儲(chǔ)一條記錄中的所有信息孩灯。

Clustered-Index

圖中對(duì) B+ 樹(shù)的描述與真實(shí)情況下 B+ 樹(shù)中的數(shù)據(jù)結(jié)構(gòu)有一些差別闺金,不過(guò)這里想要表達(dá)的主要意思是:聚集索引葉節(jié)點(diǎn)中保存的是整條行記錄,而不是其中的一部分峰档。

聚集索引與表的物理存儲(chǔ)方式有著非常密切的關(guān)系败匹,所有正常的表應(yīng)該有且僅有一個(gè)聚集索引(絕大多數(shù)情況下都是主鍵)寨昙,表中的所有行記錄數(shù)據(jù)都是按照聚集索引的順序存放的。

當(dāng)我們使用聚集索引對(duì)表中的數(shù)據(jù)進(jìn)行檢索時(shí)掀亩,可以直接獲得聚集索引所對(duì)應(yīng)的整條行記錄數(shù)據(jù)所在的頁(yè)舔哪,不需要進(jìn)行第二次操作。

輔助索引

數(shù)據(jù)庫(kù)將所有的非聚集索引都劃分為輔助索引归榕,但是這個(gè)概念對(duì)我們理解輔助索引并沒(méi)有什么幫助尸红;輔助索引也是通過(guò) B+ 樹(shù)實(shí)現(xiàn)的,但是它的葉節(jié)點(diǎn)并不包含行記錄的全部數(shù)據(jù)刹泄,僅包含索引中的所有鍵和一個(gè)用于查找對(duì)應(yīng)行記錄的『書(shū)簽』外里,在 InnoDB 中這個(gè)書(shū)簽就是當(dāng)前記錄的主鍵。

輔助索引的存在并不會(huì)影響聚集索引特石,因?yàn)榫奂饕龢?gòu)成的 B+ 樹(shù)是數(shù)據(jù)實(shí)際存儲(chǔ)的形式盅蝗,而輔助索引只用于加速數(shù)據(jù)的查找,所以一張表上往往有多個(gè)輔助索引以此來(lái)提升數(shù)據(jù)庫(kù)的性能姆蘸。

一張表一定包含一個(gè)聚集索引構(gòu)成的 B+ 樹(shù)以及若干輔助索引的構(gòu)成的 B+ 樹(shù)墩莫。

Secondary-Index

如果在表 users 中存在一個(gè)輔助索引 (first_name, age),那么它構(gòu)成的 B+ 樹(shù)大致就是上圖這樣逞敷,按照 (first_name, age) 的字母順序?qū)Ρ碇械臄?shù)據(jù)進(jìn)行排序狂秦,當(dāng)查找到主鍵時(shí),再通過(guò)聚集索引獲取到整條行記錄推捐。

Clustered-Secondary-Index

上圖展示了一個(gè)使用輔助索引查找一條表記錄的過(guò)程:通過(guò)輔助索引查找到對(duì)應(yīng)的主鍵裂问,最后在聚集索引中使用主鍵獲取對(duì)應(yīng)的行記錄,這也是通常情況下行記錄的查找方式牛柒。

索引的設(shè)計(jì)

索引的設(shè)計(jì)其實(shí)是一個(gè)非常重要的內(nèi)容堪簿,同時(shí)也是一個(gè)非常復(fù)雜的內(nèi)容;索引的設(shè)計(jì)與創(chuàng)建對(duì)于提升數(shù)據(jù)庫(kù)的查詢(xún)性能至關(guān)重要皮壁,不過(guò)這不是本文想要介紹的內(nèi)容椭更,有關(guān)索引的設(shè)計(jì)與優(yōu)化可以閱讀 數(shù)據(jù)庫(kù)索引設(shè)計(jì)與優(yōu)化 一書(shū),書(shū)中提供了一種非扯昶牵科學(xué)合理的方法能夠幫助我們?cè)跀?shù)據(jù)庫(kù)中建立最適合的索引虑瀑,當(dāng)然作者也可能會(huì)在之后的文章中對(duì)索引的設(shè)計(jì)進(jìn)行簡(jiǎn)單的介紹和分析。

我們都知道鎖的種類(lèi)一般分為樂(lè)觀鎖和悲觀鎖兩種滴须,InnoDB 存儲(chǔ)引擎中使用的就是悲觀鎖缴川,而按照鎖的粒度劃分,也可以分成行鎖和表鎖描馅。

并發(fā)控制機(jī)制

樂(lè)觀鎖和悲觀鎖其實(shí)都是并發(fā)控制的機(jī)制,同時(shí)它們?cè)谠砩暇陀兄举|(zhì)的差別而线;

  • 樂(lè)觀鎖是一種思想铭污,它其實(shí)并不是一種真正的『鎖』恋日,它會(huì)先嘗試對(duì)資源進(jìn)行修改,在寫(xiě)回時(shí)判斷資源是否進(jìn)行了改變嘹狞,如果沒(méi)有發(fā)生改變就會(huì)寫(xiě)回岂膳,否則就會(huì)進(jìn)行重試,在整個(gè)的執(zhí)行過(guò)程中其實(shí)都沒(méi)有對(duì)數(shù)據(jù)庫(kù)進(jìn)行加鎖磅网;
  • 悲觀鎖就是一種真正的鎖了谈截,它會(huì)在獲取資源前對(duì)資源進(jìn)行加鎖,確保同一時(shí)刻只有有限的線程能夠訪問(wèn)該資源涧偷,其他想要嘗試獲取資源的操作都會(huì)進(jìn)入等待狀態(tài)簸喂,直到該線程完成了對(duì)資源的操作并且釋放了鎖后,其他線程才能重新操作資源燎潮;

雖然樂(lè)觀鎖和悲觀鎖在本質(zhì)上并不是同一種東西喻鳄,一個(gè)是一種思想,另一個(gè)是一種真正的鎖确封,但是它們都是一種并發(fā)控制機(jī)制除呵。

Optimistic-Pessimistic-Locks

樂(lè)觀鎖不會(huì)存在死鎖的問(wèn)題,但是由于更新后驗(yàn)證爪喘,所以當(dāng)沖突頻率重試成本較高時(shí)更推薦使用悲觀鎖颜曾,而需要非常高的響應(yīng)速度并且并發(fā)量非常大的時(shí)候使用樂(lè)觀鎖就能較好的解決問(wèn)題,在這時(shí)使用悲觀鎖就可能出現(xiàn)嚴(yán)重的性能問(wèn)題秉剑;在選擇并發(fā)控制機(jī)制時(shí)泛豪,需要綜合考慮上面的四個(gè)方面(沖突頻率、重試成本秃症、響應(yīng)速度和并發(fā)量)進(jìn)行選擇候址。

鎖的種類(lèi)

對(duì)數(shù)據(jù)的操作其實(shí)只有兩種,也就是讀和寫(xiě)种柑,而數(shù)據(jù)庫(kù)在實(shí)現(xiàn)鎖時(shí)岗仑,也會(huì)對(duì)這兩種操作使用不同的鎖;InnoDB 實(shí)現(xiàn)了標(biāo)準(zhǔn)的行級(jí)鎖聚请,也就是共享鎖(Shared Lock)和互斥鎖(Exclusive Lock)荠雕;共享鎖和互斥鎖的作用其實(shí)非常好理解:

  • 共享鎖(讀鎖):允許事務(wù)對(duì)一條行數(shù)據(jù)進(jìn)行讀取驶赏;
  • 互斥鎖(寫(xiě)鎖):允許事務(wù)對(duì)一條行數(shù)據(jù)進(jìn)行刪除或更新炸卑;

而它們的名字也暗示著各自的另外一個(gè)特性,共享鎖之間是兼容的煤傍,而互斥鎖與其他任意鎖都不兼容:

Shared-Exclusive-Lock

稍微對(duì)它們的使用進(jìn)行思考就能想明白它們?yōu)槭裁匆@么設(shè)計(jì)盖文,因?yàn)楣蚕礞i代表了讀操作、互斥鎖代表了寫(xiě)操作蚯姆,所以我們可以在數(shù)據(jù)庫(kù)中并行讀五续,但是只能串行寫(xiě)洒敏,只有這樣才能保證不會(huì)發(fā)生線程競(jìng)爭(zhēng),實(shí)現(xiàn)線程安全疙驾。

鎖的粒度

無(wú)論是共享鎖還是互斥鎖其實(shí)都只是對(duì)某一個(gè)數(shù)據(jù)行進(jìn)行加鎖凶伙,InnoDB 支持多種粒度的鎖,也就是行鎖和表鎖它碎;為了支持多粒度鎖定函荣,InnoDB 存儲(chǔ)引擎引入了意向鎖(Intention Lock),意向鎖就是一種表級(jí)鎖扳肛。

與上一節(jié)中提到的兩種鎖的種類(lèi)相似的是傻挂,意向鎖也分為兩種:

  • 意向共享鎖:事務(wù)想要在獲得表中某些記錄的共享鎖,需要在表上先加意向共享鎖敞峭;
  • 意向互斥鎖:事務(wù)想要在獲得表中某些記錄的互斥鎖踊谋,需要在表上先加意向互斥鎖;

隨著意向鎖的加入旋讹,鎖類(lèi)型之間的兼容矩陣也變得愈加復(fù)雜:

Lock-Type-Compatibility-Matrix

意向鎖其實(shí)不會(huì)阻塞全表掃描之外的任何請(qǐng)求殖蚕,它們的主要目的是為了表示是否有人請(qǐng)求鎖定表中的某一行數(shù)據(jù)

有的人可能會(huì)對(duì)意向鎖的目的并不是完全的理解沉迹,我們?cè)谶@里可以舉一個(gè)例子:如果沒(méi)有意向鎖睦疫,當(dāng)已經(jīng)有人使用行鎖對(duì)表中的某一行進(jìn)行修改時(shí),如果另外一個(gè)請(qǐng)求要對(duì)全表進(jìn)行修改鞭呕,那么就需要對(duì)所有的行是否被鎖定進(jìn)行掃描蛤育,在這種情況下,效率是非常低的葫松;不過(guò)瓦糕,在引入意向鎖之后,當(dāng)有人使用行鎖對(duì)表中的某一行進(jìn)行修改之前腋么,會(huì)先為表添加意向互斥鎖(IX)咕娄,再為行記錄添加互斥鎖(X),在這時(shí)如果有人嘗試對(duì)全表進(jìn)行修改就不需要判斷表中的每一行數(shù)據(jù)是否被加鎖了珊擂,只需要通過(guò)等待意向互斥鎖被釋放就可以了圣勒。

鎖的算法

到目前為止已經(jīng)對(duì) InnoDB 中鎖的粒度有一定的了解,也清楚了在對(duì)數(shù)據(jù)庫(kù)進(jìn)行讀寫(xiě)時(shí)會(huì)獲取不同的鎖摧扇,在這一小節(jié)將介紹鎖是如何添加到對(duì)應(yīng)的數(shù)據(jù)行上的圣贸,我們會(huì)分別介紹三種鎖的算法:Record Lock、Gap Lock 和 Next-Key Lock扛稽。

Record Lock

記錄鎖(Record Lock)是加到索引記錄上的鎖吁峻,假設(shè)我們存在下面的一張表 users

 CREATE TABLE users(    id INT NOT NULL AUTO_INCREMENT,    last_name VARCHAR(255) NOT NULL,    first_name VARCHAR(255),    age INT,    PRIMARY KEY(id),    KEY(last_name),    KEY(age));

如果我們使用 id 或者 last_name 作為 SQL 中 WHERE 語(yǔ)句的過(guò)濾條件,那么 InnoDB 就可以通過(guò)索引建立的 B+ 樹(shù)找到行記錄并添加索引,但是如果使用 first_name 作為過(guò)濾條件時(shí)用含,由于 InnoDB 不知道待修改的記錄具體存放的位置橙困,也無(wú)法對(duì)將要修改哪條記錄提前做出判斷就會(huì)鎖定整個(gè)表。

Gap Lock

記錄鎖是在存儲(chǔ)引擎中最為常見(jiàn)的鎖耕餐,除了記錄鎖之外,InnoDB 中還存在間隙鎖(Gap Lock)辟狈,間隙鎖是對(duì)索引記錄中的一段連續(xù)區(qū)域的鎖肠缔;當(dāng)使用類(lèi)似 SELECT * FROM users WHERE id BETWEEN 10 AND 20 FOR UPDATE; 的 SQL 語(yǔ)句時(shí),就會(huì)阻止其他事務(wù)向表中插入 id = 15 的記錄哼转,因?yàn)檎麄€(gè)范圍都被間隙鎖鎖定了明未。

間隙鎖是存儲(chǔ)引擎對(duì)于性能和并發(fā)做出的權(quán)衡,并且只用于某些事務(wù)隔離級(jí)別壹蔓。

雖然間隙鎖中也分為共享鎖和互斥鎖趟妥,不過(guò)它們之間并不是互斥的,也就是不同的事務(wù)可以同時(shí)持有一段相同范圍的共享鎖和互斥鎖佣蓉,它唯一阻止的就是其他事務(wù)向這個(gè)范圍中添加新的記錄披摄。

Next-Key Lock

Next-Key 鎖相比前兩者就稍微有一些復(fù)雜,它是記錄鎖和記錄前的間隙鎖的結(jié)合勇凭,在 users 表中有以下記錄:

 +------|-------------|--------------|-------+|   id | last_name   | first_name   |   age ||------|-------------|--------------|-------||    4 | stark       | tony         |    21 ||    1 | tom         | hiddleston   |    30 ||    3 | morgan      | freeman      |    40 ||    5 | jeff        | dean         |    50 ||    2 | donald      | trump        |    80 |+------|-------------|--------------|-------+

如果使用 Next-Key 鎖疚膊,那么 Next-Key 鎖就可以在需要的時(shí)候鎖定以下的范圍:

 (-∞, 21](21, 30](30, 40](40, 50](50, 80](80, ∞)

既然叫 Next-Key 鎖,鎖定的應(yīng)該是當(dāng)前值和后面的范圍虾标,但是實(shí)際上卻不是寓盗,Next-Key 鎖鎖定的是當(dāng)前值和前面的范圍。

當(dāng)我們更新一條記錄璧函,比如 SELECT * FROM users WHERE age = 30 FOR UPDATE;傀蚌,InnoDB 不僅會(huì)在范圍 (21, 30] 上加 Next-Key 鎖,還會(huì)在這條記錄后面的范圍 (30, 40] 加間隙鎖蘸吓,所以插入 (21, 40] 范圍內(nèi)的記錄都會(huì)被鎖定善炫。

Next-Key 鎖的作用其實(shí)是為了解決幻讀的問(wèn)題,我們會(huì)在下一節(jié)談事務(wù)的時(shí)候具體介紹美澳。

死鎖的發(fā)生

既然 InnoDB 中實(shí)現(xiàn)的鎖是悲觀的销部,那么不同事務(wù)之間就可能會(huì)互相等待對(duì)方釋放鎖造成死鎖,最終導(dǎo)致事務(wù)發(fā)生錯(cuò)誤;想要在 MySQL 中制造死鎖的問(wèn)題其實(shí)非常容易:

Deadlocks

兩個(gè)會(huì)話都持有一個(gè)鎖洪规,并且嘗試獲取對(duì)方的鎖時(shí)就會(huì)發(fā)生死鎖靶草,不過(guò) MySQL 也能在發(fā)生死鎖時(shí)及時(shí)發(fā)現(xiàn)問(wèn)題,并保證其中的一個(gè)事務(wù)能夠正常工作擂涛,這對(duì)我們來(lái)說(shuō)也是一個(gè)好消息。

事務(wù)與隔離級(jí)別

在介紹了鎖之后,我們?cè)賮?lái)談?wù)剶?shù)據(jù)庫(kù)中一個(gè)非常重要的概念 —— 事務(wù)撒妈;相信只要是一個(gè)合格的軟件工程師就對(duì)事務(wù)的特性有所了解恢暖,其中被人經(jīng)常提起的就是事務(wù)的原子性,在數(shù)據(jù)提交工作時(shí)狰右,要么保證所有的修改都能夠提交杰捂,要么就所有的修改全部回滾。

但是事務(wù)還遵循包括原子性在內(nèi)的 ACID 四大特性:原子性(Atomicity)棋蚌、一致性(Consistency)嫁佳、隔離性(Isolation)和持久性(Durability);文章不會(huì)對(duì)這四大特性全部展開(kāi)進(jìn)行介紹谷暮,相信你能夠通過(guò) Google 和數(shù)據(jù)庫(kù)相關(guān)的書(shū)籍輕松獲得有關(guān)它們的概念蒿往,本文最后要介紹的就是事務(wù)的四種隔離級(jí)別。

幾種隔離級(jí)別

事務(wù)的隔離性是數(shù)據(jù)庫(kù)處理數(shù)據(jù)的幾大基礎(chǔ)之一湿弦,而隔離級(jí)別其實(shí)就是提供給用戶(hù)用于在性能和可靠性做出選擇和權(quán)衡的配置項(xiàng)瓤漏。

ISO 和 ANIS SQL 標(biāo)準(zhǔn)制定了四種事務(wù)隔離級(jí)別,而 InnoDB 遵循了 SQL:1992 標(biāo)準(zhǔn)中的四種隔離級(jí)別:READ UNCOMMITED颊埃、READ COMMITED蔬充、REPEATABLE READSERIALIZABLE;每個(gè)事務(wù)的隔離級(jí)別其實(shí)都比上一級(jí)多解決了一個(gè)問(wèn)題:

  • RAED UNCOMMITED:使用查詢(xún)語(yǔ)句不會(huì)加鎖竟秫,可能會(huì)讀到未提交的行(Dirty Read)娃惯;
  • READ COMMITED:只對(duì)記錄加記錄鎖,而不會(huì)在記錄之間加間隙鎖肥败,所以允許新的記錄插入到被鎖定記錄的附近趾浅,所以再多次使用查詢(xún)語(yǔ)句時(shí),可能得到不同的結(jié)果(Non-Repeatable Read)馒稍;
  • REPEATABLE READ:多次讀取同一范圍的數(shù)據(jù)會(huì)返回第一次查詢(xún)的快照皿哨,不會(huì)返回不同的數(shù)據(jù)行,但是可能發(fā)生幻讀(Phantom Read)纽谒;
  • SERIALIZABLE:InnoDB 隱式地將全部的查詢(xún)語(yǔ)句加上共享鎖证膨,解決了幻讀的問(wèn)題;

MySQL 中默認(rèn)的事務(wù)隔離級(jí)別就是 REPEATABLE READ鼓黔,但是它通過(guò) Next-Key 鎖也能夠在某種程度上解決幻讀的問(wèn)題央勒。

Transaction-Isolation-Matrix

接下來(lái),我們將數(shù)據(jù)庫(kù)中創(chuàng)建如下的表并通過(guò)個(gè)例子來(lái)展示在不同的事務(wù)隔離級(jí)別之下澳化,會(huì)發(fā)生什么樣的問(wèn)題:

 CREATE TABLE test(    id INT NOT NULL,    UNIQUE(id));

臟讀

在一個(gè)事務(wù)中崔步,讀取了其他事務(wù)未提交的數(shù)據(jù)。

當(dāng)事務(wù)的隔離級(jí)別為 READ UNCOMMITED 時(shí)缎谷,我們?cè)?SESSION 2 中插入的未提交數(shù)據(jù)在 SESSION 1 中是可以訪問(wèn)的井濒。

Read-Uncommited-Dirty-Read

不可重復(fù)讀

在一個(gè)事務(wù)中,同一行記錄被訪問(wèn)了兩次卻得到了不同的結(jié)果。

當(dāng)事務(wù)的隔離級(jí)別為 READ COMMITED 時(shí)瑞你,雖然解決了臟讀的問(wèn)題酪惭,但是如果在 SESSION 1 先查詢(xún)了一行數(shù)據(jù),在這之后 SESSION 2 中修改了同一行數(shù)據(jù)并且提交了修改者甲,在這時(shí)春感,如果 SESSION 1 中再次使用相同的查詢(xún)語(yǔ)句,就會(huì)發(fā)現(xiàn)兩次查詢(xún)的結(jié)果不一樣虏缸。

Read-Commited-Non-Repeatable-Read

不可重復(fù)讀的原因就是甥厦,在 READ COMMITED 的隔離級(jí)別下,存儲(chǔ)引擎不會(huì)在查詢(xún)記錄時(shí)添加行鎖寇钉,鎖定 id = 3 這條記錄。

幻讀

在一個(gè)事務(wù)中舶赔,同一個(gè)范圍內(nèi)的記錄被讀取時(shí)扫倡,其他事務(wù)向這個(gè)范圍添加了新的記錄。

重新開(kāi)啟了兩個(gè)會(huì)話 SESSION 1SESSION 2竟纳,在 SESSION 1 中我們查詢(xún)?nèi)淼男畔⒛炖#瑳](méi)有得到任何記錄;在 SESSION 2 中向表中插入一條數(shù)據(jù)并提交锥累;由于 REPEATABLE READ 的原因缘挑,再次查詢(xún)?nèi)淼臄?shù)據(jù)時(shí),我們獲得到的仍然是空集桶略,但是在向表中插入同樣的數(shù)據(jù)卻出現(xiàn)了錯(cuò)誤语淘。

Repeatable-Read-Phantom-Read

這種現(xiàn)象在數(shù)據(jù)庫(kù)中就被稱(chēng)作幻讀,雖然我們使用查詢(xún)語(yǔ)句得到了一個(gè)空的集合际歼,但是插入數(shù)據(jù)時(shí)卻得到了錯(cuò)誤惶翻,好像之前的查詢(xún)是幻覺(jué)一樣。

在標(biāo)準(zhǔn)的事務(wù)隔離級(jí)別中鹅心,幻讀是由更高的隔離級(jí)別 SERIALIZABLE 解決的吕粗,但是它也可以通過(guò) MySQL 提供的 Next-Key 鎖解決:

Repeatable-with-Next-Key-Lock

REPERATABLE READREAD UNCOMMITED 其實(shí)是矛盾的,如果保證了前者就看不到已經(jīng)提交的事務(wù)旭愧,如果保證了后者颅筋,就會(huì)導(dǎo)致兩次查詢(xún)的結(jié)果不同,MySQL 為我們提供了一種折中的方式输枯,能夠在 REPERATABLE READ 模式下加鎖訪問(wèn)已經(jīng)提交的數(shù)據(jù)议泵,其本身并不能解決幻讀的問(wèn)題,而是通過(guò)文章前面提到的 Next-Key 鎖來(lái)解決用押。

總結(jié)

文章中的內(nèi)容大都來(lái)自于 高性能 MySQL肢簿、MySQL 技術(shù)內(nèi)幕:InnoDB 存儲(chǔ)引擎數(shù)據(jù)庫(kù)索引設(shè)計(jì)與優(yōu)化 以及 MySQL 的 官方文檔

由于篇幅所限僅能對(duì)數(shù)據(jù)庫(kù)中一些重要內(nèi)容進(jìn)行簡(jiǎn)單的介紹和總結(jié)池充,文中內(nèi)容難免有所疏漏桩引,如果對(duì)文章內(nèi)容的有疑問(wèn),可以在博客下面評(píng)論留言收夸。

Innodb與Myisam引擎的區(qū)別與應(yīng)用場(chǎng)景

1. 區(qū)別:

(1)事務(wù)處理:

MyISAM是非事務(wù)安全型的坑匠,而InnoDB是事務(wù)安全型的(支持事務(wù)處理等高級(jí)處理);

(2)鎖機(jī)制不同:

MyISAM是表級(jí)鎖卧惜,而InnoDB是行級(jí)鎖厘灼;

(3)select ,update ,insert ,delete 操作:

MyISAM:如果執(zhí)行大量的SELECT,MyISAM是更好的選擇

InnoDB:如果你的數(shù)據(jù)執(zhí)行大量的INSERT或UPDATE咽瓷,出于性能方面的考慮设凹,應(yīng)該使用InnoDB表

(4)查詢(xún)表的行數(shù)不同:

MyISAM:select count() from table,MyISAM只要簡(jiǎn)單的讀出保存好的行數(shù),注意的是茅姜,當(dāng)count()語(yǔ)句包含 where條件時(shí)闪朱,兩種表的操作是一樣的

InnoDB : InnoDB 中不保存表的具體行數(shù),也就是說(shuō)钻洒,執(zhí)行select count(*) from table時(shí)奋姿,InnoDB要掃描一遍整個(gè)表來(lái)計(jì)算有多少行

(5)外鍵支持:

mysiam表不支持外鍵,而InnoDB支持

  1. 為什么MyISAM會(huì)比Innodb 的查詢(xún)速度快素标。

INNODB在做SELECT的時(shí)候称诗,要維護(hù)的東西比MYISAM引擎多很多;
1)數(shù)據(jù)塊头遭,INNODB要緩存寓免,MYISAM只緩存索引塊, 這中間還有換進(jìn)換出的減少计维;
2)innodb尋址要映射到塊再榄,再到行,MYISAM 記錄的直接是文件的OFFSET享潜,定位比INNODB要快
3)INNODB還需要維護(hù)MVCC一致困鸥;雖然你的場(chǎng)景沒(méi)有,但他還是需要去檢查和維護(hù)

MVCC ( Multi-Version Concurrency Control )多版本并發(fā)控制

3. 應(yīng)用場(chǎng)景

MyISAM適合:(1)做很多count 的計(jì)算剑按;(2)插入不頻繁疾就,查詢(xún)非常頻繁;(3)沒(méi)有事務(wù)艺蝴。

InnoDB適合:(1)可靠性要求比較高猬腰,或者要求事務(wù);(2)表更新和查詢(xún)都相當(dāng)?shù)念l繁猜敢,并且行鎖定的機(jī)會(huì)比較大的情況姑荷。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末盒延,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子鼠冕,更是在濱河造成了極大的恐慌添寺,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件懈费,死亡現(xiàn)場(chǎng)離奇詭異计露,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)憎乙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)票罐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人泞边,你說(shuō)我怎么就攤上這事该押。” “怎么了阵谚?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵沈善,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我椭蹄,道長(zhǎng),這世上最難降的妖魔是什么净赴? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任绳矩,我火速辦了婚禮,結(jié)果婚禮上玖翅,老公的妹妹穿的比我還像新娘翼馆。我一直安慰自己,他們只是感情好金度,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布应媚。 她就那樣靜靜地躺著,像睡著了一般猜极。 火紅的嫁衣襯著肌膚如雪中姜。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,598評(píng)論 1 305
  • 那天跟伏,我揣著相機(jī)與錄音丢胚,去河邊找鬼。 笑死受扳,一個(gè)胖子當(dāng)著我的面吹牛携龟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播勘高,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼峡蟋,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼坟桅!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蕊蝗,我...
    開(kāi)封第一講書(shū)人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤仅乓,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后匿又,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體方灾,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年碌更,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了裕偿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡痛单,死狀恐怖嘿棘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情旭绒,我是刑警寧澤鸟妙,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站挥吵,受9級(jí)特大地震影響重父,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜忽匈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一房午、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧丹允,春花似錦郭厌、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至批狐,卻和暖如春扇售,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嚣艇。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工缘眶, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人髓废。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓巷懈,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親慌洪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子顶燕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容