MySQL 日志(redo log 和 undo log) 都是什么鬼胀滚?

innodb事務(wù)日志包括redo log和undo log趟济。redo log是重做日志,提供前滾操作咽笼,undo log是回滾日志顷编,提供回滾操作。

undo log不是redo log的逆向過程剑刑,其實(shí)它們都算是用來恢復(fù)的日志:

1.redo log通常是物理日志媳纬,記錄的是數(shù)據(jù)頁的物理修改,而不是某一行或某幾行修改成怎樣怎樣施掏,它用來恢復(fù)提交后的物理數(shù)據(jù)頁(恢復(fù)數(shù)據(jù)頁钮惠,且只能恢復(fù)到最后一次提交的位置)。

2.undo用來回滾行記錄到某個(gè)版本七芭。undo log一般是邏輯日志素挽,根據(jù)每行記錄進(jìn)行記錄。

1.redo log

1.1 redo log和二進(jìn)制日志的區(qū)別

redo log不是二進(jìn)制日志狸驳。雖然二進(jìn)制日志中也記錄了innodb表的很多操作预明,也能實(shí)現(xiàn)重做的功能,但是它們之間有很大區(qū)別.

1耙箍、二進(jìn)制日志是在存儲引擎的上層產(chǎn)生的贮庞,不管是什么存儲引擎,對數(shù)據(jù)庫進(jìn)行了修改都會(huì)產(chǎn)生二進(jìn)制日志究西。而redo log是innodb層產(chǎn)生的窗慎,只記錄該存儲引擎中表的修改。并且二進(jìn)制日志先于redo log被記錄。具體的見后文group commit小結(jié)遮斥。

2峦失、二進(jìn)制日志記錄操作的方法是邏輯性的語句。即便它是基于行格式的記錄方式术吗,其本質(zhì)也還是邏輯的SQL設(shè)置尉辑,如該行記錄的每列的值是多少。而redo log是在物理格式上的日志较屿,它記錄的是數(shù)據(jù)庫中每個(gè)頁的修改隧魄。

3、二進(jìn)制日志只在每次事務(wù)提交的時(shí)候一次性寫入緩存中的日志"文件"(對于非事務(wù)表的操作隘蝎,則是每次執(zhí)行語句成功后就直接寫入)购啄。而redo log在數(shù)據(jù)準(zhǔn)備修改前寫入緩存中的redo log中塘秦,然后才對緩存中的數(shù)據(jù)執(zhí)行修改操作锅知;而且保證在發(fā)出事務(wù)提交指令時(shí)夭委,先向緩存中的redo log寫入日志仿荆,寫入完成后才執(zhí)行提交動(dòng)作。

4互艾、因?yàn)槎M(jìn)制日志只在提交的時(shí)候一次性寫入牺勾,所以二進(jìn)制日志中的記錄方式和提交順序有關(guān)青柄,且一次提交對應(yīng)一次記錄冰评。而redo log中是記錄的物理頁的修改映胁,redo log文件中同一個(gè)事務(wù)可能多次記錄,最后一個(gè)提交的事務(wù)記錄會(huì)覆蓋所有未提交的事務(wù)記錄甲雅。例如事務(wù)T1解孙,可能在redo log中記錄了 T1-1,T1-2,T1-3,T1* 共4個(gè)操作务荆,其中 T1* 表示最后提交時(shí)的日志記錄,所以對應(yīng)的數(shù)據(jù)頁最終狀態(tài)是 T1* 對應(yīng)的操作結(jié)果穷遂。而且redo log是并發(fā)寫入的函匕,不同事務(wù)之間的不同版本的記錄會(huì)穿插寫入到redo log文件中,例如可能redo log的記錄方式如下:T1-1,T1-2,T2-1,T2-2,T2*,T1-3,T1* 蚪黑。

5盅惜、事務(wù)日志記錄的是物理頁的情況,它具有冪等性忌穿,因此記錄日志的方式極其簡練抒寂。冪等性的意思是多次操作前后狀態(tài)是一樣的,例如新插入一行后又刪除該行掠剑,前后狀態(tài)沒有變化屈芜。而二進(jìn)制日志記錄的是所有影響數(shù)據(jù)的操作,記錄的內(nèi)容較多。例如插入一行記錄一次井佑,刪除該行又記錄一次属铁。

1.2 redo log的基本概念

redo log包括兩部分:一是內(nèi)存中的日志緩沖(redo log buffer),該部分日志是易失性的躬翁;二是磁盤上的重做日志文件(redo log file)焦蘑,該部分日志是持久的。

在概念上盒发,innodb通過force log at commit機(jī)制實(shí)現(xiàn)事務(wù)的持久性例嘱,即在事務(wù)提交的時(shí)候,必須先將該事務(wù)的所有事務(wù)日志寫入到磁盤上的redo log file和undo log file中進(jìn)行持久化宁舰。

為了確保每次日志都能寫入到事務(wù)日志文件中拼卵,在每次將log buffer中的日志寫入日志文件的過程中都會(huì)調(diào)用一次操作系統(tǒng)的fsync操作(即fsync()系統(tǒng)調(diào)用)。因?yàn)镸ariaDB/MySQL是工作在用戶空間的明吩,MariaDB/MySQL的log buffer處于用戶空間的內(nèi)存中间学。要寫入到磁盤上的log file中(redo:ib_logfileN文件,undo:share tablespace或.ibd文件),中間還要經(jīng)過操作系統(tǒng)內(nèi)核空間的os buffer印荔,調(diào)用fsync()的作用就是將OS buffer中的日志刷到磁盤上的log file中低葫。

也就是說,從redo log buffer寫日志到磁盤的redo log file中仍律,過程如下:

在此處需要注意一點(diǎn)嘿悬,一般所說的log file并不是磁盤上的物理日志文件,而是操作系統(tǒng)緩存中的log file水泉,官方手冊上的意思也是如此(例如:With a value of 2, the contents of the?InnoDB log buffer are written to the log file?after each transaction commit and?the log file is flushed to disk approximately once per second)善涨。但說實(shí)話,這不太好理解草则,既然都稱為file了钢拧,應(yīng)該已經(jīng)屬于物理文件了。所以在本文后續(xù)內(nèi)容中都以os buffer或者file system buffer來表示官方手冊中所說的Log file炕横,然后log file則表示磁盤上的物理日志文件源内,即log file on disk。

