SQL優(yōu)化(四) PostgreSQL存儲(chǔ)過(guò)程

原創(chuàng)文章,首發(fā)自作者個(gè)人博客Jason's Blog
  轉(zhuǎn)載請(qǐng)務(wù)必在文章開(kāi)頭處注明出自Jason's Blog,并給出原文鏈接

存儲(chǔ)過(guò)程簡(jiǎn)介

什么是存儲(chǔ)過(guò)程

百度百科是這么描述存儲(chǔ)過(guò)程的:存儲(chǔ)過(guò)程(Stored Procedure)是在大型數(shù)據(jù)庫(kù)系統(tǒng)中,一組為了完成特定功能的SQL語(yǔ)句集,存儲(chǔ)在數(shù)據(jù)庫(kù)中,首次編譯后再次調(diào)用不需要再次編譯,用戶(hù)通過(guò)指定存儲(chǔ)過(guò)程的名字并給出參數(shù)(如果有)來(lái)執(zhí)行它。它是數(shù)據(jù)庫(kù)中的一個(gè)重要對(duì)象,任何一個(gè)設(shè)計(jì)良好的數(shù)據(jù)庫(kù)應(yīng)用程序都應(yīng)該用到存儲(chǔ)過(guò)程。
  
  維基百科是這樣定義的:A stored procedure (also termed proc, storp, sproc, StoPro, StoredProc, StoreProc, sp, or SP) is a subroutine available to applications that access a relational database management system (RDMS). Such procedures are stored in the database data dictionary。

PostgreSQL對(duì)存儲(chǔ)過(guò)程的描述是:存儲(chǔ)過(guò)程和用戶(hù)自定義函數(shù)(UDF)是SQL和過(guò)程語(yǔ)句的集合,它存儲(chǔ)于數(shù)據(jù)庫(kù)服務(wù)器并能被SQL接口調(diào)用。

總結(jié)下來(lái)存儲(chǔ)過(guò)程有如下特性:

  • 存儲(chǔ)于數(shù)據(jù)庫(kù)服務(wù)器
  • 一次編譯后可多次調(diào)用
  • 設(shè)計(jì)良好的數(shù)據(jù)庫(kù)應(yīng)用程序很可能會(huì)用到它
  • 由SQL和過(guò)程語(yǔ)句來(lái)定義
  • 應(yīng)用程序通過(guò)SQL接口來(lái)調(diào)用

使用存儲(chǔ)過(guò)程的優(yōu)勢(shì)及劣勢(shì)

