MySQL 鎖詳解

InnoDB 鎖

數據庫使用鎖是為了支持更好的并發,提供數據的完整性和一致性。InnoDB是一個支持行鎖的存儲引擎,鎖的類型有:共享鎖(S)、排他鎖(X)、意向共享(IS)、意向排他(IX)。為了提供更好的并發,
InnoDB提供了非鎖定讀:不需要等待訪問行上的鎖釋放,讀取行的一個快照。該方法是通過InnoDB的一個特性:MVCC來實現的。

MySQL InnoDB存儲引擎,實現的是基于多版本的并發控制協議——MVCC (Multi-Version Concurrency Control) (注:與MVCC相對的,是基于鎖的并發控制,Lock-Based Concurrency Control)。MVCC最大的好處,相信也是耳熟能詳:讀不加鎖,讀寫不沖突。在讀多寫少的OLTP應用中,讀寫不沖突是非常重要的,極大的增加了系統的并發性能,這也是為什么現階段,幾乎所有的RDBMS,都支持了MVCC。

在MVCC并發控制中,讀操作可以分成兩類:快照讀 (snapshot read)當前讀 (current read)。快照讀,讀取的是記錄的可見版本 (有可能是歷史版本),不用加鎖。當前讀,讀取的是記錄的最新版本,并且,當前讀返回的記錄,都會加上鎖,保證其他事務不會再并發修改這條記錄。

在一個支持MVCC并發控制的系統中,哪些讀操作是快照讀?哪些操作又是當前讀呢?以MySQL InnoDB為例:

  • 快照讀:簡單的select操作,屬于快照讀,不加鎖。(當然,也有例外,下面會分析)

    • select * from table where ?;
  • 當前讀:特殊的讀操作,插入/更新/刪除操作,屬于當前讀,需要加鎖。

    • select * from table where ? lock in share mode;

    • select * from table where ? for update;

    • insert into table values (…);

    • update table set ? where ?;

    • delete from table where ?;

    所有以上的語句,都屬于當前讀,讀取記錄的最新版本。并且,讀取之后,還需要保證其他并發事務不能修改當前記錄,對讀取記錄加鎖。其中,除了第一條語句,對讀取記錄加S鎖 (共享鎖)外,其他的操作,都加的是X鎖 (排它鎖)。

  • 共享鎖【S鎖】
    又稱讀鎖,若事務T對數據對象A加上S鎖,則事務T可以讀A但不能修改A,其他事務只能再對A加S鎖,而不能加X鎖,直到T釋放A上的S鎖。這保證了其他事務可以讀A,但在T釋放A上的S鎖之前不能對A做任何修改。

  • 排他鎖【X鎖】
    又稱寫鎖。若事務T對數據對象A加上X鎖,事務T可以讀A也可以修改A,其他事務不能再對A加任何鎖,直到T釋放A上的鎖。這保證了其他事務在T釋放A上的鎖之前不能再讀取和修改A。

為什么將 插入/更新/刪除 操作,都歸為當前讀?可以看看下面這個 更新 操作,在數據庫中的執行流程:



從圖中,可以看到,一個Update操作的具體流程。當Update SQL被發給MySQL后,MySQL Server會根據where條件,讀取第一條滿足條件的記錄,然后InnoDB引擎會將第一條記錄返回,并加鎖 (current read)。待MySQL Server收到這條加鎖的記錄之后,會再發起一個Update請求,更新這條記錄。一條記錄操作完成,再讀取下一條記錄,直至沒有滿足條件的記錄為止。因此,Update操作內部,就包含了一個當前讀。同理,Delete操作也一樣。Insert操作會稍微有些不同,簡單來說,就是Insert操作可能會觸發Unique Key的沖突檢查,也會進行一個當前讀。

MySQL/InnoDB定義的4種隔離級別:

Read Uncommited
可以讀取未提交記錄。此隔離級別,不會使用,忽略。

Read Committed (RC)
快照讀:忽略,本文不考慮。
當前讀:RC隔離級別保證對讀取到的記錄加鎖 (記錄鎖),存在幻讀現象。