另外份殿,之所以要經(jīng)過一層os buffer膜钓,是因?yàn)閛pen日志文件的時(shí)候,open沒有使用O_DIRECT標(biāo)志位卿嘲,該標(biāo)志位意味著繞過操作系統(tǒng)層的os buffer颂斜,IO直寫到底層存儲設(shè)備。不使用該標(biāo)志位意味著將日志進(jìn)行緩沖拾枣,緩沖到了一定容量沃疮,或者顯式fsync()才會(huì)將緩沖中的刷到存儲設(shè)備盒让。使用該標(biāo)志位意味著每次都要發(fā)起系統(tǒng)調(diào)用。比如寫abcde忿磅,不使用o_direct將只發(fā)起一次系統(tǒng)調(diào)用糯彬,使用o_object將發(fā)起5次系統(tǒng)調(diào)用。

MySQL支持用戶自定義在commit時(shí)如何將log buffer中的日志刷log file中葱她。這種控制通過變量 innodb_flush_log_at_trx_commit 的值來決定撩扒。該變量有3種值:0、1吨些、2搓谆,默認(rèn)為1。但注意豪墅,這個(gè)變量只是控制commit動(dòng)作是否刷新log buffer到磁盤泉手。

當(dāng)設(shè)置為1的時(shí)候,事務(wù)每次提交都會(huì)將log buffer中的日志寫入os buffer并調(diào)用fsync()刷到log file on disk中偶器。這種方式即使系統(tǒng)崩潰也不會(huì)丟失任何數(shù)據(jù)斩萌,但是因?yàn)槊看翁峤欢紝懭氪疟P,IO的性能較差屏轰。

當(dāng)設(shè)置為0的時(shí)候颊郎,事務(wù)提交時(shí)不會(huì)將log buffer中日志寫入到os buffer,而是每秒寫入os buffer并調(diào)用fsync()寫入到log file on disk中霎苗。也就是說設(shè)置為0時(shí)是(大約)每秒刷新寫入到磁盤中的姆吭,當(dāng)系統(tǒng)崩潰,會(huì)丟失1秒鐘的數(shù)據(jù)唁盏。

當(dāng)設(shè)置為2的時(shí)候内狸,每次提交都僅寫入到os buffer,然后是每秒調(diào)用fsync()將os buffer中的日志寫入到log file on disk厘擂。

注意昆淡,有一個(gè)變量 innodb_flush_log_at_timeout 的值為1秒,該變量表示的是刷日志的頻率刽严,很多人誤以為是控制 innodb_flush_log_at_trx_commit 值為0和2時(shí)的1秒頻率昂灵,實(shí)際上并非如此。測試時(shí)將頻率設(shè)置為5和設(shè)置為1港庄,當(dāng) innodb_flush_log_at_trx_commit 設(shè)置為0和2的時(shí)候性能基本都是不變的倔既。關(guān)于這個(gè)頻率是控制什么的恕曲,在后面的"刷日志到磁盤的規(guī)則"中會(huì)說鹏氧。

在主從復(fù)制結(jié)構(gòu)中,要保證事務(wù)的持久性和一致性佩谣,需要對日志相關(guān)變量設(shè)置為如下:

如果啟用了二進(jìn)制日志把还,則設(shè)置sync_binlog=1,即每提交一次事務(wù)同步寫到磁盤中。

總是設(shè)置innodb_flush_log_at_trx_commit=1吊履,即每提交一次事務(wù)都寫到磁盤中安皱。

上述兩項(xiàng)變量的設(shè)置保證了:每次提交事務(wù)都寫入二進(jìn)制日志和事務(wù)日志,并在提交時(shí)將它們刷新到磁盤中艇炎。

選擇刷日志的時(shí)間會(huì)嚴(yán)重影響數(shù)據(jù)修改時(shí)的性能酌伊,特別是刷到磁盤的過程。下例就測試了 innodb_flush_log_at_trx_commit 分別為0缀踪、1居砖、2時(shí)的差距。

當(dāng)前環(huán)境下驴娃, innodb_flush_log_at_trx_commit 的值為1奏候,即每次提交都刷日志到磁盤。測試此時(shí)插入10W條記錄的時(shí)間唇敞。

結(jié)果是15.48秒蔗草。

再測試值為2的時(shí)候,即每次提交都刷新到os buffer疆柔,但每秒才刷入磁盤中咒精。

結(jié)果插入時(shí)間大減,只需3.41秒婆硬。

最后測試值為0的時(shí)候狠轻,即每秒才刷到os buffer和磁盤。

結(jié)果只有2.10秒彬犯。

最后可以發(fā)現(xiàn)向楼,其實(shí)值為2和0的時(shí)候,它們的差距并不太大谐区,但2卻比0要安全的多湖蜕。它們都是每秒從os buffer刷到磁盤,它們之間的時(shí)間差體現(xiàn)在log buffer刷到os buffer上宋列。因?yàn)閷og buffer中的日志刷新到os buffer只是內(nèi)存數(shù)據(jù)的轉(zhuǎn)移昭抒,并沒有太大的開銷,所以每次提交和每秒刷入差距并不大炼杖∶鸱担可以測試插入更多的數(shù)據(jù)來比較,以下是插入100W行數(shù)據(jù)的情況坤邪。從結(jié)果可見熙含,值為2和0的時(shí)候差距并不大,但值為1的性能卻差太多艇纺。

盡管設(shè)置為0和2可以大幅度提升插入性能怎静,但是在故障的時(shí)候可能會(huì)丟失1秒鐘數(shù)據(jù)邮弹,這1秒鐘很可能有大量的數(shù)據(jù),從上面的測試結(jié)果看蚓聘,100W條記錄也只消耗了20多秒腌乡,1秒鐘大約有4W-5W條數(shù)據(jù),盡管上述插入的數(shù)據(jù)簡單夜牡,但卻說明了數(shù)據(jù)丟失的大量性与纽。更好的插入數(shù)據(jù)的做法是將值設(shè)置為1,然后修改存儲過程塘装,將每次循環(huán)都提交修改為只提交一次渣锦,這樣既能保證數(shù)據(jù)的一致性,也能提升性能氢哮,修改如下:

測試值為1時(shí)的情況袋毙。


1.3 日志塊(log block)

innodb存儲引擎中,redo log以塊為單位進(jìn)行存儲的冗尤,每個(gè)塊占512字節(jié)听盖,這稱為redo log block。所以不管是log buffer中還是os buffer中以及redo log file on disk中裂七,都是這樣以512字節(jié)的塊存儲的皆看。

每個(gè)redo log block由3部分組成:日志塊頭、日志塊尾和日志主體背零。其中日志塊頭占用12字節(jié)腰吟,日志塊尾占用8字節(jié),所以每個(gè)redo log block的日志主體部分只有512-12-8=492字節(jié)徙瓶。

因?yàn)閞edo log記錄的是數(shù)據(jù)頁的變化毛雇,當(dāng)一個(gè)數(shù)據(jù)頁產(chǎn)生的變化需要使用超過492字節(jié)()的redo log來記錄,那么就會(huì)使用多個(gè)redo log block來記錄該數(shù)據(jù)頁的變化侦镇。

日志塊頭包含4部分:

log_block_hdr_no:(4字節(jié))該日志塊在redo log buffer中的位置ID灵疮。

log_block_hdr_data_len:(2字節(jié))該log block中已記錄的log大小。寫滿該log block時(shí)為0x200壳繁,表示512字節(jié)震捣。

log_block_first_rec_group:(2字節(jié))該log block中第一個(gè)log的開始偏移位置。

lock_block_checkpoint_no:(4字節(jié))寫入檢查點(diǎn)信息的位置闹炉。

關(guān)于log block塊頭的第三部分 log_block_first_rec_group 蒿赢,因?yàn)橛袝r(shí)候一個(gè)數(shù)據(jù)頁產(chǎn)生的日志量超出了一個(gè)日志塊,這是需要用多個(gè)日志塊來記錄該頁的相關(guān)日志渣触。例如羡棵,某一數(shù)據(jù)頁產(chǎn)生了552字節(jié)的日志量,那么需要占用兩個(gè)日志塊昵观,第一個(gè)日志塊占用492字節(jié)晾腔,第二個(gè)日志塊需要占用60個(gè)字節(jié),那么對于第二個(gè)日志塊來說啊犬,它的第一個(gè)log的開始位置就是73字節(jié)(60+12)灼擂。如果該部分的值和 log_block_hdr_data_len 相等,則說明該log block中沒有新開始的日志塊觉至,即表示該日志塊用來延續(xù)前一個(gè)日志塊剔应。

日志尾只有一個(gè)部分:log_block_trl_no ,該值和塊頭的 log_block_hdr_no 相等语御。

上面所說的是一個(gè)日志塊的內(nèi)容峻贮,在redo log buffer或者redo log file on disk中,由很多l(xiāng)og block組成应闯。如下圖:


1.4 log group和redo log file

log group表示的是redo log group纤控,一個(gè)組內(nèi)由多個(gè)大小完全相同的redo log file組成。組內(nèi)redo log file的數(shù)量由變量 innodb_log_files_group 決定碉纺,默認(rèn)值為2船万,即兩個(gè)redo log file。這個(gè)組是一個(gè)邏輯的概念骨田,并沒有真正的文件來表示這是一個(gè)組耿导,但是可以通過變量 innodb_log_group_home_dir 來定義組的目錄,redo log file都放在這個(gè)目錄下态贤,默認(rèn)是在datadir下舱呻。

可以看到在默認(rèn)的數(shù)據(jù)目錄下,有兩個(gè)ib_logfile開頭的文件悠汽,它們就是log group中的redo log file箱吕,而且它們的大小完全一致且等于變量 innodb_log_file_size 定義的值。第一個(gè)文件ibdata1是在沒有開啟 innodb_file_per_table 時(shí)的共享表空間文件柿冲,對應(yīng)于開啟 innodb_file_per_table 時(shí)的.ibd文件殖氏。

在innodb將log buffer中的redo log block刷到這些log file中時(shí),會(huì)以追加寫入的方式循環(huán)輪訓(xùn)寫入姻采。即先在第一個(gè)log file(即ib_logfile0)的尾部追加寫雅采,直到滿了之后向第二個(gè)log file(即ib_logfile1)寫。當(dāng)?shù)诙€(gè)log file滿了會(huì)清空一部分第一個(gè)log file繼續(xù)寫入慨亲。

由于是將log buffer中的日志刷到log file婚瓜,所以在log file中記錄日志的方式也是log block的方式。

在每個(gè)組的第一個(gè)redo log file中刑棵,前2KB記錄4個(gè)特定的部分巴刻,從2KB之后才開始記錄log block。除了第一個(gè)redo log file中會(huì)記錄蛉签,log group中的其他log file不會(huì)記錄這2KB胡陪,但是卻會(huì)騰出這2KB的空間沥寥。如下:

redo log file的大小對innodb的性能影響非常大,設(shè)置的太大柠座,恢復(fù)的時(shí)候就會(huì)時(shí)間較長邑雅,設(shè)置的太小,就會(huì)導(dǎo)致在寫redo log的時(shí)候循環(huán)切換redo log file妈经。

1.5 redo log的格式

因?yàn)閕nnodb存儲引擎存儲數(shù)據(jù)的單元是頁(和SQL Server中一樣)淮野,所以redo log也是基于頁的格式來記錄的。默認(rèn)情況下吹泡,innodb的頁大小是16KB(由 innodb_page_size 變量控制)骤星,一個(gè)頁內(nèi)可以存放非常多的log block(每個(gè)512字節(jié)),而log block中記錄的又是數(shù)據(jù)頁的變化爆哑。

其中l(wèi)og block中492字節(jié)的部分是log body洞难,該log body的格式分為4部分:

redo_log_type:占用1個(gè)字節(jié),表示redo log的日志類型揭朝。

space:表示表空間的ID廊营,采用壓縮的方式后,占用的空間可能小于4字節(jié)萝勤。

