MySQL優化系列5-Schema與數據類型優化

備注:測試數據庫版本為MySQL 8.0

一.Schema與數據類型優化概述

良好的邏輯設計和物理設計是高性能的基石,應該根據系統將要執行的查詢語句來設計schema,這往往需要權衡各種因素。

schema設計不佳,后期調整會非常的困難,筆者曾經遇到過一些設計問題:

  1. 日志表主鍵設為int類型,數據量達到2147483647的時候,insert數據直接報錯,導致生產環境不可用。
  2. 訂單主表反范式設計,多達100多列,導致生產環境鎖非常多。
  3. 建表的時候未指定not null,存儲了過多且不必要的null值。
  4. 整數類型一律用int,浪費諸多存儲空間。
    ......

二.選擇優化的數據類型

MySQL數據類型概述可以參考下面筆者的博客:
MySQL 8.0 數據類型小結

2.1 整數類型

類型 存儲(字節) 最小(有符號) 最大(有符號) 最小(無符號) 最大(無符號) 描述
BIT(M) (m+7)/8 --- --- --- --- 位值類型。M表示每個值的位數,從1到64.如果M省略,默認是1。比如bit(8)存儲888變為00000111
TINYINT(M) 1 -128 127 0 255
SMALLINT(M) 2 -32768 32767 0 65535
MEDIUMINT(M) 3 -8388608 8388607 0 16777215
INT,INTEGER(M) 4 -2147483648 2147483647 0 4294967295
BIGINT(M) 8 -2^63 2^63 -1 0 2^64
DECIMAL 變長(0-4個字節) M為總位數(精度),D為小數點后的位數(刻度)。如果D為0,則值沒有小數部分。最大(M)是65。最大(D)為30.如果省略D,D的默認值為0,。如果省略M,M的默認值為10. NUMBERIC的實現是DECIMAL
NUMBERIC 變化 同上
FLOAT(M,D) 4 M是總位數,D是小數點后面的位數。如果M和D省略,則將值存儲到硬件允許的限制。單精度浮點精確到7位小數。
正區間- [ –3.402823466E38 , –1.175494351E-38 ]
負區間-[ 1.175494351E-38 , 3.402823466E38]
DOUBLE(M,D) 8 M是總位數,D是小數點后面的位數。如果M和D省略,則將值存儲到硬件允許的限制。單精度浮點精確到15位小數。
正區間-[ –1.7976931348623157E308,–2.2250738585072014E-308 ]
負區間-[ 2.2250738585072014E-308 , 1.7976931348623157E308 ]
BOOL,BOOLEAN 1 TINYINT(1)的同義詞

有兩種類型的數字:整數(whole number)和實數(real number)。如果存儲整數,可以使用這幾種整數類型:TINYINT,SMALLINT,MEDIUMINT,INT,BIGINT。分別使用1字節、2字節、3字節、4字節、8字節。

整數類型有可選的UNSIGNED屬性,表示不允許負值,這大致可以使正數的上限提高一倍。例如TINYINT UNSIGNED可以存儲的范圍是0~255,而TINYINT的存儲范圍是?128~127。

更小的通常更好
例如枚舉類的, 選擇 tinyint、smallint即可,節省磁盤空間就是優化。
其它的業務相關表,例如用戶表、訂單表 可以選擇用 int類型。
雖然int類型不支持小數,但是例如金額這個,可以通過調整單位,例如單位為分,這樣就可以存小數金額了
對于一些大的日志表、分布式ID之類的,可以選擇bigint類型

2.2 實數類型

實數是帶有小數部分的數字。然而,它們不只是為了存儲小數部分;也可以使用DECIMAL存儲比BIGINT還大的整數。MySQL既支持精確類型,也支持不精確類型。

FLOAT和DOUBLE類型支持使用標準的浮點運算進行近似計算。如果需要知道浮點運算是怎么計算的,則需要研究所使用的平臺的浮點數的具體實現。

DECIMAL類型用于存儲精確的小數。

2.3 字符類型

VARCHAR和CHAR類型

類型 存儲(字節) 范圍 用途
CHAR(M) M 0 - 255 存儲定長的字符
VARCHAR(M) VARCHAR(10) 實際存儲3個字符,1個字節來存儲長度,總共占4字節
VARCHAR(1000) 實際存儲3個字符,2個字節來存儲長度,總共占5字節
不同的存儲引擎可能存在一定的差異
0-65536 存儲可變長度的字符串