Repeatable Read (RR)
快照讀:忽略,本文不考慮。
當前讀:RR隔離級別保證對讀取到的記錄加鎖 (記錄鎖),同時保證對讀取的范圍加鎖,新的滿足查詢條件的記錄不能夠插入 (間隙鎖),不存在幻讀現象。注意:這里的不存在幻讀,是指使用select ... for update 在同一個事務中查詢,不會出現兩次不一樣的結果

Serializable
從MVCC并發控制退化為基于鎖的并發控制。不區別快照讀與當前讀,所有的讀操作均為當前讀,讀加讀鎖 (S鎖),寫加寫鎖 (X鎖)。
Serializable隔離級別下,讀寫沖突,因此并發度急劇下降,在MySQL/InnoDB下不建議使用。

上面說的當前讀就是上面列出來的 select .. for update, update , delete, insert 等語句

加鎖情況

我們分析一下 RR RC級別下配合不同索引情況的加鎖情況
表goods 定義是 :
id: 主鍵
name: unique key
stock :無索引

組合一:id主鍵+RC

--------------------- SESSION 1 -------------------------
mysql> begin;
mysql> select * from goods ;
+----+--------+-------+
| id | name   | stock |
+----+--------+-------+
|  1 | prod11 |    15 |
+----+--------+-------+
mysql> update goods set stock =20 where id =1 
# 這里已經鎖住了id=1的記錄, 因為上面說了update,delete, insert, select ... for update都會讀當前讀,會觸發鎖機制
# 所以用這幾個任何一個命令都能鎖住記錄
mysql> select * from goods where id = 1 for update;
# 同樣鎖住id=1的記錄

--------------------- SESSION 2 -------------------------
mysql> select * from goods where id=1;
+----+--------+-------+
| id | name   | stock |
+----+--------+-------+
|  1 | prod11 |    15 |
+----+--------+-------+
1 row in set (0.00 sec)
# 默認的select 不會使用鎖,它是快照讀,不是當前讀

mysql> select * from goods where id=1 for update;
# 這里會阻塞直到session 1事務結束

結論:id是主鍵時,此SQL只需要在id=1這條記錄上加X鎖即可。

組合二:id唯一索引+RC

--------------------- SESSION 1 -------------------------
mysql> begin;
mysql> select * from goods where name="prod12" for update;
+----+--------+-------+
| id | name   | stock |
+----+--------+-------+
|  2 | prod12 |  1000 |
+----+--------+-------+

--------------------- SESSION 2 -------------------------
mysql> update goods set stock =20 where id=2;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> update goods set stock =20 where name="prod12";
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
# 使用唯一索引的時候, 無論使用唯一索引name, 還是主鍵索引id 都不能讀到當前讀,這是因為唯一索引會把唯一索引和主鍵索引都加鎖
medish (1).jpg

此組合中,name是unique索引,而主鍵是id列。此時,加鎖的情況由于組合一有所不同。由于name是unique索引,因此delete語句會選擇走name列的索引進行where條件的過濾,在找到name="prod12"的記錄后,首先會將unique索引上的name="prod12"索引記錄加上X鎖,同時,會根據讀取到的id列,回主鍵索引(聚簇索引),然后將聚簇索引上的id=2 對應的主鍵索引項加X鎖。為什么聚簇索引上的記錄也要加鎖?試想一下,如果并發的一個SQL,是通過主鍵索引來更新:update goods set name= "prod30" where id=2; 此時,如果delete語句沒有將主鍵索引上的記錄加鎖,那么并發的update就會感知不到delete語句的存在,違背了同一記錄上的更新/刪除需要串行執行的約束。
所以主鍵和唯一索引都要加X鎖,防止別的update, delete 會使用主鍵來查詢修改

結論 若name列是unique列,其上有unique索引。那么SQL需要加兩個X鎖,一個對應于name unique索引上的name="prod12"的記錄,另一把鎖對應于聚簇索引上的[id=2, name="prod"]的記錄。

組合三:非唯一索引+RC

