《高性能MySQL》筆記(2)——創(chuàng)建高性能索引

創(chuàng)建高性能索引

索引是什么?有什么作用?

索引是存儲引擎用于快速找到記錄的一種數(shù)據(jù)結(jié)構(gòu)

如書的目錄索引一般,數(shù)據(jù)庫中的索引也是另外存儲一些數(shù)據(jù)表的關(guān)鍵信息,在查詢的時候可以更方便定位到想要找的那一些內(nèi)容頁

在數(shù)據(jù)量小、負(fù)載低的時候,索引的作用可能并不明顯。但一旦到達(dá)一定量級,正確的索引往往可以輕易將性能提升幾個數(shù)量級。所以,索引優(yōu)化是查詢性能優(yōu)化最有效的手段

大多數(shù)框架都實現(xiàn)了ORM,而ORM可能很難兼顧到 每個開發(fā)者、每個業(yè)務(wù) 專門定制化的索引。但這并不意味著使用框架和ORM就不需要理解索引!最好的方法是:

簡單的、數(shù)據(jù)量較小的查詢可以直接使用ORM,當(dāng)某些復(fù)雜的查詢出現(xiàn)性能問題時,考慮舍棄ORM,采用原生的SQL語句去優(yōu)化性能

索引的類型

索引是在存儲引擎層上實現(xiàn)的,而非服務(wù)器層。所以每個引擎支持的索引類型、實現(xiàn)方法都有所不同。在使用上需要有所注意。這里主要討論的是InnoDB引擎的索引

B-Tree 索引

最常見的索引類型,絕大多數(shù)MySQL引擎都支持。一般沒有特殊說明,索引即B-Tree索引(這類索引采用類似B-Tree的結(jié)構(gòu)來存儲數(shù)據(jù),如 B樹、B+樹 等)

InnoDB使用B+ Tree的結(jié)構(gòu)存儲索引

B+樹

參考鏈接:理解B+樹算法和Innodb索引

為什么通過索引查找數(shù)據(jù),可以加快訪問的速度?

B-Tree 結(jié)構(gòu)存儲索引,通常意味著所有數(shù)據(jù)按照一定的順序存儲。在查找的時候,引擎就可以從根結(jié)點比較節(jié)點頁的值和要查找的值,找到合適的指針進(jìn)入下層子節(jié)點,不斷重復(fù)比較最終找到相應(yīng)值。避免了使用全表掃描

特別的,在B+ Tree結(jié)構(gòu)中,所有的非葉子節(jié)點都用作“查找”,葉子節(jié)點則指向具體的數(shù)據(jù)。如:有100條記錄,對于一個索引樹,有且僅有100個葉子節(jié)點,它們分別指向每一條記錄,這些記錄本身是有序且用指針相連

所以,索引在按照索引順序去查找范圍數(shù)據(jù)的時候,效率也非常高

索引對多個值進(jìn)行排序的記錄順序

如,組合索引key(last_name, first_name, birth),這個索引對應(yīng)的記錄的順序是,先按照last_name排序,如果相同,則按first_name,最后按birth。

這就是為什么條件的順序必須與組合索引的順序一致

order by也可以使用索引

因為索引樹中的節(jié)點是有序的,所以除了按值查找之外,索引還可以用于查詢中的order by操作

可以使用B-Tree 索引的查詢類型

假設(shè)有一索引,key(last_name, first_name, birth)

  • 全值匹配 last_name='ABC' and first_name='DEF' and birth='1999-01-01'
  • 匹配最左前綴 last_name='ABC'
  • 匹配列前綴 last_name LIKE 'AB%'
  • 匹配范圍值 last_name >= 'A' and last_name <= 'C'
  • 精確匹配某一列,并范圍匹配另一列 last_name='ABC' and first_name > 'C'
  • 只訪問索引(不訪問值)的查詢 (覆蓋索引) select last_name, first_name, birth from xxtbl
B-Tree 索引的查詢限制
  • 必須從最左列開始匹配,否則該索引不可用
  • 不能跳過索引的某一列
  • 如果查詢中有某個列是范圍匹配,則右邊的其他列無法使用索引