page_no:表示頁的偏移量露筒,同樣是壓縮過的。

redo_log_body表示每個(gè)重做日志的數(shù)據(jù)部分敌卓,恢復(fù)時(shí)會(huì)調(diào)用相應(yīng)的函數(shù)進(jìn)行解析慎式。例如insert語句和delete語句寫入redo log的內(nèi)容是不一樣的。

如下圖趟径,分別是insert和delete大致的記錄方式瘪吏。


1.6 日志刷盤的規(guī)則

log buffer中未刷到磁盤的日志稱為臟日志(dirty log)。

在上面的說過蜗巧,默認(rèn)情況下事務(wù)每次提交的時(shí)候都會(huì)刷事務(wù)日志到磁盤中掌眠,這是因?yàn)樽兞?innodb_flush_log_at_trx_commit 的值為1。但是innodb不僅僅只會(huì)在有commit動(dòng)作后才會(huì)刷日志到磁盤幕屹,這只是innodb存儲引擎刷日志的規(guī)則之一蓝丙。

刷日志到磁盤有以下幾種規(guī)則:

1.發(fā)出commit動(dòng)作時(shí)。已經(jīng)說明過望拖,commit發(fā)出后是否刷日志由變量 innodb_flush_log_at_trx_commit 控制渺尘。

2.每秒刷一次。這個(gè)刷日志的頻率由變量 innodb_flush_log_at_timeout 值決定说敏,默認(rèn)是1秒鸥跟。要注意,這個(gè)刷日志頻率和commit動(dòng)作無關(guān)。

3.當(dāng)log buffer中已經(jīng)使用的內(nèi)存超過一半時(shí)医咨。

4.當(dāng)有checkpoint時(shí)枫匾,checkpoint在一定程度上代表了刷到磁盤時(shí)日志所處的LSN位置。

1.7 數(shù)據(jù)頁刷盤的規(guī)則及checkpoint

內(nèi)存中(buffer pool)未刷到磁盤的數(shù)據(jù)稱為臟數(shù)據(jù)(dirty data)拟淮。由于數(shù)據(jù)和日志都以頁的形式存在干茉,所以臟頁表示臟數(shù)據(jù)和臟日志。

上一節(jié)介紹了日志是何時(shí)刷到磁盤的惩歉,不僅僅是日志需要刷盤,臟數(shù)據(jù)頁也一樣需要刷盤俏蛮。

在innodb中撑蚌,數(shù)據(jù)刷盤的規(guī)則只有一個(gè):checkpoint。但是觸發(fā)checkpoint的情況卻有幾種搏屑。不管怎樣争涌,checkpoint觸發(fā)后,會(huì)將buffer中臟數(shù)據(jù)頁和臟日志頁都刷到磁盤辣恋。

innodb存儲引擎中checkpoint分為兩種:

sharp checkpoint:在重用redo log文件(例如切換日志文件)的時(shí)候亮垫,將所有已記錄到redo log中對應(yīng)的臟數(shù)據(jù)刷到磁盤。

fuzzy checkpoint:一次只刷一小部分的日志到磁盤伟骨,而非將所有臟日志刷盤饮潦。有以下幾種情況會(huì)觸發(fā)該檢查點(diǎn):

master thread checkpoint:由master線程控制,每秒或每10秒刷入一定比例的臟頁到磁盤携狭。

flush_lru_list checkpoint:從MySQL5.6開始可通過 innodb_page_cleaners 變量指定專門負(fù)責(zé)臟頁刷盤的page cleaner線程的個(gè)數(shù)继蜡,該線程的目的是為了保證lru列表有可用的空閑頁。

async/sync flush checkpoint:同步刷盤還是異步刷盤逛腿。例如還有非常多的臟頁沒刷到磁盤(非常多是多少稀并,有比例控制),這時(shí)候會(huì)選擇同步刷到磁盤单默,但這很少出現(xiàn)碘举;如果臟頁不是很多,可以選擇異步刷到磁盤搁廓,如果臟頁很少引颈,可以暫時(shí)不刷臟頁到磁盤

dirty page too much checkpoint:臟頁太多時(shí)強(qiáng)制觸發(fā)檢查點(diǎn),目的是為了保證緩存有足夠的空閑空間境蜕。too much的比例由變量 innodb_max_dirty_pages_pct 控制线欲,MySQL 5.6默認(rèn)的值為75,即當(dāng)臟頁占緩沖池的百分之75后汽摹,就強(qiáng)制刷一部分臟頁到磁盤李丰。

由于刷臟頁需要一定的時(shí)間來完成,所以記錄檢查點(diǎn)的位置是在每次刷盤結(jié)束之后才在redo log中標(biāo)記的逼泣。

MySQL停止時(shí)是否將臟數(shù)據(jù)和臟日志刷入磁盤趴泌,由變量innodb_fast_shutdown=102控制舟舒,默認(rèn)值為1,即停止時(shí)只做一部分purge嗜憔,忽略大多數(shù)flush操作(但至少會(huì)刷日志)秃励,在下次啟動(dòng)的時(shí)候再flush剩余的內(nèi)容,實(shí)現(xiàn)fast shutdown吉捶。

1.8 LSN超詳細(xì)分析

LSN稱為日志的邏輯序列號(log sequence number)夺鲜,在innodb存儲引擎中,lsn占用8個(gè)字節(jié)呐舔。LSN的值會(huì)隨著日志的寫入而逐漸增大币励。

根據(jù)LSN,可以獲取到幾個(gè)有用的信息:

1.數(shù)據(jù)頁的版本信息珊拼。

2.寫入的日志總量食呻,通過LSN開始號碼和結(jié)束號碼可以計(jì)算出寫入的日志量。

3.可知道檢查點(diǎn)的位置澎现。

實(shí)際上還可以獲得很多隱式的信息仅胞。

LSN不僅存在于redo log中,還存在于數(shù)據(jù)頁中剑辫,在每個(gè)數(shù)據(jù)頁的頭部干旧,有一個(gè)fil_page_lsn記錄了當(dāng)前頁最終的LSN值是多少。通過數(shù)據(jù)頁中的LSN值和redo log中的LSN值比較妹蔽,如果頁中的LSN值小于redo log中LSN值莱革,則表示數(shù)據(jù)丟失了一部分,這時(shí)候可以通過redo log的記錄來恢復(fù)到redo log中記錄的LSN值時(shí)的狀態(tài)讹开。