跟組合二差不多,與組合二唯一的區別在于,組合二最多只有一個滿足等值查詢的記錄,而組合三會將所有滿足查詢條件的記錄都加鎖。結論 對應的所有滿足SQL查詢條件的記錄,都會被加鎖。同時,這些記錄在主鍵索引上的記錄,也會被加鎖。

--------------------- SESSION 1 -------------------------
mysql> select * from goods where id=7
    -> ;
+----+--------+-------+
| id | name   | stock |
+----+--------+-------+
|  7 | prod17 |   107 |
+----+--------+-------+
1 row in set (0.00 sec)

mysql> delete from goods where stock=107;
Query OK, 1 rows affected (0.00 sec)

--------------------- SESSION 2 -------------------------
mysql> select * from goods where stock=107 for update;
# 普通索引 被鎖 阻塞
mysql> select * from goods where id=7 for update;
# 聚簇索引(主鍵索引被鎖)
mysql> select * from goods where name=7 for update;
# 其他字段頁被鎖,因為主鍵索引被鎖,整行被鎖

組合四:id無索引+RC
stock去掉索引

--------------------- SESSION 1 -------------------------
mysql> begin;
mysql> select * from goods where stock=20 for update;
+----+--------+-------+
| id | name   | stock |
+----+--------+-------+
|  5 | prod15 |    20 |
| 23 | prod21 |    20 |
+----+--------+-------+
# 使用stock 來查詢,stock是沒有索引

--------------------- SESSION 2 -------------------------
mysql> begin;
mysql> select * from goods where id=1 for update
mysql> select * from goods for update;
mysql> update goods set stock =20 where 1;

# 無論查什么當前讀,更新記錄,都被阻塞,說明整個表都被鎖住了。
medish (3).jpg

由于stock列上沒有索引,因此只能走聚簇索引,進行全部掃描。從圖中可以看到,滿足條件的記錄有兩條,但是,聚簇索引上所有的記錄,都被加上了X鎖。無論記錄是否滿足條件,全部被加上X鎖。既不是加表鎖,也不是在滿足條件的記錄上加行鎖。

有人可能會問?為什么不是只在滿足條件的記錄上加鎖呢?這是由于MySQL的實現決定的。如果一個條件無法通過索引快速過濾,那么存儲引擎層面就會將所有記錄加鎖后返回,然后由MySQL Server層進行過濾。因此也就把所有的記錄,都鎖上了。

注:在實際的實現中,MySQL有一些改進,在MySQL Server過濾條件,發現不滿足后,會調用unlock_row方法,把不滿足條件的記錄放鎖 (違背了2PL的約束)。這樣做,保證了最后只會持有滿足條件記錄上的鎖,但是每條記錄的加鎖操作還是不能省略的。

結論:若stock列上沒有索引,SQL會走聚簇索引的全掃描進行過濾,由于過濾是由MySQL Server層面進行的。因此每條記錄,無論是否滿足條件,都會被加上X鎖。但是,為了效率考量,MySQL做了優化,對于不滿足條件的記錄,會在判斷后放鎖,最終持有的,是滿足條件的記錄上的鎖,但是不滿足條件的記錄上的加鎖/放鎖動作不會省略。同時,優化也違背了2PL的約束。

注意!!!! 這里說的對不滿足條件記錄會有 加鎖\放鎖的動作,但是!!實際操作中,還是所有記錄都被鎖住了,根本沒有放鎖?這是為什么??

組合五:id主鍵+RR
這個跟 id主鍵+RC組合效果一樣,都是鎖住被查詢出來的記錄

組合六: id唯一索引+RR
這個跟 id唯一索引+RC組合效果一樣,都是將唯一索引和聚簇索引的記錄鎖住

組合七:id非唯一索引+RR
還記得前面提到的MySQL的四種隔離級別的區別嗎?RC隔離級別允許幻讀,而RR隔離級別,不允許存在幻讀。但是在組合五、組合六中,加鎖行為又是與RC下的加鎖行為完全一致。那么RR隔離級別下,如何防止幻讀呢?問題的答案,就在組合七中揭曉。
我們先看看如果是級別RC,出現的幻讀情況