哈希索引

  • 基于哈希表實現(xiàn),只有精確匹配索引所有列的查詢才有效
  • 存儲引擎針對每一行計算出一個哈希碼,與行指針保存成一張表,便于查詢
  • 常用的InnoDB和MyISAM引擎并不支持,但I(xiàn)nnoDB引擎有自適應(yīng)哈希索引,它會根據(jù)查詢,動態(tài)優(yōu)化(不需要自己設(shè)置索引)
自定義(偽)哈希索引

不支持哈希索引的引擎,可以通過新增一個被索引的hash列,用crc32等算法做哈希,可以優(yōu)化查詢

例子:需要存儲并查詢一個url列

select * from tbl where url = 'http://xx.com/xx/yy/?zz=aa&bb=cc'

上面的語句明顯在比較上非常吃力,如果對url列設(shè)置索引,需要比較大的代價。如果引入一個被索引的hash列,在查找的時候,性能會變得很高

select * from tbl where url_crc = CRC32('URL...') and url = 'URL...'

注意:

  • 不要直接使用強加密的hash算法,如md5、sha1等,否則生成過長的字符串,效果不好
  • 也需要注意避免過多的哈希沖突,如果crc32不滿足,可以使用fnv64或自行實現(xiàn)一些哈希方案
  • 精確匹配查詢的時候,必須在條件中同時帶上原列和hash列,如where crc=CRC32('xx') and url='xx'
  • 如果不想維護(hù)hash列,可以創(chuàng)建觸發(fā)器,在創(chuàng)建的時候自動填充

全文索引

一種特殊類型的索引,查找的是文本中的關(guān)鍵詞,而不是直接比較索引中的值。它更類似于搜索引擎做的事情。另外,在列上同時創(chuàng)建全文索引和B-Tree索引,不會有沖突

索引的優(yōu)點

  • 索引大大減少了服務(wù)器需要掃描的數(shù)據(jù)量
  • 索引可以幫助服務(wù)器避免排序和臨時表
  • 索引可以將隨機I/O變?yōu)轫樞騃/O

另外需要注意的是,索引的建立和使用需要一些空間和額外工作。所以,如果表本身很小且可控,全表掃描或許更快

高性能的索引策略(如何在SQL語句中使用索引)

獨立的列

如果列本身是表達(dá)式的一部分,或是函數(shù)的參數(shù),這種“不獨立的列”無法使用此列索引。如:select * from tbl where id + 1 < 5

前綴索引

有時需要索引一個較長的字符列,這樣會使索引變得大且慢,解決方案可以是之前說的哈希索引,但有時這樣仍然不夠好。這時候需要考慮使用前綴索引。

對于TEXT、BLOB和較長的VARCHAR,如果確實需要索引,則必須創(chuàng)建前綴索引,設(shè)置方法如下:

alter table tbl add key (city(7))

這樣就設(shè)置了一個針對city字段前7個字符做索引的前綴索引。

前綴索引是一種能使索引更小、更快的有效方法。但缺點是不支持order by, group by和覆蓋掃描

難點:如何確定一個較好的前綴值?(上面的例子是7)

-- 首先確定所有記錄中,distinct的記錄占比
select count(distinct city)/count(*) from tbl;

-- 假設(shè)上面得到的結(jié)果值為0.3,則表示城市平均重復(fù)2次左右
-- 為了前綴索引有良好的區(qū)分度,要求使用索引之后,占比較接近實際的0.3
select count(distinct left(city, 3))/count(*) as city3,
       count(distinct left(city, 4))/count(*) as city4,
       count(distinct left(city, 5))/count(*) as city5,
       ...
from tbl;
-- 最后取一個接近0.3,且前綴值不太大的一個值,作為最后的前綴值

多列索引

一個錯誤的索引設(shè)置方案:WHERE條件的列都建立一個單獨的索引

示例: create table t (c1 int, c2 int, c3 int, KEY(c1), KEY(c2), KEY(c3));

導(dǎo)致的結(jié)果是在多條件查詢的時候,無論怎么選擇,都只能使用其中一個索引,而之后的條件則沒有索引可以走

在新版本的MySQL中,查詢會被優(yōu)化成多條走索引的語句,然后聯(lián)合或相交得出結(jié)果。但這樣做相當(dāng)于多次查詢,明顯不是最優(yōu)的索引策略

應(yīng)該采用的方式是,根據(jù)查詢,設(shè)置組合索引

選擇合適的索引列順序

