Mysql技術內幕InnoDB存儲引擎-表&索引算法和鎖


4.1、innodb存儲引擎表類型
innodb表類似oracle的IOT表(索引聚集表-indexorganized table),在innodb表中每張表都會有一個主鍵,如果在創建表時沒有顯示的定義主鍵則innodb如按照如下方式選擇或者創建主鍵。首先表中是否有唯一非空索引(unique not null),如果有則該列即為主鍵。不符合上述條件,innodb存儲引擎會自動創建一個6字節大小的指針,rowid()。

4.2、innodb邏輯存儲結構
innodb的邏輯存儲單元由大到小分別是 tablespace,segment,extent,page(block)組成。

4.2.1、表空間(tablespace)

所有數據都是存放在表空間中的,啟用了參數innodb_file_per_table,則每張表內的數據可以單獨放到一個表空間中,每張表空間內存放的只是數據,索引和插入緩沖,其他類的數據,如undo信息,系統事務信息,二次寫緩沖等還是存放在原來你的共享表空間。
閱讀更多…

4.2.2、段(segment)

常見的segment有數據段、索引段、回滾段。innodb是索引聚集表,所以數據就是索引,索引就是數據,那么數據段即是B+樹的頁節點(leaf node segment),索引段即為B+樹的非索引節點(non-leaf node segment)。而且段的管理是由引擎本身完成的。
4.2.3、區(extend)
區是由64個連續的頁主成,每個頁大小為16K,即每個區的大小為(64*16K)=1MB,對于大的數據段,mysql每次最多可以申請4個區,以此保證數據的順序性能。
4.2.4、頁(page)
頁是innodb磁盤管理最小的單位,innodb每個頁的大小是16K,且不可更改。常見的類型有:數據頁 B-tree Node;undo頁 Undo Log Page;系統頁 System Page;事務數據頁 Transaction system Page;插入緩沖位圖頁 Insert Buffer Bitmap;插入緩沖空閑列表頁 Insert Buffer freeBitmap;未壓縮的二進制大對象頁Uncompressed BLOB Page;壓縮的二進制大對象頁 Compressed BLOB Page。
4.2.5、行
innodb存儲引擎是面向行的(row-oriented),也就是說數據的存放按行進行存放。每個頁最多可以存放16K/2~200行,也就是7992個行。


4.3、innodb物理存儲結構
innodb引擎由共享表空間,日志文件(redo log),表結構定義文件組成。

4.4、innodb行記錄格式
mysql從5.1開始,innodb提供了compact和redundant(為了兼容以前版本)兩種格式來存放行記錄數據。
4.4.1、compact行記錄格式
Compact行記錄的設計目標是能高效存放數據。不管是char還是varchar類型,NULL指是不占用存儲空間的。行記錄中還包括兩個隱藏列 事務ID列(6字節)和回滾指針列(7字節) 若沒有定義的PrimaryKey 會增加一個6字節的RowID列。InnoDB在頁內部是通過一種鏈表方式串聯各個行記錄的。
4.4.2、redundant行記錄格式
Redundant行記錄格式為了兼容以前版本。每個行最多有1023個列,因為列的數量占用了10位。對于varchar的NULL值,它不占用任何存儲空間,而對于類型char的NULL值需要占用空間。
4.4.3、行溢出數據
innoDB存儲引擎可以將一條記錄中的某些數據存儲在真正的數據頁面之外,作為行溢出數據。Varchar(N)中的N指的是字符的長度,官方手冊中定義的65535長度是指所有VARCHAR列的長度總合。
數據一般都是存放在B-tree Node的頁類型中,但是發生行溢出的時,存放行溢出的頁類型為Uncompress BLOB Page。如果一個頁中至少放入兩行的數據,那varchar就不會存放到BLOB頁中,閥值長度為8098。對于TEXT或者BLOB的數據類型,我們總是以為它們是放在Uncompressed BLOB Page中的,其實這也是不準確的,放在數據頁還是BLOB頁同樣和前面討論的VARCHAR一樣。
4.4.4、compressed與dynamic記錄格式
InnoDB Plugin引入了新的文件格式成為Barracuda文件格式,它擁有兩種新的行記錄格式Compressed和Dynamic兩種,它對于存放BLOB的數據采用了安全的行溢出方式。
4.4.5、char的行結構存儲
從mysql4.1開始CHR(n),中N指定的是字符的長度,而不是之前版本的字節長度。也就是說在不同字符集下,CHAR的內部存儲不是定長的數據。可以通過select a,char_length(a),length(a) from t;查看字符和字節數。所以在多字符集下,char和varchar占用a空間是一樣的。