首先看看使用存儲(chǔ)過(guò)程的優(yōu)勢(shì)

  • 減少應(yīng)用與數(shù)據(jù)庫(kù)服務(wù)器的通信開(kāi)銷(xiāo),從而提升整體性能。筆者在項(xiàng)目中使用的存儲(chǔ)過(guò)程,少則幾十行,多則幾百行甚至上千行(假設(shè)一行10個(gè)字節(jié),一千行即相當(dāng)于10KB),如果不使用存儲(chǔ)過(guò)程而直接通過(guò)應(yīng)用程序?qū)⑾鄳?yīng)SQL請(qǐng)求發(fā)送到數(shù)據(jù)庫(kù)服務(wù)器,會(huì)增大網(wǎng)絡(luò)通信開(kāi)銷(xiāo)。相反,使用存儲(chǔ)過(guò)程能降低該開(kāi)銷(xiāo),從而提升整體性能。尤其在一些BI系統(tǒng)中,一個(gè)頁(yè)面往往要使用多個(gè)存儲(chǔ)過(guò)程,此時(shí)存儲(chǔ)過(guò)程降低網(wǎng)絡(luò)通信開(kāi)銷(xiāo)的優(yōu)勢(shì)非常明顯
  • 一次編譯多次調(diào)用,提高性能。存儲(chǔ)過(guò)程存于數(shù)據(jù)庫(kù)服務(wù)器中,第一次被調(diào)用后即被編譯,之后再調(diào)用時(shí)無(wú)需再次編譯,直接執(zhí)行,提高了性能
  • 同一套業(yè)務(wù)邏輯可被不同應(yīng)用程序共用,減少了應(yīng)用程序的開(kāi)發(fā)復(fù)雜度,同時(shí)也保證了不同應(yīng)用程序使用的一致性
  • 保護(hù)數(shù)據(jù)庫(kù)元信息。如果應(yīng)用程序直接使用SQL語(yǔ)句查詢(xún)數(shù)據(jù)庫(kù),會(huì)將數(shù)據(jù)庫(kù)表結(jié)構(gòu)暴露給應(yīng)用程序,而使用存儲(chǔ)過(guò)程是應(yīng)用程序并不知道數(shù)據(jù)庫(kù)表結(jié)構(gòu)
  • 更細(xì)粒度的數(shù)據(jù)庫(kù)權(quán)限管理。直接從表讀取數(shù)據(jù)時(shí),對(duì)應(yīng)用程序只能實(shí)現(xiàn)表級(jí)別的權(quán)限管理,而使用存儲(chǔ)過(guò)程是,可在存儲(chǔ)過(guò)程中將應(yīng)用程序無(wú)權(quán)訪問(wèn)的數(shù)據(jù)屏蔽
  • 將業(yè)務(wù)實(shí)現(xiàn)與應(yīng)用程序解耦。當(dāng)業(yè)務(wù)需求更新時(shí),只需更改存儲(chǔ)過(guò)程的定義,而不需要更改應(yīng)用程序
  • 可以通過(guò)其它語(yǔ)言并可及其它系統(tǒng)交互。比如可以使用PL/Java與Kafka交互,將存儲(chǔ)過(guò)程的參數(shù)Push到Kafka或者將從Kafka獲取的數(shù)據(jù)作為存儲(chǔ)過(guò)程的結(jié)果返回給調(diào)用方

當(dāng)然,使用存儲(chǔ)過(guò)程也有它的劣勢(shì)

  • 不便于調(diào)試。尤其在做性能調(diào)優(yōu)時(shí),以PostgreSQL為例,可使用EXPLAIN ANALYZE檢查SQL查詢(xún)計(jì)劃,從而方便的進(jìn)行性能調(diào)優(yōu)。而使用存儲(chǔ)過(guò)程時(shí),EXPLAIN ANALYZE無(wú)法顯示其內(nèi)部查詢(xún)計(jì)劃
  • 不便于移植到其它數(shù)據(jù)庫(kù)。直接使用SQL時(shí),SQL存于應(yīng)用程序中,對(duì)大部分標(biāo)準(zhǔn)SQL而言,換用其它數(shù)據(jù)庫(kù)并不影響應(yīng)用程序的使用。而使用存儲(chǔ)過(guò)程時(shí),由于不同數(shù)據(jù)庫(kù)的存儲(chǔ)過(guò)程定義方式不同,支持的語(yǔ)言及語(yǔ)法不同,移植成本較高

存儲(chǔ)過(guò)程在PostgreSQL中的使用

PostgreSQL支持的過(guò)程語(yǔ)言

PostgreSQL官方支持PL/pgSQL,PL/Tcl,PL/Perl和PL/Python這幾種過(guò)程語(yǔ)言。同時(shí)還支持一些第三方提供的過(guò)程語(yǔ)言,如PL/Java,PL/PHP,PL/Py,PL/R,PL/Ruby,PL/Scheme,PL/sh。

基于SQL的存儲(chǔ)過(guò)程定義

CREATE OR REPLACE FUNCTION add(a INTEGER, b NUMERIC)
RETURNS NUMERIC
AS $$
    SELECT a+b;
$$ LANGUAGE SQL;

調(diào)用方法

SELECT add(1,2);
 add
-----
   3
(1 row)

SELECT * FROM add(1,2);
 add
-----
   3
(1 row)

上面這種方式參數(shù)列表只包含函數(shù)輸入?yún)?shù),不包含輸出參數(shù)。下面這個(gè)例子將同時(shí)包含輸入?yún)?shù)和輸出參數(shù)