我們已知查詢的順序會影響到索引列順序的設(shè)置

  • 將選擇性最高的列放在前面通常是比較好的
  • 除了考慮WHERE條件的優(yōu)化,也需要考慮GROUP BY, ORDER BY, DISTINCT
  • 考慮值的分布情況??梢园褏^(qū)分度大的列放在前面,適當(dāng)去調(diào)整語句的編寫
  • 極端的一些情況。比如某一列中,某個值出現(xiàn)的概率極高,這種可能不太適合建立索引

針對第三個情況的例子:

假設(shè)有一張表有customer_idstaff_id,這兩列經(jīng)常作為條件用于篩選。此時可以通過實際情況或SQL進(jìn)行查詢,不同的值更多的列可以考慮放在前面

select
count(distinct staff_id)/count(*) as staff_id_selectivity,
count(distinct customer_id)/count(*) as customer_id_selectivity,
count(*)
from payment;

-- res:
-- staff_id_selectivity: 0.0001, customer_id_selectivity: 0.0373
-- 此時應(yīng)該選擇 KEY(customer_id, staff_id)

聚簇索引

聚簇索引并不是一種單獨的索引類型,而是一種數(shù)據(jù)存儲方式。對于InnoDB來說,聚簇索引實際上是同一個結(jié)構(gòu)中保存了B-Tree索引和數(shù)據(jù)行

一張表只能有一個聚簇索引,因為一份數(shù)據(jù)只能保存在一個地方。在InnoDB中,不能直接選擇某一列成為聚簇索引,而是直接通過主鍵作為聚簇索引

聚簇索引的優(yōu)點:

  • 可以把相關(guān)的數(shù)據(jù)保存在一起。例如通過用戶id獲取用戶的數(shù)據(jù),如果用戶id不是聚簇索引,則找到用戶id節(jié)點之后,還需要一次I/O才能拿到用戶的數(shù)據(jù)
  • 相比非聚簇索引,數(shù)據(jù)訪問更快,理由同上
  • 使用覆蓋索引掃描的查詢,可以直接使用頁節(jié)點中的(其他)主鍵值

聚簇索引的缺點:

  • 聚簇索引可以最大限度提高I/O密集型應(yīng)用的效率。但如果所有數(shù)據(jù)都已經(jīng)加載到內(nèi)存中,則訪問順序沒那么重要,聚簇索引也就沒什么優(yōu)勢
  • 插入速度嚴(yán)重依賴插入排序,如果不是按照主鍵順序加載數(shù)據(jù),那么在加載完成后最好使用OPTIMAZE TABLE命令重新組織一下表
  • 更新聚簇索引列的代價很高,因為會強制InnoDB將每個被更新的行移動到新的位置
  • 基于聚簇索引的表在插入新行,或者主鍵被更新導(dǎo)致需要移動行的時候,可能面臨“頁分裂”的問題。當(dāng)行的主鍵值要求必須將這一行插入到某個已滿的頁中時,存儲引擎會將該頁分裂成兩個頁面來容納該行,這就是一次頁分裂操作,頁分裂會導(dǎo)致表占用更多的磁盤空間
  • 聚簇索引可能導(dǎo)致全表掃描變慢,尤其是行比較稀疏,或者由于頁分裂導(dǎo)致數(shù)據(jù)存儲不連續(xù)的時候
  • 二級索引(非聚簇索引)可能比想象中的要更大,因為在二級索引的葉子節(jié)點包含了引用行的主鍵列
  • 二級索引訪問需要兩次索引查找,而不是一次

對于InnoDB引擎的表來說

  • 如果沒有什么數(shù)據(jù)需要聚集,可以設(shè)置一個自增列作為主鍵。保證順序?qū)懭牒鸵恍┎僮鞯谋憬菪?/li>
  • 最好避免隨機的(不連續(xù)且值的分布范圍非常大)聚簇索引,特別是對于I/O密集型的應(yīng)用。從性能上來說,類似UUID這種隨機性很大的內(nèi)容,作為聚簇索引會非常糟糕

所以通常情況下,保證InnoDB的主鍵是順序插入的,減少不必要的調(diào)整和頁分裂,性能更優(yōu)

在高并發(fā)的時候,順序的主鍵可能會造成爭用,這種情況下不使用順序的主鍵

覆蓋索引