1.類型選擇問題
很多時候,開發同事為了方便,直接用varchar(200) 來存儲字符,不考慮實際需求。
這樣做,存在諸多弊端。
如果是md5密碼這樣的定長字段,如果用varchar類型,會浪費一定的存儲空間。
如果存儲的字符只有5個,而這時都用varchar(200),感覺存儲空間是一樣的。但是程序端讀取的時候,varchar(200)會消耗更多的內存。

2.變長字符的更新問題
InnoDB存儲引擎
varchar由于是變長,遇到更新的時候,如果比原先的長度長很多,這個時候頁的空間不夠,會分裂頁,此時會比較消耗性能

BLOB和TEXT類型

類型 描述
TINYBLOB 最大長度255(2^8-1),使用1字節前綴存儲長度信息
BLOB 最大長度65,535(2^16-1),使用2字節前綴存儲長度信息
MEDIUMBLOB 最大長度16,777,215(2^24-1),使用3字節前綴存儲長度信息
LONGBLOB 最大長度(2^32-1)或4GB,使用4字節前綴存儲長度信息
TINYTEXT 最大長度255(2^8-1),使用1字節前綴存儲長度信息
TEXT 最大長度65,535(2^16-1),使用2字節前綴存儲長度信息
MEDIUMTEXT 最大長度16,777,215(2^24-1),使用3字節前綴存儲長度信息
LONGTEXT 最大長度(2^32-1)或4GB,使用4字節前綴存儲長度信息

BLOB是SMALLBLOB的同義詞
TEXT是SMALLTEXT的同義詞

MySQL把每個BLOB和TEXT當做一個獨立的對象處理。
當BLOB和TEXT值太大時,InnoDB會使用專門的外部存儲區域來進行存儲,此時每個值在行內需要1-4個值存儲一個指針,然后在外部存儲區域存儲實際的值

BLOB和TEXT家族之間僅有的不同是BLOB類型存儲的是二進制數據,沒有排序規則或字符集,而TEXT類型有字符集和排序規則。

2.4 日期和時間類型

類型 存儲(字節) 范圍 格式 用途
DATE 3 1000-01-01/9999-12-31 YYYY-MM-DD 日期值
TIME 3 '-838:59:59'/'838:59:59' HH:MM:SS 時間值或持續時間
YEAR 1 1901/2155 YYYY 年份值
DATETIME 8 1000-01-01 00:00:00/9999-12-31 23:59:59 YYYY-MM-DD HH:MM:SS 混合日期和時間值
TIMESTAMP 4 1970-01-01 00:00:00/2038 YYYYMMDD HHMMSS 混合日期和時間值,時間戳

關于時間類型的選擇:
1.如果只存年,用YEAR類型
2.如果只存年月日,用DATE
3.如果需要存年月日時分秒,用TIMESTAMP
不要被這個2038年給嚇到了,而不用TIMESTAMP,其實更節約存儲空間,且能容納時區信息
4.不用將TIMESTAMP轉換為數值
FROM_UNIXTIME() -- 把數值轉換為時間戳
UNIX_TIMESTAMP() -- 把時間戳轉換為數值

轉換感覺是節省了空間,不過處理起來非常的不方便,不推薦使用

2.5 其它類型

一個例子是一個IPv4地址。人們經常使用VARCHAR(15)列來存儲IP地址。然而,它們實際上是32位無符號整數,不是字符串。用小數點將地址分成四段的表示方法只是為了讓人們閱讀容易。所以應該用無符號整數存儲IP地址。MySQL提供INET_ATON()和INET_NTOA()函數在這兩種表示方法之間轉換。

一個例子是枚舉ENUM和SET類型,實際生產中使用較少,暫不考慮。

一個例子是位BIT類型,可以使用BIT列在一列中存儲一個或多個true/false值。BIT(1)定義一個包含單個位的字段,BIT(2)存儲2個位,依此類推。BIT列的最大長度是64個位。


image.png

三.范式和反范式

MySQL的OLAP會弱于傳統的Oracle、Postgresql,所以很多時候設計的時候,需要考慮使用反范式,減少表之間的連接,但是凡事都有個度,過而不及。筆者就見過為了查詢方便,開發設計的業務表都是反范式的,不但冗余多,遇到并發上來之后,鎖表現象也頻繁發生。

范式的優點和缺點:
優點:

  1. 范式化的更新操作通常比反范式化要快。
  2. 當數據較好地范式化時,就只有很少或者沒有重復數據,所以只需要修改更少的數據。
  3. 范式化的表通常更小,可以更好地放在內存里,所以執行操作會更快。
  4. 很少有多余的數據意味著檢索列表數據時更少需要DISTINCT或者GROUP BY語句。