CREATE OR REPLACE FUNCTION plus_and_minus
(IN a INTEGER, IN b NUMERIC, OUT c NUMERIC, OUT d NUMERIC)
AS $$
    SELECT a+b, a-b;
$$ LANGUAGE SQL;

調(diào)用方式

SELECT plus_and_minus(3,2);
 add_and_minute
----------------
 (5,1)
(1 row)

SELECT * FROM plus_and_minus(3,2);
 c | d
---+---
 5 | 1
(1 row)

該例中,IN代表輸入?yún)?shù),OUT代表輸出參數(shù)。這個(gè)帶輸出參數(shù)的函數(shù)和之前的add函數(shù)并無(wú)本質(zhì)區(qū)別。事實(shí)上,輸出參數(shù)的最大價(jià)值在于它為函數(shù)提供了返回多個(gè)字段的途徑。

在函數(shù)定義中,可以寫(xiě)多個(gè)SQL語(yǔ)句,不一定是SELECT語(yǔ)句,可以是其它任意合法的SQL。但最后一條SQL必須是SELECT語(yǔ)句,并且該SQL的結(jié)果將作為該函數(shù)的輸出結(jié)果。

CREATE OR REPLACE FUNCTION plus_and_minus
(IN a INTEGER, IN b NUMERIC, OUT c NUMERIC, OUT d NUMERIC)
AS $$
    SELECT a+b, a-b;
    INSERT INTO test VALUES('test1');
    SELECT a-b, a+b;
$$ LANGUAGE SQL;

其效果如下

SELECT * FROM plus_and_minus(5,3);
 c | d
---+---
 2 | 8
(1 row)

SELECT * FROM test;
   a
-------
 test1
(1 row)

基于PL/PgSQL的存儲(chǔ)過(guò)程定義

PL/pgSQL是一個(gè)塊結(jié)構(gòu)語(yǔ)言。函數(shù)定義的所有文本都必須是一個(gè)塊。一個(gè)塊用下面的方法定義:

[ <<label>> ]
[DECLARE
    declarations]
BEGIN
    statements
END [ label ];
  • 中括號(hào)部分為可選部分
  • 塊中的每一個(gè)declaration和每一條statement都由一個(gè)分號(hào)終止
  • 塊支持嵌套,嵌套時(shí)子塊的END后面必須跟一個(gè)分號(hào),最外層的塊END后可不跟分號(hào)
  • BEGIN后面不必也不能跟分號(hào)
  • END后跟的label名必須和塊開(kāi)始時(shí)的標(biāo)簽名一致
  • 所有關(guān)鍵字都不區(qū)分大小寫(xiě)。標(biāo)識(shí)符被隱含地轉(zhuǎn)換成小寫(xiě)字符,除非被雙引號(hào)包圍
  • 聲明的變量在當(dāng)前塊及其子塊中有效,子塊開(kāi)始前可聲明并覆蓋(只在子塊內(nèi)覆蓋)外部塊的同名變量
  • 變量被子塊中聲明的變量覆蓋時(shí),子塊可以通過(guò)外部塊的label訪問(wèn)外部塊的變量

聲明一個(gè)變量的語(yǔ)法如下:

name [ CONSTANT ] type [ NOT NULL ] [ { DEFAULT | := } expression ];

使用PL/PgSQL語(yǔ)言的函數(shù)定義如下:

CREATE FUNCTION somefunc() RETURNS integer AS $$
DECLARE
    quantity integer := 30;
BEGIN
    -- Prints 30
    RAISE NOTICE 'Quantity here is %', quantity;
    quantity := 50;

    -- Create a subblock
    DECLARE
        quantity integer := 80;
    BEGIN
        -- Prints 80
        RAISE NOTICE 'Quantity here is %', quantity;
        -- Prints 50
        RAISE NOTICE 'Outer quantity here is %', outerblock.quantity;
    END;

    -- Prints 50
    RAISE NOTICE 'Quantity here is %', quantity;
    RETURN quantity;