redo log的lsn信息可以通過 show engine innodb status 來查看盅视。MySQL 5.5版本的show結(jié)果中只有3條記錄,沒有pages flushed up to旦万。

其中:

log sequence number就是當(dāng)前的redo log(in buffer)中的lsn闹击;

log flushed up to是刷到redo log file on disk中的lsn;

pages flushed up to是已經(jīng)刷到磁盤數(shù)據(jù)頁上的LSN成艘;

last checkpoint at是上一次檢查點(diǎn)所在位置的LSN赏半。

innodb從執(zhí)行修改語句開始:

(1).首先修改內(nèi)存中的數(shù)據(jù)頁,并在數(shù)據(jù)頁中記錄LSN淆两,暫且稱之為data_in_buffer_lsn断箫;

(2).并且在修改數(shù)據(jù)頁的同時(shí)(幾乎是同時(shí))向redo log in buffer中寫入redo log,并記錄下對應(yīng)的LSN秋冰,暫且稱之為redo_log_in_buffer_lsn仲义;

(3).寫完buffer中的日志后,當(dāng)觸發(fā)了日志刷盤的幾種規(guī)則時(shí),會(huì)向redo log file on disk刷入重做日志埃撵,并在該文件中記下對應(yīng)的LSN赵颅,暫且稱之為redo_log_on_disk_lsn;

(4).數(shù)據(jù)頁不可能永遠(yuǎn)只停留在內(nèi)存中暂刘,在某些情況下饺谬,會(huì)觸發(fā)checkpoint來將內(nèi)存中的臟頁(數(shù)據(jù)臟頁和日志臟頁)刷到磁盤,所以會(huì)在本次checkpoint臟頁刷盤結(jié)束時(shí)谣拣,在redo log中記錄checkpoint的LSN位置募寨,暫且稱之為checkpoint_lsn。

(5).要記錄checkpoint所在位置很快森缠,只需簡單的設(shè)置一個(gè)標(biāo)志即可拔鹰,但是刷數(shù)據(jù)頁并不一定很快,例如這一次checkpoint要刷入的數(shù)據(jù)頁非常多辅鲸。也就是說要刷入所有的數(shù)據(jù)頁需要一定的時(shí)間來完成格郁,中途刷入的每個(gè)數(shù)據(jù)頁都會(huì)記下當(dāng)前頁所在的LSN腹殿,暫且稱之為data_page_on_disk_lsn独悴。

詳細(xì)說明如下圖:

上圖中,從上到下的橫線分別代表:時(shí)間軸锣尉、buffer中數(shù)據(jù)頁中記錄的LSN(data_in_buffer_lsn)刻炒、磁盤中數(shù)據(jù)頁中記錄的LSN(data_page_on_disk_lsn)、buffer中重做日志記錄的LSN(redo_log_in_buffer_lsn)自沧、磁盤中重做日志文件中記錄的LSN(redo_log_on_disk_lsn)以及檢查點(diǎn)記錄的LSN(checkpoint_lsn)坟奥。

假設(shè)在最初時(shí)(12:0:00)所有的日志頁和數(shù)據(jù)頁都完成了刷盤,也記錄好了檢查點(diǎn)的LSN拇厢,這時(shí)它們的LSN都是完全一致的爱谁。

假設(shè)此時(shí)開啟了一個(gè)事務(wù),并立刻執(zhí)行了一個(gè)update操作孝偎,執(zhí)行完成后访敌,buffer中的數(shù)據(jù)頁和redo log都記錄好了更新后的LSN值,假設(shè)為110衣盾。這時(shí)候如果執(zhí)行 show engine innodb status 查看各LSN的值寺旺,即圖中①處的位置狀態(tài),結(jié)果會(huì)是:

logsequence?number(110)?>logflushed?up?to(100)?=?pages?flushed?up?to?=?last?checkpoint?at

之后又執(zhí)行了一個(gè)delete語句势决,LSN增長到150阻塑。等到12:00:01時(shí),觸發(fā)redo log刷盤的規(guī)則(其中有一個(gè)規(guī)則是 innodb_flush_log_at_timeout 控制的默認(rèn)日志刷盤頻率為1秒)果复,這時(shí)redo log file on disk中的LSN會(huì)更新到和redo log in buffer的LSN一樣陈莽,所以都等于150,這時(shí) show engine innodb status ,即圖中②的位置传透,結(jié)果將會(huì)是:

logsequence?number(150)?=logflushed?up?to?>?pages?flushed?up?to(100)?=?last?checkpoint?at

再之后耘沼,執(zhí)行了一個(gè)update語句,緩存中的LSN將增長到300朱盐,即圖中③的位置群嗤。

假設(shè)隨后檢查點(diǎn)出現(xiàn),即圖中④的位置兵琳,正如前面所說狂秘,檢查點(diǎn)會(huì)觸發(fā)數(shù)據(jù)頁和日志頁刷盤,但需要一定的時(shí)間來完成躯肌,所以在數(shù)據(jù)頁刷盤還未完成時(shí)者春,檢查點(diǎn)的LSN還是上一次檢查點(diǎn)的LSN,但此時(shí)磁盤上數(shù)據(jù)頁和日志頁的LSN已經(jīng)增長了清女,即:

logsequence?number?>logflushed?up?to?和?pages?flushed?up?to?>?last?checkpoint?at

但是log flushed up to和pages flushed up to的大小無法確定钱烟,因?yàn)槿罩舅⒈P可能快于數(shù)據(jù)刷盤,也可能等于嫡丙,還可能是慢于拴袭。但是checkpoint機(jī)制有保護(hù)數(shù)據(jù)刷盤速度是慢于日志刷盤的:當(dāng)數(shù)據(jù)刷盤速度超過日志刷盤時(shí),將會(huì)暫時(shí)停止數(shù)據(jù)刷盤曙博,等待日志刷盤進(jìn)度超過數(shù)據(jù)刷盤拥刻。