4.5、innodb數據頁結構
InnoDB數據頁由七部分組成:File Header:文件頭( 38 bytes )Page Header:頁頭( 56 bytes )Infimum + Supremum Records:頁中上/下界記錄Users Records:用戶記錄,即行記錄Free Space:空閑空間Page Directory:葉目錄File Trailer:文件結尾信息

4.6、named file formats
innodb存儲引擎通過named file formats機制來解決不同版本下頁結構兼容性問題。之前的版本定義為Antelope(包括Compact和Redudant文件格式),最新定義為Barracuda(包括Compressed和Dynamic文件格式)。使用參數innodb_file_format指定文件格式。

4.7、約束
4.7.1、數據完整性
innodb提供了以下四種約束:Primary key,Unique Key,Foreign Key,Default,Not NULL。
4.7.2、約束的創建和查找
創建時候定義,或者使用alter table定義。
4.7.3、約束和索引的區別
primary key和unique key既是約束也是主鍵。約束是一個邏輯的概念,用來保證數據完整性,而索引是一個數據結構,有邏輯上的概念,在數據庫中更是一個物理存儲的方式。
4.7.4、對于錯誤數據的約束
可以通過修改sql_mode來保證約束的強制性。
4.7.5、ENUM和SET約束
由于mysql不支持check約束,所以可以通過ENUM和SET來實現部分需求,還可以通過觸發器來實現check約束,注意需要修改sql_mode=’strict_trans_tables’; 只能限于對離散數值的約束,對于ENUM 若插入非法值將插入空字符串作為特殊錯誤值。
4.7.6、觸發器與約束
觸發器的作用是在insert,delete和update命令之前或之后自動調用sql命令或者存儲過程。所以一個表最多可以建立6個觸發器。
4.7.7、外鍵

4.8、視圖
4.8.1、視圖的作用
4.8.2、物化視圖
Oracle數據庫支持物化視圖—該視圖不是基于基表的虛表,而是根據基表實際存在的實表,物化視圖可以用于預先計算并保存表鏈接或聚集等耗時較多的操作結果。在MS中,這種視圖為索引視圖。當基表發生了DML操作后,物化視圖采用ON DEMAND和ON COMMIT方式刷新進行同步。Mysql的視圖不支持物化視圖,都是虛擬的。