END;
$$ LANGUAGE plpgsql;

聲明函數(shù)參數(shù)

如果只指定輸入?yún)?shù)類(lèi)型,不指定參數(shù)名,則函數(shù)體里一般用$1,$n這樣的標(biāo)識(shí)符來(lái)使用參數(shù)。

CREATE OR REPLACE FUNCTION discount(NUMERIC)
RETURNS NUMERIC
AS $$
BEGIN
    RETURN $1 * 0.8;
END;
$$ LANGUAGE PLPGSQL;

但該方法可讀性不好,此時(shí)可以為$n參數(shù)聲明別名,然后可以在函數(shù)體內(nèi)通過(guò)別名指向該參數(shù)值。

CREATE OR REPLACE FUNCTION discount(NUMERIC)
RETURNS NUMERIC
AS $$
DECLARE
    total ALIAS FOR $1;
BEGIN
    RETURN total * 0.8;
END;
$$ LANGUAGE PLPGSQL;

筆者認(rèn)為上述方法仍然不夠直觀,也不夠完美。幸好PostgreSQL提供另外一種更為直接的方法來(lái)聲明函數(shù)參數(shù),即在聲明參數(shù)類(lèi)型時(shí)同時(shí)聲明相應(yīng)的參數(shù)名。

CREATE OR REPLACE FUNCTION discount(total NUMERIC)
RETURNS NUMERIC
AS $$
BEGIN
    RETURN total * 0.8;
END;
$$ LANGUAGE PLPGSQL;

返回多行或多列

使用自定義復(fù)合類(lèi)型返回一行多列

PostgreSQL除了支持自帶的類(lèi)型外,還支持用戶(hù)創(chuàng)建自定義類(lèi)型。在這里可以自定義一個(gè)復(fù)合類(lèi)型,并在函數(shù)中返回一個(gè)該復(fù)合類(lèi)型的值,從而實(shí)現(xiàn)返回一行多列。

CREATE TYPE compfoo AS (col1 INTEGER, col2 TEXT);


CREATE OR REPLACE FUNCTION getCompFoo
(in_col1 INTEGER, in_col2 TEXT)
RETURNS compfoo
AS $$
DECLARE result compfoo;
BEGIN
    result.col1 := in_col1 * 2;
    result.col2 := in_col2 || '_result';
    RETURN result;
END;
$$ LANGUAGE PLPGSQL;


SELECT * FROM getCompFoo(1,'1');
 col1 |   col2
------+----------
    2 | 1_result
(1 row)

使用輸出參數(shù)名返回一行多列

在聲明函數(shù)時(shí),除指定輸入?yún)?shù)名及類(lèi)型外,還可同時(shí)聲明輸出參數(shù)類(lèi)型及參數(shù)名。此時(shí)函數(shù)可以輸出一行多列。

CREATE OR REPLACE FUNCTION get2Col
(IN in_col1 INTEGER,IN in_col2 TEXT,
OUT out_col1 INTEGER, OUT out_col2 TEXT)
AS $$
BEGIN
    out_col1 := in_col1 * 2;
    out_col2 := in_col2 || '_result';
END;
$$ LANGUAGE PLPGSQL;


SELECT * FROM get2Col(1,'1');
 out_col1 | out_col2 
----------+----------
        2 | 1_result
(1 row)

使用SETOF返回多行記錄

實(shí)際項(xiàng)目中,存儲(chǔ)過(guò)程經(jīng)常需要返回多行記錄,可以通過(guò)SETOF實(shí)現(xiàn)。

CREATE TYPE compfoo AS (col1 INTEGER, col2 TEXT);

CREATE OR REPLACE FUNCTION getSet(rows INTEGER)
RETURNS SETOF compfoo
AS $$
BEGIN
    RETURN QUERY SELECT i * 2, i || '_text' 
    FROM generate_series(1, rows, 1) as t(i);
END;
$$ LANGUAGE PLPGSQL;


SELECT col1, col2 FROM getSet(2);
 col1 |  col2
