死鎖產生的原因和解鎖的方法

死鎖產生的原因和解鎖的方法

產生死鎖的四個必要條件:

(1) 互斥條件:一個資源每次只能被一個進程使用。

(2) 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。

(3) 不剝奪條件:進程已獲得的資源,在末使用完之前,不能強行剝奪。

(4) 循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關系。

二?鎖的分類

鎖的類別有兩種分法:

1.?從數據庫系統的角度來看:分為獨占鎖(即排它鎖),共享鎖和更新鎖

MS-SQL?Server?使用以下資源鎖模式。

鎖模式?描述

共享?(S) :讀鎖,用于不更改或不更新數據的操作(只讀操作),如?SELECT?語句。

更新?(U) :(介于共享和排它鎖之間),可以讓其他程序在不加鎖的條件下讀,但本程序可以隨時更改。

讀取表時使用更新鎖,而不使用共享鎖,并將鎖一直保留到語句或事務的結束。UPDLOCK 的優點是允許您讀取數據(不阻塞其它事務)并在以后更新數據,同時確保自從上次讀取數據后數據沒有被更改。當我們用UPDLOCK來讀取記錄時可以對取到的記錄加上更新鎖,從而加上鎖的記錄在其它的線程中是不能更改的只能等本線程的事務結束后才能更改,我如下示例:

BEGIN TRANSACTION --開始一個事務

SELECT Qty

FROM myTable WITH (UPDLOCK)

WHERE Idin(1,2,3)

UPDATE myTable SET Qty= Qty -A.Qty

FROM myTable? AS A

INNER JOIN? @_Table AS B ON A.ID=B.ID

COMMIT TRANSACTION--提交事務

這樣在更新時其它的線程或事務在這些語句執行完成前是不能更改ID是1,2,3的記錄的.其它的都可以修改和讀,1,2,3的只能讀,要是修改的話只能等這些語句完成后才能操作.從而保證的數據的修改正確.

排它?(X):寫鎖。 用于數據修改操作,例如?INSERT、UPDATE?或?DELETE。確保不會同時同一資源進行多重更新。

意向鎖?用于建立鎖的層次結構。意向鎖的類型為:意向共享?(IS)、意向排它?(IX)?以及與意向排它共享?(SIX)。

架構鎖?在執行依賴于表架構的操作時使用。架構鎖的類型為:架構修改?(Sch-M)?和架構穩定性?(Sch-S)。

大容量更新?(BU)?向表中大容量復制數據并指定了?TABLOCK?提示時使用。

共享鎖

共享?(S)?鎖允許并發事務讀取?(SELECT)?一個資源。資源上存在共享?(S)?鎖時,任何其它事務都不能修改數據。一旦已經讀取數據,便立即釋放資源上的共享?(S)?鎖,除非將事務隔離級別設置為可重復讀或更高級別,或者在事務生存周期內用鎖定提示保留共享?(S)?鎖。

更新鎖

更新?(U)?鎖可以防止通常形式的死鎖。一般更新模式由一個事務組成,此事務讀取記錄,獲取資源(頁或行)的共享?(S)?鎖,然后修改行,此操作要求鎖轉換為排它?(X)?鎖。如果兩個事務獲得了資源上的共享模式鎖,然后試圖同時更新數據,則一個事務嘗試將鎖轉換為排它?(X)?鎖。共享模式到排它鎖的轉換必須等待一段時間,因為一個事務的排它鎖與其它事務的共享模式鎖不兼容;發生鎖等待。第二個事務試圖獲取排它?(X)?鎖以進行更新。由于兩個事務都要轉換為排它?(X)?鎖,并且每個事務都等待另一個事務釋放共享模式鎖,因此發生死鎖。

若要避免這種潛在的死鎖問題,請使用更新?(U)?鎖。一次只有一個事務可以獲得資源的更新?(U)?鎖。如果事務修改資源,則更新?(U)?鎖轉換為排它?(X)?鎖。否則,鎖轉換為共享鎖。