--------------------- SESSION 1 -------------------------
# RC級別
mysql> begin;
mysql> select * from goods where stock=15 for update;
+----+--------+-------+
| id | name   | stock |
+----+--------+-------+
| 21 | prod20 |    15 |
| 22 | prod22 |    15 |
+----+--------+-------+

--------------------- SESSION 2 -------------------------
mysql> begin;
mysql> insert into goods values(24, 'prod24', 15);
# 這里依然可以插入stock=15的記錄,因為session 1直接對21 22兩條stock=15記錄加鎖了
mysql> commit;

--------------------- SESSION 1 -------------------------
mysql> select * from goods where stock=15 for update;
+----+--------+-------+
| id | name   | stock |
+----+--------+-------+
| 21 | prod20 |    15 |
| 22 | prod22 |    15 |
| 24 | prod24 |    15 |
+----+--------+-------+
# 多了session 2提交的數據,同一個事務兩次select for update 居然不同了。 這就是幻讀!!

再看看 RR級別會不會出現幻讀

--------------------- SESSION 1 -------------------------
# RR級別
mysql> begin;
mysql> select * from goods where stock=15 for update;
+----+--------+-------+
| id | name   | stock |
+----+--------+-------+
| 21 | prod20 |    15 |
| 22 | prod22 |    15 |
+----+--------+-------+

--------------------- SESSION 2 -------------------------
mysql> begin;
mysql> insert into goods values(24, 'prod24', 15);
#  這里跟RC級別不一樣了, stock=15的24記錄根本插不進去!
# 這就是為什么RR級別不會出現幻讀的原因,因為不能給其他事務插足
medish (4).jpg

RR與RC 幻讀總結

RR隔離級別其實這個多出來的GAP鎖,相對于RC隔離級別,不會出現幻讀的關鍵。確實,GAP鎖鎖住的位置,也不是記錄本身,而是兩條記錄之間的GAP。所謂幻讀,就是同一個事務,連續做兩次當前讀 (例如:select * from goods where stock=15 for update;),那么這兩次當前讀返回的是完全相同的記錄 (記錄數量一致,記錄本身也一致),第二次的當前讀,不會比第一次返回更多的記錄 (幻象)。

如何保證兩次當前讀返回一致的記錄,那就需要在第一次當前讀與第二次當前讀之間,其他的事務不會插入新的滿足條件的記錄并提交。為了實現這個功能,GAP鎖應運而生。

mysql> select * from goods order by stock;
+----+--------+-------+
| id | name   | stock |
+----+--------+-------+
|  7 | prod17 |    10 |
|  8 | prod18 |    10 |
| 22 | prod22 |    15 |
| 21 | prod20 |    15 |
| 23 | prod21 |    20 |
|  5 | prod15 |    20 |
| 10 | pro10  |    50 
+----+--------+-------+

上面的記錄所示,有哪些位置可以插入新的滿足條件的項 stock= 15,考慮到B+樹索引的有序性,stock索引的存儲一定是有序的,滿足條件的項一定是連續存放的。
所以stock在[10, 15]之間, [15, 20]之間都是可以被其他事務插入stock=15的記錄的。因此要想杜絕幻讀,這個gap鎖也就是間隙鎖,必須鎖住10-15, 15-20之間的記錄。
我們繼續看session 2, 上面示例看到不能插入stock=15的記錄,其實10-15, 15-20之間都是不可以插入的

--------------------- SESSION 2 -------------------------
mysql> insert into goods values('', 'prod24', 11);
mysql> insert into goods values('', 'prod24', 13);
mysql> insert into goods values('', 'prod24', 18);
mysql> insert into goods values('', 'prod24', 19);
# 在10-15, 15-20之間的記錄都不能插入,因為他們都有可能被放入stock=15的記錄

mysql> insert into goods values('', 'prod24', 21);
Query OK, 1 row affected, 1 warning (0.00 sec)

mysql> insert into goods values('', 'prod24', 8);
Query OK, 1 row affected, 1 warning (0.00 sec)
# 超出這個范圍的是可以插入的。