4.9、分區表
4.9.1、分區表的概述
分區表不是在存儲引擎曾完成的,所以不止innodb支持分區表功能。myisma,ndb等都支持。mysql的分區表是水平分區,并不是垂直分區,mysql的分區表是局部分區索引,一個分區中既存儲數據又存放索引。當前mysql數據庫支持以下幾種類型的分區:Range分區,行數據基于屬于一個給定連續區間的列值放入分區,這個值只能是整數。VALUE LESS THAN需指定MAXVALUE值的分區,主要用于日期列的分區。對于RANGE分區的查詢,優化器只能對YEAR() TO_DAYS() TO_SECONDS()和UNIX_TIMESTAMP()函數進行優化選擇。LIST分區和range類似,只是list分區里面是離散的值,這個值只能是整數。(VALUE IN對于未定義的插入,MySQL會拋出異常。對于多條記錄同時插入過程中存在未定義的值時,MyISAM分區會允許之前的行數據插入,而拒絕之后的行數據插入,但是InnoDB將其視為一個事務從而ROLLBACK整個插入。HASH分區,根據用戶自定義的表達式的返回值 返回值不為負(PARTITION BY HASH (expr) 將數據均勻分布還可按LINEAR HASH分區區別在于算法不同)。hash分區的目的是將數據均勻的分布到預先定義的各個分區中,保證各分區的數據量大致一致。KEY分區,根據mysql數據庫提供的哈西函數進行分區。key分區和hash分區相似,不同在于hash分區是用戶自定義函數進行分區,key分區使用mysql數據庫提供的函數進行分區。columns分區,mysql-5.5開始支持COLUMNS分區,可視為RANGE和LIST分區的進化,COLUMNS分區可以直接使用非整形數據進行分區。RANGE COLUMNS分區可對多個列的值進行分區。不論什么類型的分區,如果表中存在主鍵和唯一索引,那么分區列必須是主鍵或者唯一索引的一個組成部分。否則回報錯。
4.9.2、子分區
mysql允許在RANGE和LIST分區上再進行HASH或者key的子分區。每個分區上的子分區數量必須相同。在每個分區內,子分區的名稱是唯一的,分區可以放到不同磁盤上。
4.9.3、分區中的NULL值
RANGE,HASH,KEY分區如果插入null值,mysql會把它放入最左邊的分區,如果刪除最左邊的分區,null值不會被刪除,他會記錄到新的最左邊的分區。LIST分區如果沒有指定NULL值的存放位置,那么就會報錯。
4.9.4、分區的性能
OLTP(在線事務處理,如博客,電子商務,網絡游戲)系統不適合使用分區表,如果磁盤空間和磁盤IO沒出現瓶頸,也不建議使用分區表。而OLAP(在線分析處理,如數據倉庫,數據集市)比較適合分區操作。

索引和算法
索引和開銷是需要找一個平衡點,過多或者過少都會影響性能,從而導致負載過高,浪費硬件資源。而且索引應該一開始就需要添加上,事后添加的話需要DBA根據監控大量SQL語句,耗費大量時間。

5.1、innodb存儲引擎概述
innodb支持常見的兩種索引,B+樹索引和hash索引。hash索引是自適應的,不能認為干預。B+樹是由平衡二叉樹演化而來,但是B+樹不是一個二叉樹。B+樹并不能直接找到具體的行,B+樹索引只能找到數據行所在的頁,然后數據庫通過把頁讀入內存,再在內存中進行查找。

5.2、二分查找法
頁中的具體行就是通過二分法查找的。1946年發明的二分查找法,直到1962年才出現完整正確的二分查找法。

5.3、平衡二叉樹
平衡二叉樹(左節點鍵值<根節點鍵值 <右節點鍵值)首先的符合二叉樹定義,其次必須滿足任何節點的左右兩個子樹高度最大差1.平衡二叉樹的效率較高,但是維護平衡二次樹需要消耗比較多的資源。多用于內存結構對象中,維護開銷相對比較小。

5.4、B+樹
B+樹是從B樹和索引順序訪問方法演化而來。在B+樹中,所有記錄節點都是按鍵值的大小順序存放在同一層的葉節點中,各頁節點指針進行鏈接。同時它們的父節點只是作為索引節點使用。
5.4.1、B+樹的插入操作
B+樹總會保持平衡,但是對于新插入的值可能需要大量拆分,這樣會消耗大量磁盤資源,所以B+樹有了旋轉(rotation)功能,旋轉發生在leat page已經滿了,但是其左右節點沒有滿的情況下,這時B+樹并不會著急去拆分頁的操作,而且是將記錄轉移到所在頁的兄弟節點上,通常左兄弟先被檢查。具體操作看書。
5.4.2、B+樹的刪除操作
B+樹使用填充因子(fill factor)來控制樹的刪除變化,50%是填充因子可設的最小值。B+樹的刪除操作同樣必須保證刪除后頁節點中的記錄依然排序。具體操作看書。

5.5、B+樹索引
B+樹索引在數據庫中有一個特點是高扇出性(fan out),B+樹的高度一般是2-3層。B+樹索引可以分為聚集索引(clustered index)和輔助聚集索引(secondary index),其內部都是B+樹,葉節點存放著所有的數據。它們不同的是:葉節點存放的是否是一整行的信息。聚集索引:即表中數據按照主鍵順序存放,而聚集索引就是按照每張表的主鍵構造一顆B+樹,并且葉節點中存放著整張表的行記錄數據。聚集索引的存儲并不是物理上的連續,而是邏輯上的連續。它的另一個好處是:對于主鍵的排序查找和范圍查找速度非常快。
輔助索引:也稱為非聚集索引,葉級別不包含行的全部數據,葉節點除了包行鍵值以外,每個葉級別中的索引行中還包含了一個書簽,該書簽就是對應行數據的聚集索引鍵。



5.5.1、B+樹索引的管理
索引可以索引整個列的數據,也可以只索引一個列的開頭部分數據。InnoDB Plugin支持一種稱為快速索引創建方法,這種方法只限定于輔助索引,創建索引會對表加上一個S鎖,刪除時只需將輔助索引的空間標記為可用,并刪除內部視圖上的對該表的索引定義即可。

5.6、B+樹索引的使用
5.6.1、什么時候使用B+樹索引
當某個字段的取值范圍很廣,幾乎沒有重復,即高選擇性,則使用B+樹索引是最適合的。根據筆者經驗,一般取出數據占整個的20%時,優化器就不會使用索引,而是全表掃描。
5.6.2、順序讀,隨機讀與預讀取
順序讀是指根據索引的葉節點數據就能順序地讀取所需要的行數據,只是邏輯地順序讀在物理磁盤上可能還是隨機讀取。隨機讀是指一般需要根據輔助索引葉節點中的主鍵尋找實際行數據,而輔助索引和主鍵所在的數據段不同,因此訪問方式是隨機的。為提高讀取性能,InnoDB采用預讀取方式將所需數據讀入內存,包括隨機預讀取 random read ahead 和線性預讀取 linear read ahead。但是自InnoDB Plugin1.0.4起,隨機訪問的預讀取被取消了,保留了線性預讀取,并加入了innodb_read_ahead_threshold參數。它控制一個區中多少頁被順序訪問時,InnoDB才啟用預讀取,預讀取下一個頁中所有的頁。

5.7、hash索引
innodb存儲引擎中自適應hash索引使用的是散列表(hash table)的數據結構。但是散列表不只存在于自適應hash中,每個數據庫中都存在,用來加速內存中數據的查找。
5.7.1哈西表(hash table)
hash table又叫散列表,由直接尋址表改進而來。利用哈希函數解決了直接尋址遇到的問題,同時又使用鏈接發解決了碰撞問題。
5.7.2自適應哈西索引
它是數據庫系統自己創建并使用的,DBA本身并不能對其進行干預。需要注意的是,哈希索引只能用來搜素等值的查詢,對于其它的查找是不能使用哈希索引的。我們只能通過參數innodb_adaptive_hash_index來禁用或啟動此特性。

鎖是區別文件系統和數據庫系統的一個關鍵特性。

6.1、什么是鎖?
鎖是用來管理對共享文件的并發訪問。innodb會在行級別上對數據庫上鎖。不過innodb存儲引擎會在數據庫內部其他多個地方使用鎖,從而允許對不同資源提供并發訪問。例如操作緩沖池中的LRU列表,刪除,添加,移動LRU列表中的元素,為了保證一致性,必須有鎖的介入。

6.2、innodb存儲引擎中的鎖
6.2.1、鎖的類型
S lock 共享鎖允許事務讀一行數據。X lock 排它鎖允許事務刪除或者更新一條數據。IS lock 意向共享鎖事務想要獲得一個表中某幾行的共享鎖。IX lock 意向拍他所事務想要獲得一個表中某幾行的排它鎖。因為InnoDB存儲引擎支持的是行級別的鎖,所以意向鎖其實不會阻塞除全表掃描以外的任何請求。
6.2.2、一致性的非鎖定讀操作
一致性非鎖定讀(consistent nonlocking read)是指innodb通過多版本控制(multi versioning)的方式來讀取當前執行時間數據庫中行的數據。非鎖定讀的機制大大提高了數據讀取的并發性,在InnoDB引擎中為默認的讀取方法,即讀取不會占用和等代表上的鎖。多版本控制是通過快照實現的,快照數據其實就是當前數據之前的歷史版本,可能有多個版本。這種技術稱為行多版本技術,由此帶來的并發控制叫做多半本并發控制(multi version concurrency control,MVCC).在Read Committed和Repeatable Read(innodb默認的事務隔離級別)下,innodb存儲引擎使用非鎖定的一致性讀。但是對于快照數據的定義卻不同。在Read Commited級別,對于快照數據,非一致性讀總是讀取被鎖定行的最新一份快照。在Repeatable級別下,對于快照數據,非一致性讀總是讀取事務開始時的行數據版本。
6.2.3、SELECT…FOR UPDATE &SELECT…LOCK IN SHARE MODE
SELECT…FOR UPDATE 可以獲得一個X鎖。SELECT…LOCK IN SHARE MODE 可以獲得一個S鎖。注意上述操作時必須使用顯示提交方式,即加上begin,start transaction或者set autocommit = 0。
6.2.4、自增長和鎖
對于含有子增長計數器的表進行插入時,會執行”SELECT MAX(auto_inc_col) FROM t FOR UPDATE;”插入操作會更具這個自增長的計數器值加1賦予自增長列。這個實現方式叫做AUTO-INC Locking。這是一種特殊的鎖,為了提高并發,它不會在事務執行完才釋放,只是在語句執行后立即釋放。從mysql-5.1.22版本開始,innodb引擎提供了一種輕量級互斥量的自增長實現機制,這種機制大大提高了子增長值插入的性能。并且mysql-5.1.22開始,innodb引擎提供了一個參數innodb_autoinc_lock_mode,默認的值為1。在討論新的增長方式之前我們需要對自增長實現方式分類:1.INSERT-LIKE:指所有的插入語句,比如 INSERT、REPLACE、INSERT…SELECT、REPLACE…SELECT,LOAD DATA等。2.Simple insert:指在插入前就能確定插入行數的語句,包括INSERT、REPLACE,不包含INSERT…ON DUPLICATE KEY UPDATE這類語句。3.Bulk inserts:指在插入前不能確定得到插入行的語句。如INSERT…SELECT,REPLACE…SELECT,LOAD DATA.4.Mixed-mode inserts:指其中一部分是子增長的,有一部分是確定的。現在有SIMPLE INSERT、BULK INSERTS、MIXED-MODE INSERTS三種類型的INSERT語句,有AUTO-inc locking(最早的)和輕量級互斥量的自增長兩種auto—increment鎖。1.innodb_autoinc_lock_mode=0 5.1.22之前的方式,也就是所有類型的insert都用AUTO-inc locking。2.innodb_autoinc_lock_mode=1 這個參數是5.1.22之后出現的也是之后的默認值,對于SIMPLE INSERT,使用輕量級互斥量的鎖,對于BULK INSERT,使用AUTO-inc locking。3.innodb_autoinc_lock_mode=2 指不管什么情況都使用輕量級互斥的鎖,效率最高,但是復制只能使用row-basereplication,因為statement-base replication會出現問題。另外就是innodb和myisam的一個區別,innodb下,自增長必須是索引,而且必須是索引的第一個列,不然會報錯,myisam不會出現這個問題。
6.2.5、外鍵和鎖
外鍵主要用于引用完整性的約束檢查。innodb中,對于一個外鍵列,如果沒有顯示的對這個列加索引,innodb就自動的對其加一個索引。

6.3、鎖的算法
1.Record Lock,單行記錄上的鎖,鎖住索引記錄。2.GapLock,間隙鎖能鎖定一個范圍,但不包括記錄本身如 < 6 時,依然可以插入6。3.Next-KeyLock:Gap Lock + Record Lock,鎖定一個范圍并且鎖定記錄本身,如 < 6,插入6時會被阻塞。在REPEATABLE READ模式下 Next-KeyLock算法是默認的行記錄鎖定算法。

6.4、鎖問題
本來鎖問題會導致的是更新丟失、幻讀、臟讀、不可重復讀,但是innodb作者卻只寫出了三種問題,可能是幻讀通過innodb Next-key Lock解決了,作者就沒有提及。這幾個鎖問題對應事務隔離的4個安全級別:READ UNCOMMITTED(事務隔離最低的級別,有事務隔離就能解決更新丟失,但是存在臟讀的問題)。READ COMMITED(ORACLE和SQL SERVER默認的隔離級別,解決了臟讀,但是一個事務多次讀取的內容不同,出現了不可重復讀的問題)。READ REPEATABLE(可重復讀,innodb引擎的默認事務隔離級別,解決了不可重復讀的問題,但是產生了幻讀,innodb通過Next-key lock解決了幻讀)。SERIALIZABLE(可串行話,通過強制事務排序解決幻讀問題,會降低性能)總的看來innodb默認的 READ REPEATABLE是非常棒的。

6.5、阻塞
innodb中需要其他事務的鎖釋放它鎖占用的資源,這個時候就會發生鎖等待,這就是阻塞。innodb引擎有兩個相關參數:innodb_lock_wait_timeout 用來設定等待的時間,默認是50秒,這是一個動態參數,可以隨時調整;innodb_rollback_on_timeout用來設定是否在等待超時時對進行中的事務進行回滾操作,默認是OFF,代表不回滾,這是一個靜態參數。

6.6、死鎖
死鎖會產生阻塞,所以可以通過6.5的參數,讓超時的阻塞回滾。還有就是開發的時候,每個事務對表,字段,行的操作,都是順序的,這樣可以很大程度上避免死鎖。

大家喜歡可以訪問我的個人網站:http://www.yingminxing.com
如有疑問,歡迎溝通交流:QQ:370399195, 微信:yingminxing1988

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

推薦閱讀更多精彩內容