排它鎖

排它?(X)?鎖可以防止并發事務對資源進行訪問。其它事務不能讀取或修改排它?(X)?鎖鎖定的數據。

意向鎖

意向鎖表示?SQL?Server?需要在層次結構中的某些底層資源上獲取共享?(S)?鎖或排它?(X)?鎖。例如,放置在表級的共享意向鎖表示事務打算在表中的頁或行上放置共享?(S)?鎖。在表級設置意向鎖可防止另一個事務隨后在包含那一頁的表上獲取排它?(X)?鎖。意向鎖可以提高性能,因為?SQL?Server?僅在表級檢查意向鎖來確定事務是否可以安全地獲取該表上的鎖。而無須檢查表中的每行或每頁上的鎖以確定事務是否可以鎖定整個表。

意向鎖包括意向共享?(IS)、意向排它?(IX)?以及與意向排它共享?(SIX)。

死鎖原理

根據操作系統中的定義:死鎖是指在一組進程中的各個進程均占有不會釋放的資源,但因互相申請被其他進程所站用不會釋放的資源而處于的一種永久等待狀態。

死鎖的四個必要條件:

互斥條件(Mutual exclusion):資源不能被共享,只能由一個進程使用。

請求與保持條件(Hold and wait):已經得到資源的進程可以再次申請新的資源。

非剝奪條件(No pre-emption):已經分配的資源不能從相應的進程中被強制地剝奪。

循環等待條件(Circular wait):系統中若干進程組成環路,該環路中每個進程都在等待相鄰進程正占用的資源。

對應到SQL Server中,當在兩個或多個任務中,如果每個任務鎖定了其他任務試圖鎖定的資源,此時會造成這些任務永久阻塞,從而出現死鎖;這些資源可能是:單行(RID,堆中的單行)、索引中的鍵(KEY,行鎖)、頁(PAG,8KB)、區結構(EXT,連續的8頁)、堆或B樹(HOBT) 、表(TAB,包括數據和索引)、文件(File,數據庫文件)、應用程序專用資源(APP)、元數據(METADATA)、分配單元(Allocation_Unit)、整個數據庫(DB)。一個死鎖示例如下圖所示:

說明:T1、T2表示兩個任務;R1和R2表示兩個資源;由資源指向任務的箭頭(如R1->T1,R2->T2)表示該資源被改任務所持有;由任務指向資源的箭頭(如T1->S2,T2->S1)表示該任務正在請求對應目標資源;

其滿足上面死鎖的四個必要條件:

(1).互斥:資源S1和S2不能被共享,同一時間只能由一個任務使用;

(2).請求與保持條件:T1持有S1的同時,請求S2;T2持有S2的同時請求S1;

(3).非剝奪條件:T1無法從T2上剝奪S2,T2也無法從T1上剝奪S1;

(4).循環等待條件:上圖中的箭頭構成環路,存在循環等待。

2.死鎖排查

(1). 使用SQL Server的系統存儲過程sp_who和sp_lock,可以查看當前數據庫中的鎖情況;進而根據objectID(@objID)(SQL Server 2005)/ object_name(@objID)(Sql Server 2000)可以查看哪個資源被鎖,用dbcc ld(@blk),可以查看最后一條發生給SQL Server的Sql語句;

CREATE?Table?#Who(spid?int,

ecid?int,

status?nvarchar(50),

loginname?nvarchar(50),

hostname?nvarchar(50),

blk?int,

dbname?nvarchar(50),

cmd?nvarchar(50),

request_ID?int);

CREATE?Table?#Lock(spid?int,

dpid?int,

objid?int,

indld?int,

[Type]?nvarchar(20),

Resource?nvarchar(50),

Mode?nvarchar(10),

Status?nvarchar(10)

);

INSERT?INTO?#Who

EXEC?sp_who?active??--看哪個引起的阻塞,blk