如果一個索引包含(覆蓋)所有需要查詢的字段的值,我們就稱之為“覆蓋索引”

好處:索引中已經(jīng)包含了全部數(shù)據(jù),不需要回表查詢。通常來說,索引沒有太多多余的數(shù)據(jù),規(guī)模小,所以查起來速度很快(MySQL中只有B-Tree 索引可以做覆蓋索引)

當(dāng)發(fā)起一個覆蓋索引的查詢時,在EXPLAIN的Extra列可以看到“Using index”的信息

一則簡單明了的例子:

有一張products表,有主鍵prod_id,以及關(guān)聯(lián)其他表的的兩個id:aid、bid,這兩個id形成一個key(aid, bid),然后還有其他的一些產(chǎn)品信息

select aid, bid from products;

上述語句就是最簡單的覆蓋索引查詢,涉及的列只有索引列

復(fù)雜一些的例子:

products表中有一個key(actor, title),但是現(xiàn)在要查出滿足條件的所有數(shù)據(jù)項

select * from products where actor='abc' and title like '%abc%';

上述語句并不能覆蓋索引。且title前面也有%,故最終只能使用actor索引數(shù)據(jù),然后回表查滿足條件的title,再返回整行數(shù)據(jù)

解決方案:

建立索引key(actor, title, prod_id)

select * from products join (
  select prod_id from products where actor='abc' and title like '%abc%'
) t1 on (t1.prod_id = products.prod_id);

上述語句的子查詢滿足覆蓋索引的查詢(一個索引包含所有需要查詢的字段的值)。另外,where條件也可以匹配最左前綴actor

如果在上述子查詢中,過濾掉了大多數(shù)的不符合條件的products,且本身此表的規(guī)模有一定大小,則優(yōu)化效果會很好,否則可能適得其反

示例:

  1. abc這個actor的條目有30000條,其中20000條title包含有abc => 這個屬于規(guī)模大、過濾少的,效果不好
  2. abc這個actor的條目有30000條,其中40條title包含有abc => 這個屬于規(guī)模大、過濾多的,效果很好
  3. abc這個actor的條目有50條,其中10條title包含有abc => 這個屬于規(guī)模小,效果不好(子查詢需要耗費一些性能)

另外的:

InnoDB引擎的二級索引的葉子節(jié)點是包含主鍵的值的,所以,主鍵是可以作為查詢列覆蓋索引的

使用索引掃描來做排序

MySQL有兩種方式可以生成有序的結(jié)果:

  • 通過排序操作
  • 按索引順序掃描

當(dāng)按索引順序掃描排序時,在EXPLAIN的type列會顯示index。這里注意區(qū)分之前的覆蓋索引,覆蓋索引是在Extra列顯示“Using index”

掃描索引本身速度很快,但是如果不能覆蓋查詢的所有列,就不得不每掃描一條索引記錄就回表查詢一次對應(yīng)的行,這樣會造成隨機I/O,這種情況是不如順序地全表掃描的

所以,只有以下條件同時滿足,引擎才會按索引順序掃描:

  • 滿足最左前綴的要求,即order by的列必須是索引列的最左前綴子列
  • order by的列順序和索引列順序一致,且所有列的排列方向(正序、倒序)必須一致
  • 如果查詢需要關(guān)聯(lián)多張表,則只有當(dāng)order by的列都是第一張表的字段才可以

有一種情況可以不滿足最左前綴,就是前導(dǎo)列為常量的時候。即假設(shè)有KEY(a,b,c),WHERE或JOIN子句對a已經(jīng)指定了定值,則ORDER BY可以只針對b和c字段

MySQL可以使用同一個索引滿足排序和查找行的任務(wù),如果可能,盡量設(shè)計這樣的索引

例子:假設(shè)有KEY(rental_date, inventory_id, customer_id)

以下方式可以實現(xiàn)索引掃描:

  • 前導(dǎo)列為常量,最左前綴:... where rental_date = '2005-05-25' order by inventory_id desc
  • 純粹的最左前綴(與where無關(guān)):... where rental_date > '2005-05-25' order by rental_date, inventory_id