等到數(shù)據(jù)頁和日志頁刷盤完畢,即到了位置⑤的時(shí)候父泳,所有的LSN都等于300般哼。

隨著時(shí)間的推移到了12:00:02,即圖中位置⑥惠窄,又觸發(fā)了日志刷盤的規(guī)則蒸眠,但此時(shí)buffer中的日志LSN和磁盤中的日志LSN是一致的,所以不執(zhí)行日志刷盤杆融,即此時(shí) show engine innodb status 時(shí)各種lsn都相等楞卡。

隨后執(zhí)行了一個(gè)insert語句,假設(shè)buffer中的LSN增長到了800擒贸,即圖中位置⑦臀晃。此時(shí)各種LSN的大小和位置①時(shí)一樣。

隨后執(zhí)行了提交動(dòng)作介劫,即位置⑧徽惋。默認(rèn)情況下,提交動(dòng)作會(huì)觸發(fā)日志刷盤座韵,但不會(huì)觸發(fā)數(shù)據(jù)刷盤险绘,所以 show engine innodb status 的結(jié)果是:

logsequence?number?=logflushed?up?to?>?pages?flushed?up?to?=?last?checkpoint?at

最后隨著時(shí)間的推移踢京,檢查點(diǎn)再次出現(xiàn),即圖中位置⑨宦棺。但是這次檢查點(diǎn)不會(huì)觸發(fā)日志刷盤瓣距,因?yàn)槿罩镜腖SN在檢查點(diǎn)出現(xiàn)之前已經(jīng)同步了。假設(shè)這次數(shù)據(jù)刷盤速度極快代咸,快到一瞬間內(nèi)完成而無法捕捉到狀態(tài)的變化蹈丸,這時(shí) show engine innodb status 的結(jié)果將是各種LSN相等。

1.9 innodb的恢復(fù)行為

在啟動(dòng)innodb的時(shí)候呐芥,不管上次是正常關(guān)閉還是異常關(guān)閉逻杖,總是會(huì)進(jìn)行恢復(fù)操作。

因?yàn)閞edo log記錄的是數(shù)據(jù)頁的物理變化思瘟,因此恢復(fù)的時(shí)候速度比邏輯日志(如二進(jìn)制日志)要快很多荸百。而且,innodb自身也做了一定程度的優(yōu)化滨攻,讓恢復(fù)速度變得更快够话。

重啟innodb時(shí),checkpoint表示已經(jīng)完整刷到磁盤上data page上的LSN光绕,因此恢復(fù)時(shí)僅需要恢復(fù)從checkpoint開始的日志部分女嘲。例如,當(dāng)數(shù)據(jù)庫在上一次checkpoint的LSN為10000時(shí)宕機(jī)奇钞,且事務(wù)是已經(jīng)提交過的狀態(tài)澡为。啟動(dòng)數(shù)據(jù)庫時(shí)會(huì)檢查磁盤中數(shù)據(jù)頁的LSN漂坏,如果數(shù)據(jù)頁的LSN小于日志中的LSN景埃,則會(huì)從檢查點(diǎn)開始恢復(fù)。

還有一種情況顶别,在宕機(jī)前正處于checkpoint的刷盤過程谷徙,且數(shù)據(jù)頁的刷盤進(jìn)度超過了日志頁的刷盤進(jìn)度。這時(shí)候一宕機(jī)驯绎,數(shù)據(jù)頁中記錄的LSN就會(huì)大于日志頁中的LSN完慧,在重啟的恢復(fù)過程中會(huì)檢查到這一情況,這時(shí)超出日志進(jìn)度的部分將不會(huì)重做剩失,因?yàn)檫@本身就表示已經(jīng)做過的事情屈尼,無需再重做。

另外拴孤,事務(wù)日志具有冪等性脾歧,所以多次操作得到同一結(jié)果的行為在日志中只記錄一次。而二進(jìn)制日志不具有冪等性演熟,多次操作會(huì)全部記錄下來鞭执,在恢復(fù)的時(shí)候會(huì)多次執(zhí)行二進(jìn)制日志中的記錄司顿,速度就慢得多。例如兄纺,某記錄中id初始值為2大溜,通過update將值設(shè)置為了3,后來又設(shè)置成了2估脆,在事務(wù)日志中記錄的將是無變化的頁钦奋,根本無需恢復(fù);而二進(jìn)制會(huì)記錄下兩次update操作疙赠,恢復(fù)時(shí)也將執(zhí)行這兩次update操作锨苏,速度比事務(wù)日志恢復(fù)更慢。

1.10 和redo log有關(guān)的幾個(gè)變量

innodb_flush_log_at_trx_commit=01|2# 指定何時(shí)將事務(wù)日志刷到磁盤棺聊,默認(rèn)為1伞租。

0表示每秒將"log buffer"同步到"os buffer"且從"os buffer"刷到磁盤日志文件中。

1表示每事務(wù)提交都將"log buffer"同步到"os buffer"且從"os buffer"刷到磁盤日志文件中限佩。

2表示每事務(wù)提交都將"log buffer"同步到"os buffer"但每秒才從"os buffer"刷到磁盤日志文件中葵诈。

innodb_log_buffer_size:# log buffer的大小,默認(rèn)8M

innodb_log_file_size:#事務(wù)日志的大小祟同,默認(rèn)5M

innodb_log_files_group =2:# 事務(wù)日志組中的事務(wù)日志文件個(gè)數(shù)作喘,默認(rèn)2個(gè)

innodb_log_group_home_dir =./:# 事務(wù)日志組路徑,當(dāng)前目錄表示數(shù)據(jù)目錄

innodb_mirrored_log_groups =1:# 指定事務(wù)日志組的鏡像組個(gè)數(shù)晕城,但鏡像功能好像是強(qiáng)制關(guān)閉的泞坦,所以只有一個(gè)log group。在MySQL5.7中該變量已經(jīng)移除砖顷。

2.undo log

2.1 基本概念

undo log有兩個(gè)作用:提供回滾和多個(gè)行版本控制(MVCC)贰锁。

在數(shù)據(jù)修改的時(shí)候,不僅記錄了redo滤蝠,還記錄了相對應(yīng)的undo豌熄,如果因?yàn)槟承┰驅(qū)е率聞?wù)失敗或回滾了,可以借助該undo進(jìn)行回滾物咳。