INSERT?INTO?#Lock

EXEC?sp_lock??--看鎖住了那個資源id,objid

DECLARE?@DBName?nvarchar(20);

SET?@DBName='NameOfDataBase'

SELECT?#Who.*?FROM?#Who?WHERE?dbname=@DBName

SELECT?#Lock.*?FROM?#Lock

JOIN?#Who

ON?#Who.spid=#Lock.spid

AND?dbname=@DBName;

--最后發送到SQL?Server的語句

DECLARE?crsr?Cursor?FOR

SELECT?blk?FROM?#Who?WHERE?dbname=@DBName?AND?blk<>0;

DECLARE?@blk?int;

open?crsr;

FETCH?NEXT?FROM?crsr?INTO?@blk;

WHILE?(@@FETCH_STATUS=0)

BEGIN;

dbcc?inputbuffer(@blk);

FETCH?NEXT?FROM?crsr?INTO?@blk;

END;

close?crsr;

DEALLOCATE?crsr;

--鎖定的資源

SELECT?#Who.spid,hostname,objid,[type],mode,object_name(objid)?as?objName?FROM?#Lock

JOIN?#Who

ON?#Who.spid=#Lock.spid

AND?dbname=@DBName

WHERE?objid<>0;

DROP?Table?#Who;

DROP?Table?#Lock;

(2). 使用 SQL Server Profiler 分析死鎖: 將 Deadlock graph 事件類添加到跟蹤。此事件類使用死鎖涉及到的進程和對象的 XML 數據填充跟蹤中的 TextData 數據列。SQL Server 事件探查器?可以將 XML 文檔提取到死鎖 XML (.xdl) 文件中,以后可在 SQL Server Management Studio 中查看該文件。

3.避免死鎖

上面1中列出了死鎖的四個必要條件,我們只要想辦法破其中的任意一個或多個條件,就可以避免死鎖發生,一般有以下幾種方法(FROM Sql Server 2005聯機叢書):

(1).按同一順序訪問對象。(注:避免出現循環)

(2).避免事務中的用戶交互。(注:減少持有資源的時間,較少鎖競爭)

(3).保持事務簡短并處于一個批處理中。(注:同(2),減少持有資源的時間)

(4).使用較低的隔離級別。(注:使用較低的隔離級別(例如已提交讀)比使用較高的隔離級別(例如可序列化)持有共享鎖的時間更短,減少鎖競爭)

(5).使用基于行版本控制的隔離級別:2005中支持快照事務隔離和指定READ_COMMITTED隔離級別的事務使用行版本控制,可以將讀與寫操作之間發生的死鎖幾率降至最低:

SET ALLOW_SNAPSHOT_ISOLATION ON --事務可以指定 SNAPSHOT 事務隔離級別;

SET READ_COMMITTED_SNAPSHOT ON? --指定 READ_COMMITTED 隔離級別的事務將使用行版本控制而不是鎖定。默認情況下(沒有開啟此選項,沒有加with nolock提示),SELECT語句會對請求的資源加S鎖(共享鎖);而開啟了此選項后,SELECT不會對請求的資源加S鎖。

注意:設置 READ_COMMITTED_SNAPSHOT 選項時,數據庫中只允許存在執行 ALTER DATABASE 命令的連接。在 ALTER DATABASE 完成之前,數據庫中決不能有其他打開的連接。數據庫不必一定要處于單用戶模式中。