缺點:
范式化設計的schema的缺點是通常需要關聯。稍微復雜一些的查詢語句在符合范式的schema上都可能需要至少一次關聯,也許更多。這不但代價昂貴,也可能使一些索引策略無效。例如,范式化可能將列存放在不同的表中,而這些列如果在一個表中本可以屬于同一個索引。

反范式的優點和缺點
優點:
反范式化的schema因為所有數據都在一張表中,可以很好地避免關聯。

缺點:

  1. 數據冗余
  2. 更新操作慢

混用范式化和反范式化
范式化和反范式化的schema各有優劣,怎么選擇最佳的設計?

事實是,完全的范式化和完全的反范式化schema都是實驗室里才有的東西:在真實世界中很少會這么極端地使用。在實際應用中經常需要混用,可能使用部分范式化的schema、緩存表,以及其他技巧。

例如有兩張表,一個是申請表,一個是申請的流程日志表,我們需要知道申請單最后一個審批的人,那么每次都需要在申請流程日志表中進行group by然后求最后一個審批記錄。這樣不但sql復雜,且性能慢。比較好的方法是申請表在滿足范式的情況下,新增一列最后審批人字段,通過反范式進行冗余。

四.計數器表

這個案例來自《高性能MySQL》 ,真的太厲害了,之前遇到類似的問題,都不知道如何優化。

如果應用在表中保存計數器,則在更新計數器時可能碰到并發問題。計數器表在Web應用中很常見。可以用這種表緩存一個用戶的朋友數、文件下載次數等。創建一張獨立的表存儲計數器通常是個好主意,這樣可使計數器表小且快。使用獨立的表可以幫助避免查詢緩存失效,并且可以使用本節展示的一些更高級的技巧。

應該讓事情變得盡可能簡單,假設有一個計數器表,只有一行數據,記錄網站的點擊次數:

mysql> CREATE TABLE hit_counter (
        -> cnt int unsigned not null
        -> ) ENGINE=InnoDB;

網站的每次點擊都會導致對計數器進行更新:

mysql> UPDATE hit_counter SET cnt = cnt + 1;

問題在于,對于任何想要更新這一行的事務來說,這條記錄上都有一個全局的互斥鎖(mutex)。這會使得這些事務只能串行執行。要獲得更高的并發更新性能,也可以將計數器保存在多行中,每次隨機選擇一行進行更新。這樣做需要對計數器表進行如下修改:

mysql> CREATE TABLE hit_counter (
-> slot tinyint unsigned not null primary key,
-> cnt int unsigned not null
-> ) ENGINE=InnoDB;

然后預先在這張表增加100行數據。現在選擇一個隨機的槽(slot)進行更新:

mysql> UPDATE hit_counter SET cnt = cnt + 1 WHERE slot = ceil(RAND() * 100);

要獲得統計結果,需要使用下面這樣的聚合查詢:

mysql> SELECT SUM(cnt) FROM hit_counter;

一個常見的需求是每隔一段時間開始一個新的計數器(例如,每天一個)。如果需要這么做,則可以再簡單地修改一下表設計:

mysql> CREATE TABLE daily_hit_counter (
-> day date not null,
-> slot tinyint unsigned not null,
-> cnt int unsigned not null,
-> primary key(day, slot)
-> ) ENGINE=InnoDB;

在這個場景中,可以不用像前面的例子那樣預先生成行,而用ON DUPLICATE KEY UPDATE代替:

mysql> INSERT INTO daily_hit_counter(day, slot, cnt)
-> VALUES(CURRENT_DATE, ceil(RAND() * 100), 1)
-> ON DUPLICATE KEY UPDATE cnt = cnt + 1;

如果希望減少表的行數,以避免表變得太大,可以寫一個周期執行的任務,合并所有結果到0號槽,并且刪除所有其他的槽:

mysql> UPDATE daily_hit_counter as c
-> INNER JOIN (
-> SELECT day, SUM(cnt) AS cnt, MIN(slot) AS mslot
-> FROM daily_hit_counter
-> GROUP BY day
-> ) AS x USING(day)
-> SET c.cnt = IF(c.slot = x.mslot, x.cnt, 0),
-> c.slot = IF(c.slot = x.mslot, 0, c.slot);
mysql> DELETE FROM daily_hit_counter WHERE slot <> 0 AND cnt = 0;

五.加快ALTER TABLE操作的速度

MySQL的ALTER TABLE操作的性能對大表來說是個大問題。MySQL執行大部分修改表結構操作的方法是用新的結構創建一個空表,從舊表中查出所有數據插入新表,然后刪除舊表。這樣操作可能需要花費很長時間,如果內存不足而表又很大,而且還有很多索引的情況下尤其如此。許多人都有這樣的經驗,ALTER TABLE操作需要花費數個小時甚至數天才能完成。