以下方式不能實現(xiàn)索引掃描:

  • 排列方向不一致:... where rental_date = '2005-05-25' order by inventory_id desc, customer_id asc
  • 引用了不在索引的列:... where rental_date = '2005-05-25' order by inventory_id, staff_id
  • 不能組合成最左前綴:... where rental_date = '2005-05-25' order by customer_id
  • 前導(dǎo)列為范圍值:... where rental_date > '2005-05-25' order by inventory_id, customer_id
  • 前導(dǎo)列使用了IN,相當(dāng)于范圍值:... where rental_date = '2005-05-25' and inventory_id in (1, 2) order by customer_id

壓縮(前綴壓縮)索引

MyISAM使用前綴壓縮來減少索引的大小,從而讓更多的索引放入內(nèi)存

特點:

  • 默認(rèn)只壓縮字符串
  • 方式大致是:對于一個索引塊,先保存一個完整的字符串,然后后面如果有前綴相同的字符串,則只存儲不相同的值。比如,存儲一個“perform”,之后存儲“performance”只需要存儲“7,ance”即可
  • 無法在索引塊中使用二分查找,如果倒序掃描——order by xx desc的話,由于需要從頭開始掃描,所以會比較慢
  • 適合I/O密集型的應(yīng)用,不適合CPU密集型的應(yīng)用

重復(fù)索引、冗余索引

MySQL允許在相同列創(chuàng)建多個索引,并需要維護(hù)重復(fù)的索引,在優(yōu)化器優(yōu)化查詢時,逐個考慮(這種情況是影響性能的。有些沒有意義,需要去掉,但有時候是需要的)

重復(fù)索引:在相同的列按照相同的順序創(chuàng)建相同類型的索引。這種是必須去掉的

錯誤示范:

create table t(
  ID int NOT NULL PRIMARY KEY,
  A  int NOT NULL,
  UNIQUE(ID),
  INDEX(ID)
) ENGINE=InnoDB;

上面三種索引(主鍵、唯一、普通索引)是同類型的B-Tree 索引,必須去掉(如果索引分別是INDEX和FULLTEXT,它們屬于不同類型的索引,也不是重復(fù)索引,是完全沒問題的)

冗余索引:如果已有KEY(A, B),這時再創(chuàng)建一個KEY(A),由于前者的左前綴包含A,所以后者是冗余索引。這種需要按實際情況分析

另外,在InnoDB中,主鍵默認(rèn)已經(jīng)包含在二級索引中,所以 KEY(A)
KEY(A, ID) 如果同時存在,也屬于冗余索引

大多數(shù)情況,冗余索引是不需要的,應(yīng)該盡量擴展已有的索引而不是建立新的索引。但如果擴展索引會導(dǎo)致索引太大,影響原有的查詢,這時就需要考慮冗余索引了

例子:如果在整數(shù)列上有一個索引,現(xiàn)在需要額外加一個長的VARCHAR列來擴展索引

假設(shè)原來有以下需求,記作Q1。對應(yīng)有一個索引KEY(state_id)

select count(*) from userinfo where state_id = 5;

現(xiàn)在有另外的新需求,記作Q2。如果引入覆蓋索引,對應(yīng)的索引是KEY(state_id, city, address)

select city, address from userinfo where state_id = 5;

由于MyISAM前綴索引的原因,如果采用直接擴展的方式,性能下降很嚴(yán)重;InnoDB影響不太大。書上例子的測試結(jié)果:

只有state_id 只有擴展索引 同時有state_id和擴展索引
MyISAM, Q1 114.96 25.40 112.19
MyISAM, Q2 9.97 16.34 16.37
InnoDB, Q1 108.55 100.33 107.97
InnoDB, Q2 12.12 28.04 28.06

有兩個索引的缺點就是,索引成本高,增刪改記錄的效率變低。書上例子的測試結(jié)果:

插入100W行數(shù)據(jù)(單位:秒) 只有state_id 同時有state_id和擴展索引
InnoDB 80 136
MyISAM(壓縮索引) 72 470

在決定刪除“認(rèn)為無用”的冗余索引時,需要檢查是否有采用到主鍵作為二級索引的查詢,如... where A = 1 order by id。如果有,當(dāng)去掉
KEY(A) ,只保留 KEY(A, B),會導(dǎo)致此查詢不能用索引掃描排序

可以使用一些工具檢測重復(fù)和冗余的索引

未使用的索引

可能會有服務(wù)器永遠(yuǎn)不用的索引,這種也建議刪除。同樣可以通過工具檢測出

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

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