(6).使用綁定連接。(注:綁定會話有利于在同一臺服務器上的多個會話之間協調操作。綁定會話允許一個或多個會話共享相同的事務和鎖(但每個回話保留其自己的事務隔離級別),并可以使用同一數據,而不會有鎖沖突。可以從同一個應用程序內的多個會話中創建綁定會話,也可以從包含不同會話的多個應用程序中創建綁定會話。在一個會話中開啟事務(begin tran)后,調用exec sp_getbindtoken @Token out;來取得Token,然后傳入另一個會話并執行EXEC sp_bindsession @Token來進行綁定(最后的示例中演示了綁定連接)。

4.死鎖處理方法:

(1). 根據2中提供的sql,查看那個spid處于wait狀態,然后用kill spid來干掉(即破壞死鎖的第四個必要條件:循環等待);當然這只是一種臨時解決方案,我們總不能在遇到死鎖就在用戶的生產環境上排查死鎖、Kill sp,我們應該考慮如何去避免死鎖。

(2). 使用SET LOCK_TIMEOUT timeout_period(單位為毫秒)來設定鎖請求超時。默認情況下,數據庫沒有超時期限(timeout_period值為-1,可以用SELECT @@LOCK_TIMEOUT來查看該值,即無限期等待)。當請求鎖超過timeout_period時,將返回錯誤。timeout_period值為0時表示根本不等待,一遇到鎖就返回消息。設置鎖請求超時,破環了死鎖的第二個必要條件(請求與保持條件)。

服務器:?消息?1222,級別?16,狀態?50,行?1

已超過了鎖請求超時時段。

(3). SQL Server內部有一個鎖監視器線程執行死鎖檢查,鎖監視器對特定線程啟動死鎖搜索時,會標識線程正在等待的資源;然后查找特定資源的所有者,并遞歸地繼續執行對那些線程的死鎖搜索,直到找到一個構成死鎖條件的循環。檢測到死鎖后,數據庫引擎?選擇運行回滾開銷最小的事務的會話作為死鎖犧牲品,返回1205 錯誤,回滾死鎖犧牲品的事務并釋放該事務持有的所有鎖,使其他線程的事務可以請求資源并繼續運行。

5.兩個死鎖示例及解決方法

5.1 SQL死鎖

(1).測試用的基礎數據:

CREATE?TABLE?Lock1(C1?int?default(0));

CREATE?TABLE?Lock2(C1?int?default(0));

INSERT?INTO?Lock1?VALUES(1);

INSERT?INTO?Lock2?VALUES(1);

(2).開兩個查詢窗口,分別執行下面兩段sql

--Query?1

Begin?Tran

Update?Lock1?Set?C1=C1+1;

WaitFor?Delay?'00:01:00';

SELECT?*?FROM?Lock2

Rollback?Tran;

--Query?2

Begin?Tran

Update?Lock2?Set?C1=C1+1;

WaitFor?Delay?'00:01:00';

SELECT?*?FROM?Lock1

Rollback?Tran;

上面的SQL中有一句WaitFor Delay '00:01:00',用于等待1分鐘,以方便查看鎖的情況。

(3).查看鎖情況

在執行上面的WaitFor語句期間,執行第二節中提供的語句來查看鎖信息:

Query1中,持有Lock1中第一行(表中只有一行數據)的行排他鎖(RID:X),并持有該行所在頁的意向更新鎖(PAG:IX)、該表的意向更新鎖(TAB:IX);Query2中,持有Lock2中第一行(表中只有一行數據)的行排他鎖(RID:X),并持有該行所在頁的意向更新鎖(PAG:IX)、該表的意向更新鎖(TAB:IX);

執行完Waitfor,Query1查詢Lock2,請求在資源上加S鎖,但該行已經被Query2加上了X鎖;Query2查詢Lock1,請求在資源上加S鎖,但該行已經被Query1加上了X鎖;于是兩個查詢持有資源并互不相讓,構成死鎖。

(4).解決辦法

a).SQL Server自動選擇一條SQL作死鎖犧牲品:運行完上面的兩個查詢后,我們會發現有一條SQL能正常執行完畢,而另一個SQL則報如下錯誤:

服務器:?消息?1205,級別?13,狀態?50,行?1

事務(進程?ID??xx)與另一個進程已被死鎖在??lock?資源上,且該事務已被選作死鎖犧牲品。請重新運行該事務。