------+--------
    2 | 1_text
    4 | 2_text
(2 rows)

本例返回的每一行記錄是復(fù)合類(lèi)型,該方法也可返回基本類(lèi)型的結(jié)果集,即多行一列。

使用RETURN TABLE返回多行多列

CREATE OR REPLACE FUNCTION getTable(rows INTEGER)
RETURNS TABLE(col1 INTEGER, col2 TEXT)
AS $$
BEGIN
    RETURN QUERY SELECT i * 2, i || '_text'
    FROM generate_series(1, rows, 1) as t(i);
END;
$$ LANGUAGE PLPGSQL;


SELECT col1, col2 FROM getTable(2);
 col1 |  col2
------+--------
    2 | 1_text
    4 | 2_text
(2 rows)

此時(shí)從函數(shù)中讀取字段就和從表或視圖中取字段一樣,可以看此種類(lèi)型的函數(shù)看成是帶參數(shù)的表或者視圖。

使用EXECUTE語(yǔ)句執(zhí)行動(dòng)態(tài)命令

有時(shí)在PL/pgSQL函數(shù)中需要生成動(dòng)態(tài)命令,這個(gè)命令將包括他們每次執(zhí)行時(shí)使用不同的表或者字符。EXECUTE語(yǔ)句用法如下:

EXECUTE command-string [ INTO [STRICT] target] [USING expression [, ...]];

此時(shí)PL/plSQL將不再緩存該命令的執(zhí)行計(jì)劃。相反,在該語(yǔ)句每次被執(zhí)行的時(shí)候,命令都會(huì)編譯一次。這也讓該語(yǔ)句獲得了對(duì)各種不同的字段甚至表進(jìn)行操作的能力。
  command-string包含了要執(zhí)行的命令,它可以使用參數(shù)值,在命令中通過(guò)引用如$1,$2等來(lái)引用參數(shù)值。這些符號(hào)的值是指USING字句的值。這種方法對(duì)于在命令字符串中使用參數(shù)是最好的:它能避免運(yùn)行時(shí)數(shù)值從文本來(lái)回轉(zhuǎn)換,并且不容易產(chǎn)生SQL注入,而且它不需要引用或者轉(zhuǎn)義。

CREATE TABLE testExecute
AS
SELECT
    i || '' AS a,
    i AS b
FROM
    generate_series(1, 10, 1) AS t(i);

CREATE OR REPLACE FUNCTION execute(filter TEXT)
RETURNS TABLE (a TEXT, b INTEGER)
AS $$
BEGIN
    RETURN QUERY EXECUTE
        'SELECT * FROM testExecute where a = $1'
    USING filter;
END;
$$ LANGUAGE PLPGSQL;


SELECT * FROM execute('3');
 a | b
---+---
 3 | 3
(1 row)

SELECT * FROM execute('3'' or ''c''=''c');
 a | b
---+---
(0 rows)

當(dāng)然,也可以使用字符串拼接的方式在command-string中使用參數(shù),但會(huì)有SQL注入的風(fēng)險(xiǎn)。

CREATE TABLE testExecute
AS
SELECT
    i || '' AS a,
    i AS b
FROM
    generate_series(1, 10, 1) AS t(i);