如果我們查的stock=14沒有數據,那么會不會也有gap鎖?答案是:有的。
我發現如果索引是主鍵也會有這個gap鎖,當然查詢的是一個范圍

--------------------- SESSION 1 -------------------------
mysql> select * from goods where id > 16 for update;
+----+--------+-------+
| id | name   | stock |
+----+--------+-------+
| 21 | prod20 |    15 |
| 22 | prod22 |    15 |
| 23 | prod21 |    20 |
+----+--------+-------+

--------------------- SESSION 2 -------------------------
mysql> insert into goods values(9, 'prod24', 11);
ERROR 1062 (23000): Duplicate entry '9' for key 'PRIMARY'
mysql> insert into goods values(12, 'prod24', '');
# 發現>16的 還有10-15這個區間也被鎖住了

總結 只要是范圍查詢,都會有gap鎖。

組合八:id無索引+RR

medish (5).jpg

如圖,這是一個很恐怖的現象。首先,聚簇索引上的所有記錄,都被加上了X鎖。其次,聚簇索引每條記錄間的間隙(GAP),也同時被加上了GAP鎖。這個示例表,只有6條記錄,一共需要6個記錄鎖,7個GAP鎖。試想,如果表上有1000萬條記錄呢?

在這種情況下,這個表上,除了不加鎖的快照度,其他任何加鎖的并發SQL,均不能執行,不能更新,不能刪除,不能插入,全表被鎖死。

當然,跟組合四類似,這個情況下,MySQL也做了一些優化,就是所謂的semi-consistent read。semi-consistent read開啟的情況下,對于不滿足查詢條件的記錄,MySQL會提前放鎖。針對上面的這個用例,就是除了記錄[d,10],[g,10]之外,所有的記錄鎖都會被釋放,同時不加GAP鎖。semi-consistent read如何觸發:要么是read committed隔離級別;要么是Repeatable Read隔離級別,同時設置了 innodb_locks_unsafe_for_binlog 參數

結論:在Repeatable Read隔離級別下,如果進行全表掃描的當前讀,那么會鎖上表中的所有記錄,同時會鎖上聚簇索引內的所有GAP,杜絕所有的并發 更新/刪除/插入 操作。當然,也可以通過觸發semi-consistent read,來緩解加鎖開銷與并發影響,但是semi-consistent read本身也會帶來其他問題,不建議使用。

組合九:Serializable
Serializable隔離級別,影響的是SQL1:select * from t1 where id = 10; 這條SQL,在RC,RR隔離級別下,都是快照讀,不加鎖。但是在Serializable隔離級別,SQL1會加讀鎖,也就是說快照讀不復存在,MVCC并發控制降級為Lock-Based CC。

結論:在MySQL/InnoDB中,所謂的讀不加鎖,并不適用于所有的情況,而是隔離級別相關的。Serializable隔離級別,讀不加鎖就不再成立,所有的讀操作,都是當前讀。