undo log和redo log記錄物理日志不一樣锣险,它是邏輯日志。可以認(rèn)為當(dāng)delete一條記錄時(shí)览闰,undo log中會(huì)記錄一條對應(yīng)的insert記錄芯肤,反之亦然,當(dāng)update一條記錄時(shí)压鉴,它記錄一條對應(yīng)相反的update記錄崖咨。

當(dāng)執(zhí)行rollback時(shí),就可以從undo log中的邏輯記錄讀取到相應(yīng)的內(nèi)容并進(jìn)行回滾晴弃。有時(shí)候應(yīng)用到行版本控制的時(shí)候掩幢,也是通過undo log來實(shí)現(xiàn)的:當(dāng)讀取的某一行被其他事務(wù)鎖定時(shí)逊拍,它可以從undo log中分析出該行記錄以前的數(shù)據(jù)是什么,從而提供該行版本信息际邻,讓用戶實(shí)現(xiàn)非鎖定一致性讀取芯丧。

undo log是采用段(segment)的方式來記錄的,每個(gè)undo操作在記錄的時(shí)候占用一個(gè)undo log segment世曾。

另外缨恒,undo log也會(huì)產(chǎn)生redo log,因?yàn)閡ndo log也要實(shí)現(xiàn)持久性保護(hù)轮听。

2.2 undo log的存儲方式

innodb存儲引擎對undo的管理采用段的方式骗露。rollback segment稱為回滾段薪韩,每個(gè)回滾段中有1024個(gè)undo log segment筐赔。

在以前老版本,只支持1個(gè)rollback segment朗兵,這樣就只能記錄1024個(gè)undo log segment述寡。后來MySQL5.5可以支持128個(gè)rollback segment柿隙,即支持128*1024個(gè)undo操作,還可以通過變量 innodb_undo_logs (5.6版本以前該變量是 innodb_rollback_segments )自定義多少個(gè)rollback segment鲫凶,默認(rèn)值為128禀崖。

undo log默認(rèn)存放在共享表空間中。

如果開啟了 innodb_file_per_table 螟炫,將放在每個(gè)表的.ibd文件中波附。

在MySQL5.6中,undo的存放位置還可以通過變量 innodb_undo_directory 來自定義存放目錄昼钻,默認(rèn)值為"."表示datadir掸屡。

默認(rèn)rollback segment全部寫在一個(gè)文件中,但可以通過設(shè)置變量 innodb_undo_tablespaces 平均分配到多少個(gè)文件中换吧。該變量默認(rèn)值為0折晦,即全部寫入一個(gè)表空間文件钥星。該變量為靜態(tài)變量沾瓦,只能在數(shù)據(jù)庫示例停止?fàn)顟B(tài)下修改,如寫入配置文件或啟動(dòng)時(shí)帶上對應(yīng)參數(shù)谦炒。但是innodb存儲引擎在啟動(dòng)過程中提示贯莺,不建議修改為非0的值,如下:

2017-03-31?13:16:00?7f665bfab720?InnoDB:?Expected?to?open?3?undo?tablespaces?but?was?able

2017-03-31?13:16:00?7f665bfab720?InnoDB:?to?find?only?0?undo?tablespaces.

2017-03-31?13:16:00?7f665bfab720?InnoDB:?Set?the?innodb_undo_tablespaces?parameter?to?the

2017-03-31?13:16:00?7f665bfab720?InnoDB:?correct?value?and?retry.?Suggested?value?is?0

2.3 和undo log相關(guān)的變量

undo相關(guān)的變量在MySQL5.6中已經(jīng)變得很少宁改。如下:它們的意義在上文中已經(jīng)解釋了缕探。


2.4 delete/update操作的內(nèi)部機(jī)制

當(dāng)事務(wù)提交的時(shí)候,innodb不會(huì)立即刪除undo log还蹲,因?yàn)楹罄m(xù)還可能會(huì)用到undo log爹耗,如隔離級別為repeatable read時(shí)耙考,事務(wù)讀取的都是開啟事務(wù)時(shí)的最新提交行版本,只要該事務(wù)不結(jié)束潭兽,該行版本就不能刪除倦始,即undo log不能刪除。

但是在事務(wù)提交的時(shí)候山卦,會(huì)將該事務(wù)對應(yīng)的undo log放入到刪除列表中鞋邑,未來通過purge來刪除。并且提交事務(wù)時(shí)账蓉,還會(huì)判斷undo log分配的頁是否可以重用枚碗,如果可以重用,則會(huì)分配給后面來的事務(wù)铸本,避免為每個(gè)獨(dú)立的事務(wù)分配獨(dú)立的undo log頁而浪費(fèi)存儲空間和性能肮雨。

通過undo log記錄delete和update操作的結(jié)果發(fā)現(xiàn):(insert操作無需分析,就是插入行而已)

delete操作實(shí)際上不會(huì)直接刪除箱玷,而是將delete對象打上delete flag酷含,標(biāo)記為刪除,最終的刪除操作是purge線程完成的汪茧。

update分為兩種情況:update的列是否是主鍵列椅亚。

如果不是主鍵列,在undo log中直接反向記錄是如何update的舱污。即update是直接進(jìn)行的呀舔。

如果是主鍵列,update分兩部執(zhí)行:先刪除該行扩灯,再插入一行目標(biāo)行媚赖。

3.binlog和事務(wù)日志的先后順序及group commit

如果事務(wù)不是只讀事務(wù),即涉及到了數(shù)據(jù)的修改珠插,默認(rèn)情況下會(huì)在commit的時(shí)候調(diào)用fsync()將日志刷到磁盤惧磺,保證事務(wù)的持久性。

但是一次刷一個(gè)事務(wù)的日志性能較低捻撑,特別是事務(wù)集中在某一時(shí)刻時(shí)事務(wù)量非常大的時(shí)候磨隘。innodb提供了group commit功能,可以將多個(gè)事務(wù)的事務(wù)日志通過一次fsync()刷到磁盤中顾患。