這就是上面第四節中介紹的鎖監視器干活了。

b).按同一順序訪問對象:顛倒任意一條SQL中的Update與SELECT語句的順序。例如修改第二條SQL成如下:

--Query2

Begin?Tran

SELECT?*?FROM?Lock1--在Lock1上申請S鎖

WaitFor?Delay?'00:01:00';

Update?Lock2?Set?C1=C1+1;--Lock2:RID:X

Rollback?Tran;

當然這樣修改也是有代價的,這會導致第一條SQL執行完畢之前,第二條SQL一直處于阻塞狀態。單獨執行Query1或Query2需要約1分鐘,但如果開始執行Query1時,馬上同時執行Query2,則Query2需要2分鐘才能執行完;這種按順序請求資源從一定程度上降低了并發性。

c).SELECT語句加With(NoLock)提示:默認情況下SELECT語句會對查詢到的資源加S鎖(共享鎖),S鎖與X鎖(排他鎖)不兼容;但加上With(NoLock)后,SELECT不對查詢到的資源加鎖(或者加Sch-S鎖,Sch-S鎖可以與任何鎖兼容);從而可以是這兩條SQL可以并發地訪問同一資源。當然,此方法適合解決讀與寫并發死鎖的情況,但With(NoLock)可能會導致臟讀。

SELECT?*?FROM?Lock2?WITH(NOLock)

SELECT?*?FROM?Lock1?WITH(NOLock)

d).使用較低的隔離級別。SQL Server 2000支持四種事務處理隔離級別(TIL),分別為:READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE;SQL Server 2005中增加了SNAPSHOT TIL。默認情況下,SQL Server使用READ COMMITTED TIL,我們可以在上面的兩條SQL前都加上一句SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED,來降低TIL以避免死鎖;事實上,運行在READ UNCOMMITTED TIL的事務,其中的SELECT語句不對結果資源加鎖或加Sch-S鎖,而不會加S鎖;但還有一點需要注意的是:READ UNCOMMITTED TIL允許臟讀,雖然加上了降低TIL的語句后,上面兩條SQL在執行過程中不會報錯,但執行結果是一個返回1,一個返回2,即讀到了臟數據,也許這并不是我們所期望的。

e). 在SQL前加SET LOCK_TIMEOUT timeout_period,當請求鎖超過設定的timeout_period時間后,就會終止當前SQL的執行,犧牲自己,成全別人。

f).使用基于行版本控制的隔離級別(SQL Server 2005支持):開啟下面的選項后,SELECT不會對請求的資源加S鎖,不加鎖或者加Sch-S鎖,從而將讀與寫操作之間發生的死鎖幾率降至最低;而且不會發生臟讀。

SET?ALLOW_SNAPSHOT_ISOLATION?ON

SET?READ_COMMITTED_SNAPSHOT?ON

g). 使用綁定連接(使用方法見下一個示例。)

5.2程序死鎖(SQL阻塞)

看一個例子:一個典型的數據庫操作事務死鎖分析,按照我自己的理解,我覺得這應該算是C#程序中出現死鎖,而不是數據庫中的死鎖;下面的代碼模擬了該文中對數據庫的操作過程:

//略去的無關的code

SqlConnection?conn?=?new?SqlConnection(connectionString);

conn.Open();

SqlTransaction?tran?=?conn.BeginTransaction();

string?sql1?=?"Update?Lock1?SET?C1=C1+1";

string?sql2?=?"SELECT?*?FROM?Lock1";

ExecuteNonQuery(tran,?sql1);?//使用事務:事務中Lock了Table

ExecuteNonQuery(null,?sql2);?//新開一個connection來讀取Table

public?static?void?ExecuteNonQuery(SqlTransaction?tran,?string?sql)