雖然從MySQL 5.6開始支持online DDL,不需要停機,但是每次版本發布的時候,一個團隊的人員都在等著DDL的完成,然后驗證。

那么有沒有什么方法能加快ALTER TABLE操作的速度呢?
辦法當然是有,一般有如下三種方法:

  1. 預留列
  2. 更改表定義文件
  3. MySQL 8.0 快速加列

5.1 預留列

對于一些主表,例如訂單表、客戶表等,可以在create table或Online DDL的時候,直接新增2-3個預留列,字段類型最好選擇varchar類型,這樣無論是存儲數值、字符、時間類型,都是可行的。當后面的變更需要新增列的時候,可以將預留列進行改名,直接使用。

代碼:

create table t1(id int not null,name varchar(100) not null,reserved1 varchar(200),reserved2 varchar(200));

-- 遇到變更,需要新增列身份證號
alter table t1 change reserved1 idcard varchar(200);

測試記錄:

mysql> create table t1(id int not null,name varchar(100) not null,reserved1 varchar(200),reserved2 varchar(200));
Query OK, 0 rows affected (0.02 sec)

mysql> 
mysql> desc t1;
+-----------+--------------+------+-----+---------+-------+
| Field     | Type         | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| id        | int(11)      | NO   |     | NULL    |       |
| name      | varchar(100) | NO   |     | NULL    |       |
| reserved1 | varchar(200) | YES  |     | NULL    |       |
| reserved2 | varchar(200) | YES  |     | NULL    |       |
+-----------+--------------+------+-----+---------+-------+
4 rows in set (0.00 sec)