因?yàn)槭聞?wù)在提交的時(shí)候不僅會(huì)記錄事務(wù)日志番捂,還會(huì)記錄二進(jìn)制日志,但是它們誰先記錄呢江解?二進(jìn)制日志是MySQL的上層日志设预,先于存儲引擎的事務(wù)日志被寫入。

在MySQL5.6以前犁河,當(dāng)事務(wù)提交(即發(fā)出commit指令)后鳖枕,MySQL接收到該信號進(jìn)入commit prepare階段魄梯;進(jìn)入prepare階段后,立即寫內(nèi)存中的二進(jìn)制日志宾符,寫完內(nèi)存中的二進(jìn)制日志后就相當(dāng)于確定了commit操作画恰;然后開始寫內(nèi)存中的事務(wù)日志;最后將二進(jìn)制日志和事務(wù)日志刷盤吸奴,它們?nèi)绾嗡⒈P允扇,分別由變量 sync_binlog 和 innodb_flush_log_at_trx_commit 控制。

但因?yàn)橐WC二進(jìn)制日志和事務(wù)日志的一致性则奥,在提交后的prepare階段會(huì)啟用一個(gè)prepare_commit_mutex鎖來保證它們的順序性和一致性考润。但這樣會(huì)導(dǎo)致開啟二進(jìn)制日志后group commmit失效,特別是在主從復(fù)制結(jié)構(gòu)中读处,幾乎都會(huì)開啟二進(jìn)制日志糊治。

在MySQL5.6中進(jìn)行了改進(jìn)。提交事務(wù)時(shí)罚舱,在存儲引擎層的上一層結(jié)構(gòu)中會(huì)將事務(wù)按序放入一個(gè)隊(duì)列井辜,隊(duì)列中的第一個(gè)事務(wù)稱為leader,其他事務(wù)稱為follower管闷,leader控制著follower的行為粥脚。雖然順序還是一樣先刷二進(jìn)制,再刷事務(wù)日志包个,但是機(jī)制完全改變了:刪除了原來的prepare_commit_mutex行為刷允,也能保證即使開啟了二進(jìn)制日志,group commit也是有效的碧囊。

MySQL5.6中分為3個(gè)步驟:flush階段树灶、sync階段、commit階段糯而。

flush階段:向內(nèi)存中寫入每個(gè)事務(wù)的二進(jìn)制日志天通。

sync階段:將內(nèi)存中的二進(jìn)制日志刷盤。若隊(duì)列中有多個(gè)事務(wù)熄驼,那么僅一次fsync操作就完成了二進(jìn)制日志的刷盤操作像寒。這在MySQL5.6中稱為BLGC(binary log group commit)。

commit階段:leader根據(jù)順序調(diào)用存儲引擎層事務(wù)的提交谜洽,由于innodb本就支持group commit萝映,所以解決了因?yàn)殒i prepare_commit_mutex 而導(dǎo)致的group commit失效問題。

在flush階段寫入二進(jìn)制日志到內(nèi)存中阐虚,但是不是寫完就進(jìn)入sync階段的,而是要等待一定的時(shí)間蚌卤,多積累幾個(gè)事務(wù)的binlog一起進(jìn)入sync階段实束,等待時(shí)間由變量 binlog_max_flush_queue_time 決定奥秆,默認(rèn)值為0表示不等待直接進(jìn)入sync,設(shè)置該變量為一個(gè)大于0的值的好處是group中的事務(wù)多了咸灿,性能會(huì)好一些构订,但是這樣會(huì)導(dǎo)致事務(wù)的響應(yīng)時(shí)間變慢,所以建議不要修改該變量的值避矢,除非事務(wù)量非常多并且不斷的在寫入和更新悼瘾。

進(jìn)入到sync階段,會(huì)將binlog從內(nèi)存中刷入到磁盤审胸,刷入的數(shù)量和單獨(dú)的二進(jìn)制日志刷盤一樣亥宿,由變量 sync_binlog 控制。

當(dāng)有一組事務(wù)在進(jìn)行commit階段時(shí)砂沛,其他新事務(wù)可以進(jìn)行flush階段烫扼,它們本就不會(huì)相互阻塞,所以group commit會(huì)不斷生效碍庵。當(dāng)然映企,group commit的性能和隊(duì)列中的事務(wù)數(shù)量有關(guān),如果每次隊(duì)列中只有1個(gè)事務(wù)静浴,那么group commit和單獨(dú)的commit沒什么區(qū)別堰氓,當(dāng)隊(duì)列中事務(wù)越來越多時(shí),即提交事務(wù)越多越快時(shí)苹享,group commit的效果越明顯豆赏。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市富稻,隨后出現(xiàn)的幾起案子掷邦,更是在濱河造成了極大的恐慌,老刑警劉巖椭赋,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抚岗,死亡現(xiàn)場離奇詭異,居然都是意外死亡哪怔,警方通過查閱死者的電腦和手機(jī)宣蔚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來认境,“玉大人胚委,你說我怎么就攤上這事〔嫘牛” “怎么了亩冬?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我硅急,道長覆享,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任营袜,我火速辦了婚禮撒顿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘荚板。我一直安慰自己凤壁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布跪另。 她就那樣靜靜地躺著拧抖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪罚斗。 梳的紋絲不亂的頭發(fā)上徙鱼,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天,我揣著相機(jī)與錄音针姿,去河邊找鬼袱吆。 笑死,一個(gè)胖子當(dāng)著我的面吹牛距淫,可吹牛的內(nèi)容都是我干的绞绒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼榕暇,長吁一口氣:“原來是場噩夢啊……” “哼蓬衡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起彤枢,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤狰晚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后缴啡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體壁晒,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年业栅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了秒咐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,764評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡碘裕,死狀恐怖携取,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情帮孔,我是刑警寧澤雷滋,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響惊豺,放射性物質(zhì)發(fā)生泄漏燎孟。R本人自食惡果不足惜禽作,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一尸昧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧旷偿,春花似錦烹俗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至茫负,卻和暖如春蕉鸳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背忍法。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工潮尝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人饿序。 一個(gè)月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓勉失,卻偏偏與公主長得像,于是被迫代替她去往敵國和親原探。 傳聞我的和親對象是個(gè)殘疾皇子乱凿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評論 2 354

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