{

SqlCommand?cmd?=?new?SqlCommand(sql);

if?(tran?!=?null)

{

cmd.Connection?=?tran.Connection;

cmd.Transaction?=?tran;

cmd.ExecuteNonQuery();

}

else

{

using?(SqlConnection?conn?=?new?SqlConnection(connectionString))

{

conn.Open();

cmd.Connection?=?conn;

cmd.ExecuteNonQuery();

}

}

}

執行到ExecuteNonQuery(null, sql2)時拋出SQL執行超時的異常,下圖從數據庫的角度來看該問題:

代碼從上往下執行,會話1持有了表Lock1的X鎖,且事務沒有結束,回話1就一直持有X鎖不釋放;而會話2執行select操作,請求在表Lock1上加S鎖,但S鎖與X鎖是不兼容的,所以回話2的被阻塞等待,不在等待中,就在等待中獲得資源,就在等待中超時。。。從中我們可以看到,里面并沒有出現死鎖,而只是SELECT操作被阻塞了。也正因為不是數據庫死鎖,所以SQL Server的鎖監視器無法檢測到死鎖。

我們再從C#程序的角度來看該問題:

C#程序持有了表Lock1上的X鎖,同時開了另一個SqlConnection還想在該表上請求一把S鎖,圖中已經構成了環路;太貪心了,結果自己把自己給鎖死了。。。

雖然這不是一個數據庫死鎖,但卻是因為數據庫資源而導致的死鎖,上例中提到的解決死鎖的方法在這里也基本適用,主要是避免讀操作被阻塞,解決方法如下:

a).SELECT放在Update語句前:SELECT不在事務中,且執行完畢會釋放S鎖;

b).SELECT也放加入到事務中:ExecuteNonQuery(tran, sql2);

c).SELECTWith(NOLock)提示:可能產生臟讀;

d).降低事務隔離級別:SELECT語句前加SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;同上,可能產生臟讀;

e).使用基于行版本控制的隔離級別(同上例)。

g).使用綁定連接:取得事務所在會話的token,然后傳入新開的connection中;執行EXEC sp_bindsession @Token后綁定了連接,最后執行exec sp_bindsession null;來取消綁定;最后需要注意的四點是:

(1). 使用了綁定連接的多個connection共享同一個事務和相同的鎖,但各自保留自己的事務隔離級別;

(2). 如果在sql3字符串的“exec sp_bindsession null”換成“commit tran”或者“rollback tran”,則會提交整個事務,最后一行C#代碼tran.Commit()就可以不用執行了(執行會報錯,因為事務已經結束了-,-)。

(3). 開啟事務(begin tran)后,才可以調用exec sp_getbindtoken @Token out來取得Token;如果不想再新開的connection中結束掉原有的事務,則在這個connection close之前,必須執行“exec sp_bindsession null”來取消綁定連接,或者在新開的connectoin close之前先結束掉事務(commit/tran)。

(4). (Sql server 2005 聯機叢書)后續版本的 Microsoft SQL Server 將刪除該功能。請避免在新的開發工作中使用該功能,并著手修改當前還在使用該功能的應用程序。 請改用多個活動結果集 (MARS) 或分布式事務。

tran?=?connection.BeginTransaction();

string?sql1?=?"Update?Lock1?SET?C1=C1+1";

ExecuteNonQuery(tran,?sql1);?//使用事務:事務中Lock了測試表Lock1

string?sql2?=?@"DECLARE?@Token?varchar(255);

exec?sp_getbindtoken?@Token?out;

SELECT?@Token;";

string?token?=?ExecuteScalar(tran,?sql2).ToString();

string?sql3?=?"EXEC?sp_bindsession?@Token;Update?Lock1?SET?C1=C1+1;exec?sp_bindsession?null;";

SqlParameter?parameter?=?new?SqlParameter("@Token",?SqlDbType.VarChar);

parameter.Value?=?token;

ExecuteNonQuery(null,?sql3,?parameter);?//新開一個connection來操作測試表Lock1

tran.Commit();

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

推薦閱讀更多精彩內容