mysql> alter table t1 change reserved1 idcard varchar(200);
Query OK, 0 rows affected (0.00 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> desc t1;
+-----------+--------------+------+-----+---------+-------+
| Field     | Type         | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| id        | int(11)      | NO   |     | NULL    |       |
| name      | varchar(100) | NO   |     | NULL    |       |
| idcard    | varchar(200) | YES  |     | NULL    |       |
| reserved2 | varchar(200) | YES  |     | NULL    |       |
+-----------+--------------+------+-----+---------+-------+
4 rows in set (0.00 sec)

mysql> 

5.2 更改表定義文件

對于一些需要修改列的屬性,例如 由varchar(100)增加到varchar(200),通過ALTER語句耗時非常久,此時可以修改表定義文件快速完成。

步驟如下:

  1. 創建一個新的空表,表結構同需要變更的表
  2. 對新表進行alter操作
  3. flush tables with read lock;
  4. 替換新表與需要變更的表的表定義文件
  5. unlock tables;

我們先來看看,給一個大表進行DDL需要多長時間。
從下面的測試我們可以看到,給一個7億多條數據的表進行DDL操作,耗時一個小時4分鐘。

mysql> select count(*) from fact_sale;
+-----------+
| count(*)  |
+-----------+
| 767830000 |
+-----------+
1 row in set (2 min 29.23 sec)

mysql> 
mysql> alter table fact_sale modify prod_name varchar(100) not null;


Query OK, 767830000 rows affected (1 hour 4 min 0.83 sec)
Records: 767830000  Duplicates: 0  Warnings: 0

下面我們使用修改表定義的方法

代碼:

CREATE TABLE  fact_sale_new like fact_sale;
alter table fact_sale_new modify prod_name varchar(200) not null;

flush tables with read lock;

-- os層操作
mv fact_sale.frm fact_sale.frm.bak
mv fact_sale_new.frm fact_sale.frm
mv fact_sale.frm.bak fact_sale_new.frm

unlock tables;

測試記錄:

mysql> 
mysql> CREATE TABLE `fact_sale_new` (
    ->   `id` bigint(8) NOT NULL AUTO_INCREMENT,
    ->   `sale_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    ->   `prod_name` varchar(200) NOT NULL,
    ->   `sale_nums` int(11) DEFAULT NULL,
    ->   PRIMARY KEY (`id`)
    -> ) ENGINE=InnoDB AUTO_INCREMENT=787621598 DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.01 sec)

mysql> 
mysql> flush tables with read lock;
Query OK, 0 rows affected (0.00 sec)

mysql> 
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)

mysql> 
mysql> show create table fact_sale\G
*************************** 1. row ***************************
       Table: fact_sale
Create Table: CREATE TABLE `fact_sale` (
  `id` bigint(8) NOT NULL AUTO_INCREMENT,
  `sale_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `prod_name` varchar(200) NOT NULL,
  `sale_nums` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=787621598 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

mysql> show create table fact_sale_new\G
*************************** 1. row ***************************
       Table: fact_sale_new
Create Table: CREATE TABLE `fact_sale_new` (
  `id` bigint(8) NOT NULL AUTO_INCREMENT,
  `sale_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `prod_name` varchar(100) NOT NULL,
  `sale_nums` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=787621598 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

mysql> 

5.3 MySQL 8.0 快速加列

5.3.1 快速加列支持類型

官方文檔列出了一些可以快速DDL的操作,大體包括:

修改索引類型
Add column 
  當一條alter語句中同時存在不支持instant的ddl時,則無法使用
  只能順序加列
  不支持壓縮表、不支持包含全文索引的表
  不支持臨時表,臨時表只能使用copy的方式執行DDL
  不支持那些在數據詞典表空間中創建的表
修改/刪除列的默認值、修改索引類型
修改ENUM/SET類型的定義
  存儲的大小不變時
  向后追加成員
增加或刪除類型為virtual的generated column
RENAME TABLE操作

5.3.2 立刻加列的限制

雖然立刻加列這一特性十分好用,但也存在著一些限制:

1、當一條alter語句中同時存在不支持instant的ddl時,則無法使用
2、只能順序加列
3、不支持壓縮表、不支持包含全文索引的表,不支持臨時表
4、不支持那些在數據詞典表空間中創建的表
5、修改ENUM/SET類型的定義時,存儲的大小不變,向后追加成員

5.3.3 立刻加列的實現

立刻加列時,只會變更數據字典中的內容:在列定義中增加新列的定義,增加新列的默認值。(information_schema.INNODB_TABLES,information_schema.INNODB_COLUMNS)

立刻加列后,當要讀取表中的數據時:由于立刻加列沒有變更行數據,讀取的行數據為原列數對應的數據;MySQL會將新增的列的默認值,追加到讀取的數據后面。

當讀取數據行時,通過判斷數據行的頭信息中的instant 標志位,可以知道該行的格式是 “新格式”:該行頭信息后有一個新字段 "列數"通過讀取數據行的 “列數” 字段,可以知道該行數據中多少列有"真實"的數據,從而按列數讀取數據。

快速加列特性,在增加列時,實際上只是修改了元數據,原來存儲在文件中的行記錄并沒有被修改。當行格式為redundent類型時,記錄解析是不依賴元數據的,可以自解析,但如果行格式是dynamic或者compact類型,由于行內不存儲元數據,尤其是列的個數信息,其記錄的解析需要依賴元數據的輔助。因此為了支持動態加列功能,會對行格式做一定的修改。

大體思路如下:
如果表上從未發生過instant add column, 則行格式維持不變;如果發生過instant ddl, 那么所有新的記錄上都被特殊標記了一個flag, 同時在行內存儲了列的個數;由于只支持往后順序加列,通過列的個數就可以知道這個行記錄中包含了哪些列的信息。

MySQL 5.7

mysql> select count(*) from fact_sale;
+-----------+
| count(*)  |
+-----------+
| 767830000 |
+-----------+
1 row in set (2 min 28.01 sec)

mysql> 
mysql> show create table fact_sale\G
*************************** 1. row ***************************
       Table: fact_sale
Create Table: CREATE TABLE `fact_sale` (
  `id` bigint(8) NOT NULL AUTO_INCREMENT,
  `sale_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `prod_name` varchar(200) NOT NULL,
  `sale_nums` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=787621598 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

mysql> alter table fact_sale add column reserverd1 varchar(100);
Query OK, 0 rows affected (15 min 33.13 sec)
Records: 0  Duplicates: 0  Warnings: 0

MySQL 8.0:

mysql> select count(*) from fact_sale;
+-----------+
| count(*)  |
+-----------+
| 767830000 |
+-----------+
1 row in set (1 min 4.25 sec)

mysql> 
mysql> 
mysql> 
mysql> show create table fact_sale\G
*************************** 1. row ***************************
       Table: fact_sale
Create Table: CREATE TABLE `fact_sale` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `sale_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `prod_name` varchar(200) NOT NULL,
  `sale_nums` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=787621598 DEFAULT CHARSET=utf8mb3
1 row in set (0.01 sec)

mysql> 
mysql> alter table fact_sale add column reserverd1 varchar(100);
Query OK, 0 rows affected (0.04 sec)
Records: 0  Duplicates: 0  Warnings: 0

參考:

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

推薦閱讀更多精彩內容