CREATE OR REPLACE FUNCTION execute(filter TEXT)
RETURNS TABLE (a TEXT, b INTEGER)
AS $$
BEGIN
    RETURN QUERY EXECUTE
        'SELECT * FROM testExecute where b = '''
        || filter || '''';
END;
$$ LANGUAGE PLPGSQL;


SELECT * FROM execute(3);
 a | b
---+---
 3 | 3
(1 row)

 SELECT * FROM execute('3'' or ''c''=''c');
 a  | b
----+----
 1  |  1
 2  |  2
 3  |  3
 4  |  4
 5  |  5
 6  |  6
 7  |  7
 8  |  8
 9  |  9
 10 | 10
(10 rows)

從該例中可以看出使用字符串拼接的方式在command-string中使用參數(shù)會(huì)引入SQL注入攻擊的風(fēng)險(xiǎn),而使用USING的方式則能有效避免這一風(fēng)險(xiǎn)。

PostgreSQL中的UDF與存儲(chǔ)過(guò)程

本文中并未區(qū)分PostgreSQL中的UDF和存儲(chǔ)過(guò)程。實(shí)際上PostgreSQL創(chuàng)建存儲(chǔ)與創(chuàng)建UDF的方式一樣,并沒(méi)有專(zhuān)用于創(chuàng)建存儲(chǔ)過(guò)程的語(yǔ)法,如CREATE PRECEDURE。在PostgreSQL官方文檔中也暫未找到這二者的區(qū)別。倒是從一些資料中找對(duì)了它們的對(duì)比,如下表如示,僅供參考。


UDF VS. Stored Precedure
UDF VS. Stored Precedure

多態(tài)SQL函數(shù)

SQL函數(shù)可以聲明為接受多態(tài)類(lèi)型(anyelement和anyarray)的參數(shù)或返回多態(tài)類(lèi)型的返回值。

  • 函數(shù)參數(shù)和返回值均為多態(tài)類(lèi)型。其調(diào)用方式和調(diào)用其它類(lèi)型的SQL函數(shù)完全相同,只是在傳遞字符串類(lèi)型的參數(shù)時(shí),需要顯示轉(zhuǎn)換到目標(biāo)類(lèi)型,否則將會(huì)被視為unknown類(lèi)型。
CREATE OR REPLACE FUNCTION get_array(anyelement, anyelement)
RETURNS anyarray
AS $$
    SELECT ARRAY[$1, $2];
$$ LANGUAGE SQL;

SELECT get_array(1,2), get_array('a'::text,'b'::text);
 get_array | get_array 
-----------+-----------
 {1,2}     | {a,b}
(1 row)
  • 函數(shù)參數(shù)為多態(tài)類(lèi)型,而返回值為基本類(lèi)型
CREATE OR REPLACE FUNCTION is_greater(anyelement, anyelement)
RETURNS BOOLEAN
AS $$
    SELECT $1 > $2;
$$ LANGUAGE SQL;

SELECT is_greater(7.0, 4.5);
 is_greater 
------------
 t
(1 row)

SELECT is_greater(2, 4);    
 is_greater 
------------
 f
(1 row)
  • 輸入輸出參數(shù)均為多態(tài)類(lèi)型。這種情況與第一種情況一樣。
CREATE OR REPLACE FUNCTION get_array
(IN anyelement, IN anyelement, OUT anyelement, OUT anyarray)
AS $$
    SELECT $1, ARRAY[$1, $2];
$$ LANGUAGE SQL;

SELECT get_array(4,5), get_array('c'::text, 'd'::text);
  get_array  |  get_array  
-------------+-------------
 (4,"{4,5}") | (c,"{c,d}")
(1 row)

函數(shù)重載(Overwrite)

在PostgreSQL中,多個(gè)函數(shù)可共用同一個(gè)函數(shù)名,但它們的參數(shù)必須得不同。這一規(guī)則與面向?qū)ο笳Z(yǔ)言(比如Java)中的函數(shù)重載類(lèi)似。也正因如此,在PostgreSQL刪除函數(shù)時(shí),必須指定其參數(shù)列表,如:

DROP FUNCTION get_array(anyelement, anyelement);

另外,在實(shí)際項(xiàng)目中,經(jīng)常會(huì)用到CREATE OR REPLACE FUNCTION去替換已有的函數(shù)實(shí)現(xiàn)。如果同名函數(shù)已存在,但輸入?yún)?shù)列表不同,會(huì)創(chuàng)建同名的函數(shù),也即重載。如果同名函數(shù)已存在,且輸入輸出參數(shù)列表均相同,則替換。如果已有的函數(shù)輸入?yún)?shù)列表相同,但輸出參數(shù)列表不同,則會(huì)報(bào)錯(cuò),并提示需要先DROP已有的函數(shù)定義。

SQL優(yōu)化系列

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

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