鎖總結

  • 在MVCC(基于多版本的并發控制協議)并發控制中,讀操作可以分成兩類:快照讀 (snapshot read)與當前讀 (current read)。
  • 2PL (二階段鎖):Two-Phase Locking。說的是鎖操作分為兩個階段:加鎖階段與解鎖階段,并且保證加鎖階段與解鎖階段不相交。
  • 主鍵索引(聚簇索引) id主鍵+RC :只會在匹配的主鍵上加X鎖
  • id唯一索引+RC: id是unique索引,而主鍵是name列。此時,由于id是unique索引,因此delete語句會選擇走id列的索引進行where條件的過濾,在找到id=10的記錄后,首先會將unique索引上的id=10索引記錄加上X鎖(注意:這里是給索引加鎖,innodb的二級索引只會帶上主鍵索引數據,其他數據需要回行查詢),同時,會根據讀取到的name列,回主鍵索引(聚簇索引),然后將聚簇索引上的name = ‘d’ 對應的主鍵索引項加X鎖(注意:由于聚簇索引本身就是帶上行數據,所以要真正鎖這個聚簇索引才能真正鎖行!)。為什么聚簇索引上的記錄也要加鎖?試想一下,如果并發的一個SQL,是通過主鍵索引來更新:update t1 set id = 100 where name = ‘d’; 此時,如果delete語句沒有將主鍵索引上的記錄加鎖,那么并發的update就會感知不到delete語句的存在,違背了同一記錄上的更新/刪除需要串行執行的約束。刪除需要串行執行的約束**
  • id非唯一索引+RC :也是會同時鎖匹配的索引和指向的主鍵索引,跟上面那個原理一樣,只不過這個非唯一索引匹配多條記錄。(同樣道理,鎖住普通索引后,還得鎖聚簇索引才行
  • id無索引+RC:若id列上沒有索引,SQL會走聚簇索引的全掃描進行過濾,由于過濾是由MySQL Server層面進行的。因此每條記錄,無論是否滿足條件,都會被加上X鎖。但是,為了效率考量,MySQL做了優化,對于不滿足條件的記錄,會在判斷后放鎖,最終持有的,是滿足條件的記錄上的鎖,但是不滿足條件的記錄上的加鎖/放鎖動作不會省略。同時,優化也違背了2PL的約束。

  • id主鍵+RR: 同 id主鍵+RC,因為他們都是精確到記錄,所以就加在主鍵索引上就可以了
  • id唯一索引+RR 同id唯一索引+RC 兩個X鎖,id唯一索引滿足條件的記錄上一個,對應的聚簇索引上的記錄一個。
  • id非唯一索引+RR 幻讀的重要分界點
    • RC級別下,session1查詢的select .. where id=7 for update, (非唯一索引),查出有2條記錄, session2事務插入一個id=7的記錄(為什么可以插入?:因為session1的 where id=7 for update, 鎖住了普通索引索引7,和主鍵索引比如a 和 b , 那么此時插入id=7的數據時可以的,因為新插入的數據,首先新的主鍵,沒有被鎖, 而且索引7的兩條記錄是被鎖住,但是新加的記錄沒有被鎖。所以可以繼續插入,session2 這時commit事務,session1 再次查詢select .. where id=7 for update, 就會出現3條記錄,這就是RC級別的幻讀;
    • RR級別下,session1查詢的select .. where id=7 for update, (非唯一索引),查出有2條記錄,session2事務插入一個id=7的記錄,這里就是跟RC級別的最大差別,因為這時的插入時被阻塞的,不能插進去的!這就使得幻讀不能出現,因為根本不允許插入。為什么?因為gap鎖,gap鎖在索引id(非唯一索引)的前后都加了鎖,不允許這中間再出現可能的數據

死鎖原理與分析

--------------------- SESSION 1 -------------------------
mysql> select * from goods where id =1 for update;
--------------------- SESSION 2 -------------------------
mysql> update  goods set stock=120 where id=4;

# 兩個session 各自維護一個鎖

--------------------- SESSION 1 -------------------------
mysql>  select * from goods where id =4 for update;
# 阻塞中,因為鎖再session2  
--------------------- SESSION 2 -------------------------
update  goods set stock=120 where id=1;
# 死鎖出現

session1 提示死鎖:
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
sesseion2 執行成功

Deadlock found when trying to get lock; try restarting transaction 說明innodb會檢測死鎖

死鎖的發生與否,并不在于事務中有多少條SQL語句,死鎖的關鍵在于:兩個(或以上)的Session加鎖的順序不一致。而使用本文上面提到的,分析MySQL每條SQL語句的加鎖規則,分析出每條語句的加鎖順序,然后檢查多個并發SQL間是否存在以相反的順序加鎖的情況,就可以分析出各種潛在的死鎖情況,也可以分析出線上死鎖發生的原因。

參考

重點好文:MySQL 加鎖處理分析
Innodb鎖機制:Next-Key Lock 淺談
MySQL 四種事務隔離級的說明
Innodb中的事務隔離級別和鎖的關系
MySQL中的鎖(表鎖、行鎖)
mysql、innodb和加鎖分析

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內容