MySQL事務(wù)隔離級別和實(shí)現(xiàn)

[TOC]

MySQL 事務(wù)

本文所說的 MySQL 事務(wù)都是指在 InnoDB 引擎下裙犹,MyISAM 引擎是不支持事務(wù)的轻庆。

數(shù)據(jù)庫事務(wù)指的是一組數(shù)據(jù)操作,事務(wù)內(nèi)的操作要么就是全部成功,要么就是全部失敗布隔,什么都不做,其實(shí)不是沒做稼虎,是可能做了一部分但是只要有一步失敗衅檀,就要回滾所有操作,有點(diǎn)一不做二不休的意思霎俩。

事務(wù)具有原子性(Atomicity)哀军、一致性(Consistency)沉眶、隔離性(Isolation)、持久性(Durability)四個(gè)特性杉适,簡稱 ACID谎倔,缺一不可。今天要說的就是隔離性猿推。

臟讀

臟讀指的是讀到了其他事務(wù)未提交的數(shù)據(jù)片习,未提交意味著這些數(shù)據(jù)可能會(huì)回滾,也就是可能最終不會(huì)存到數(shù)據(jù)庫中蹬叭,也就是不存在的數(shù)據(jù)藕咏。讀到了并一定最終存在的數(shù)據(jù),這就是臟讀具垫。

可重復(fù)讀

可重復(fù)讀指的是在一個(gè)事務(wù)內(nèi)侈离,最開始讀到的數(shù)據(jù)和事務(wù)結(jié)束前的任意時(shí)刻讀到的同一批數(shù)據(jù)都是一致的。通常針對數(shù)據(jù)更新(UPDATE)操作筝蚕。

不可重復(fù)讀

對比可重復(fù)讀卦碾,不可重復(fù)讀指的是在同一事務(wù)內(nèi),不同的時(shí)刻讀到的同一批數(shù)據(jù)可能是不一樣的起宽,可能會(huì)受到其他事務(wù)的影響洲胖,比如其他事務(wù)改了這批數(shù)據(jù)并提交了。通常針對數(shù)據(jù)更新(UPDATE)操作坯沪。

幻讀

幻讀是針對數(shù)據(jù)插入(INSERT)操作來說的绿映。假設(shè)事務(wù)A對某些行的內(nèi)容作了更改,但是還未提交腐晾,此時(shí)事務(wù)B插入了與事務(wù)A更改前的記錄相同的記錄行叉弦,并且在事務(wù)A提交之前先提交了,而這時(shí)藻糖,在事務(wù)A中查詢淹冰,會(huì)發(fā)現(xiàn)好像剛剛的更改對于某些數(shù)據(jù)未起作用,但其實(shí)是事務(wù)B剛插入進(jìn)來的巨柒,讓用戶感覺很魔幻樱拴,感覺出現(xiàn)了幻覺,這就叫幻讀洋满。

事務(wù)隔離級別

SQL 標(biāo)準(zhǔn)定義了四種隔離級別晶乔,MySQL 全都支持。這四種隔離級別分別是:

  1. 讀未提交(READ UNCOMMITTED)
  2. 讀提交 (READ COMMITTED)
  3. 可重復(fù)讀 (REPEATABLE READ)
  4. 串行化 (SERIALIZABLE)

從上往下牺勾,隔離強(qiáng)度逐漸增強(qiáng)正罢,性能逐漸變差。采用哪種隔離級別要根據(jù)系統(tǒng)需求權(quán)衡決定驻民,其中翻具,可重復(fù)讀是 MySQL 的默認(rèn)級別袱饭。

事務(wù)隔離其實(shí)就是為了解決上面提到的臟讀、不可重復(fù)讀呛占、幻讀這幾個(gè)問題濒募,下面展示了 4 種隔離級別對這三個(gè)問題的解決程度技俐。

image.png

下面,我們來一一分析這 4 種隔離級別到底是怎么個(gè)意思。

如何設(shè)置隔離級別

我們可以通過以下語句查看當(dāng)前數(shù)據(jù)庫的隔離級別术徊,通過下面語句可以看出我使用的 MySQL 的隔離級別是 REPEATABLE-READ丁侄,也就是可重復(fù)讀徐绑,這也是 MySQL 的默認(rèn)級別伐割。

# 查看事務(wù)隔離級別 5.7.20 之后
show variables like 'transaction_isolation';
SELECT @@transaction_isolation

# 5.7.20 之后
SELECT @@tx_isolation
show variables like 'tx_isolation'

+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+

稍后,我們要修改數(shù)據(jù)庫的隔離級別笙隙,所以先了解一下具體的修改方式洪灯。

修改隔離級別的語句是:set [作用域] transaction isolation level [事務(wù)隔離級別], SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}竟痰。

其中作用于可以是 SESSION 或者 GLOBAL签钩,GLOBAL 是全局的,而 SESSION 只針對當(dāng)前回話窗口坏快。隔離級別是 {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE} 這四種铅檩,不區(qū)分大小寫。

比如下面這個(gè)語句的意思是設(shè)置全局隔離級別為讀提交級別莽鸿。

mysql> set global transaction isolation level read committed;

MySQL 中執(zhí)行事務(wù)

事務(wù)的執(zhí)行過程如下昧旨,以 begin 或者 start transaction 開始,然后執(zhí)行一系列操作祥得,最后要執(zhí)行 commit 操作兔沃,事務(wù)才算結(jié)束。當(dāng)然级及,如果進(jìn)行回滾操作(rollback)乒疏,事務(wù)也會(huì)結(jié)束。

image.png

需要注意的是创千,begin 命令并不代表事務(wù)的開始缰雇,事務(wù)開始于 begin 命令之后的第一條語句執(zhí)行的時(shí)候入偷。例如下面示例中追驴,select * from xxx 才是事務(wù)的開始,

begin;
select * from xxx; 
commit; -- 或者 rollback;

另外疏之,通過以下語句可以查詢當(dāng)前有多少事務(wù)正在運(yùn)行殿雪。

select * from information_schema.innodb_trx;

好了,重點(diǎn)來了锋爪,開始分析這幾個(gè)隔離級別了

接下來我會(huì)用一張表來做一下驗(yàn)證丙曙,表結(jié)構(gòu)簡單如下:

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(30) DEFAULT NULL,
  `age` tinyint(4) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8

初始只有一條記錄:

mysql> SELECT * FROM user;
+----+-----------------+------+
| id | name            | age  |
+----+-----------------+------+
|  1 | 古時(shí)的風(fēng)箏        |    1 |
+----+-----------------+------+

讀未提交

MySQL 事務(wù)隔離其實(shí)是依靠鎖來實(shí)現(xiàn)的爸业,加鎖自然會(huì)帶來性能的損失。而讀未提交隔離級別是不加鎖的亏镰,所以它的性能是最好的扯旷,沒有加鎖、解鎖帶來的性能開銷索抓。但有利就有弊钧忽,這基本上就相當(dāng)于裸奔啊,所以它連臟讀的問題都沒辦法解決逼肯。

任何事務(wù)對數(shù)據(jù)的修改都會(huì)第一時(shí)間暴露給其他事務(wù)耸黑,即使事務(wù)還沒有提交。

下面來做個(gè)簡單實(shí)驗(yàn)驗(yàn)證一下篮幢,首先設(shè)置全局隔離級別為讀未提交大刊。

set global transaction isolation level read uncommitted;

設(shè)置完成后,只對之后新起的 session 才起作用三椿,對已經(jīng)啟動(dòng) session 無效缺菌。如果用 shell 客戶端那就要重新連接 MySQL,如果用 Navicat 那就要?jiǎng)?chuàng)建新的查詢窗口搜锰。

啟動(dòng)兩個(gè)事務(wù)男翰,分別為事務(wù)A和事務(wù)B,在事務(wù)A中使用 update 語句纽乱,修改 age 的值為10蛾绎,初始是1 ,在執(zhí)行完 update 語句之后鸦列,在事務(wù)B中查詢 user 表租冠,會(huì)看到 age 的值已經(jīng)是 10 了,這時(shí)候事務(wù)A還沒有提交薯嗤,而此時(shí)事務(wù)B有可能拿著已經(jīng)修改過的 age=10 去進(jìn)行其他操作了顽爹。在事務(wù)B進(jìn)行操作的過程中,很有可能事務(wù)A由于某些原因骆姐,進(jìn)行了事務(wù)回滾操作镜粤,那其實(shí)事務(wù)B得到的就是臟數(shù)據(jù)了,拿著臟數(shù)據(jù)去進(jìn)行其他的計(jì)算玻褪,那結(jié)果肯定也是有問題的肉渴。

順著時(shí)間軸往表示兩事務(wù)中操作的執(zhí)行順序,重點(diǎn)看圖中 age 字段的值带射。


image.png

讀未提交同规,其實(shí)就是可以讀到其他事務(wù)未提交的數(shù)據(jù),但沒有辦法保證你讀到的數(shù)據(jù)最終一定是提交后的數(shù)據(jù),如果中間發(fā)生回滾券勺,那就會(huì)出現(xiàn)臟數(shù)據(jù)問題绪钥,讀未提交沒辦法解決臟數(shù)據(jù)問題。更別提可重復(fù)讀和幻讀了关炼,想都不要想程腹。

讀提交

既然讀未提交沒辦法解決臟數(shù)據(jù)問題,那么就有了讀提交儒拂。讀提交就是一個(gè)事務(wù)只能讀到其他事務(wù)已經(jīng)提交過的數(shù)據(jù)跪楞,也就是其他事務(wù)調(diào)用 commit 命令之后的數(shù)據(jù)。那臟數(shù)據(jù)問題迎刃而解了侣灶。

讀提交事務(wù)隔離級別是大多數(shù)流行數(shù)據(jù)庫的默認(rèn)事務(wù)隔離界別甸祭,比如 Oracle,但是不是 MySQL 的默認(rèn)隔離界別褥影。

我們繼續(xù)來做一下驗(yàn)證池户,首先把事務(wù)隔離級別改為讀提交級別。

set global transaction isolation level read committed;

之后需要重新打開新的 session 窗口凡怎,也就是新的 shell 窗口才可以校焦。

同樣開啟事務(wù)A和事務(wù)B兩個(gè)事務(wù),在事務(wù)A中使用 update 語句將 id=1 的記錄行 age 字段改為 10统倒。此時(shí)寨典,在事務(wù)B中使用 select 語句進(jìn)行查詢,我們發(fā)現(xiàn)在事務(wù)A提交之前房匆,事務(wù)B中查詢到的記錄 age 一直是1耸成,直到事務(wù)A提交,此時(shí)在事務(wù)B中 select 查詢浴鸿,發(fā)現(xiàn) age 的值已經(jīng)是 10 了井氢。

這就出現(xiàn)了一個(gè)問題,在同一事務(wù)中(本例中的事務(wù)B)岳链,事務(wù)的不同時(shí)刻同樣的查詢條件花竞,查詢出來的記錄內(nèi)容是不一樣的,事務(wù)A的提交影響了事務(wù)B的查詢結(jié)果掸哑,這就是不可重復(fù)讀约急,也就是讀提交隔離級別。


image.png

每個(gè) select 語句都有自己的一份快照苗分,而不是一個(gè)事務(wù)一份厌蔽,所以在不同的時(shí)刻,查詢出來的數(shù)據(jù)可能是不一致的俭嘁。

讀提交解決了臟讀的問題躺枕,但是無法做到可重復(fù)讀服猪,也沒辦法解決幻讀供填。

可重復(fù)讀

可重復(fù)是對比不可重復(fù)而言的拐云,上面說不可重復(fù)讀是指同一事物不同時(shí)刻讀到的數(shù)據(jù)值可能不一致。而可重復(fù)讀是指近她,事務(wù)不會(huì)讀到其他事務(wù)對已有數(shù)據(jù)的修改叉瘩,及時(shí)其他事務(wù)已提交,也就是說粘捎,事務(wù)開始時(shí)讀到的已有數(shù)據(jù)是什么薇缅,在事務(wù)提交前的任意時(shí)刻,這些數(shù)據(jù)的值都是一樣的攒磨。但是泳桦,對于其他事務(wù)新插入的數(shù)據(jù)是可以讀到的,這也就引發(fā)了幻讀問題娩缰。

同樣的灸撰,需改全局隔離級別為可重復(fù)讀級別。

set global transaction isolation level repeatable read;

在這個(gè)隔離級別下拼坎,啟動(dòng)兩個(gè)事務(wù)浮毯,兩個(gè)事務(wù)同時(shí)開啟。

首先看一下可重復(fù)讀的效果泰鸡,事務(wù)A啟動(dòng)后修改了數(shù)據(jù)债蓝,并且在事務(wù)B之前提交,事務(wù)B在事務(wù)開始和事務(wù)A提交之后兩個(gè)時(shí)間節(jié)點(diǎn)都讀取的數(shù)據(jù)相同盛龄,已經(jīng)可以看出可重復(fù)讀的效果饰迹。


image.png

可重復(fù)讀做到了,這只是針對已有行的更改操作有效余舶,但是對于新插入的行記錄蹦锋,就沒這么幸運(yùn)了,幻讀就這么產(chǎn)生了欧芽。我們看一下這個(gè)過程:

事務(wù)A開始后莉掂,執(zhí)行 update 操作,將 age = 1 的記錄的 name 改為“風(fēng)箏2號”千扔;

事務(wù)B開始后憎妙,在事務(wù)執(zhí)行完 update 后,執(zhí)行 insert 操作曲楚,插入記錄 age =1厘唾,name = 古時(shí)的風(fēng)箏,這和事務(wù)A修改的那條記錄值相同龙誊,然后提交抚垃。

事務(wù)B提交后,事務(wù)A中執(zhí)行 select,查詢 age=1 的數(shù)據(jù)鹤树,這時(shí)铣焊,會(huì)發(fā)現(xiàn)多了一行,并且發(fā)現(xiàn)還有一條 name = 古時(shí)的風(fēng)箏罕伯,age = 1 的記錄曲伊,這其實(shí)就是事務(wù)B剛剛插入的,這就是幻讀追他。

image.png

要說明的是坟募,當(dāng)你在 MySQL 中測試幻讀的時(shí)候,并不會(huì)出現(xiàn)上圖的結(jié)果邑狸,幻讀并沒有發(fā)生懈糯,MySQL 的可重復(fù)讀隔離級別其實(shí)解決了幻讀問題,這會(huì)在后面的內(nèi)容說明

串行化

串行化是4種事務(wù)隔離級別中隔離效果最好的单雾,解決了臟讀昂利、可重復(fù)讀、幻讀的問題铁坎,但是效果最差蜂奸,它將事務(wù)的執(zhí)行變?yōu)轫樞驁?zhí)行,與其他三個(gè)隔離級別相比硬萍,它就相當(dāng)于單線程扩所,后一個(gè)事務(wù)的執(zhí)行必須等待前一個(gè)事務(wù)結(jié)束。

MySQL 中是如何實(shí)現(xiàn)事務(wù)隔離的

首先說讀未提交朴乖,它是性能最好祖屏,也可以說它是最野蠻的方式,因?yàn)樗鼔焊鶅壕筒患渔i买羞,所以根本談不上什么隔離效果袁勺,可以理解為沒有隔離。

再來說串行化畜普。讀的時(shí)候加共享鎖期丰,也就是其他事務(wù)可以并發(fā)讀,但是不能寫吃挑。寫的時(shí)候加排它鎖钝荡,其他事務(wù)不能并發(fā)寫也不能并發(fā)讀。

最后說讀提交和可重復(fù)讀舶衬。這兩種隔離級別是比較復(fù)雜的埠通,既要允許一定的并發(fā),又想要兼顧的解決問題逛犹。

實(shí)現(xiàn)可重復(fù)讀

為了解決不可重復(fù)讀端辱,或者為了實(shí)現(xiàn)可重復(fù)讀梁剔,MySQL 采用了 MVVC (多版本并發(fā)控制) 的方式。

我們在數(shù)據(jù)庫表中看到的一行記錄可能實(shí)際上有多個(gè)版本舞蔽,每個(gè)版本的記錄除了有數(shù)據(jù)本身外荣病,還要有一個(gè)表示版本的字段,記為 row trx_id喷鸽,而這個(gè)字段就是使其產(chǎn)生的事務(wù)的 id众雷,事務(wù) ID 記為 transaction id灸拍,它在事務(wù)開始的時(shí)候向事務(wù)系統(tǒng)申請做祝,按時(shí)間先后順序遞增。

image.png

按照上面這張圖理解鸡岗,一行記錄現(xiàn)在有 3 個(gè)版本混槐,每一個(gè)版本都記錄這使其產(chǎn)生的事務(wù) ID,比如事務(wù)A的transaction id 是100轩性,那么版本1的row trx_id 就是 100声登,同理版本2和版本3。

在上面介紹讀提交和可重復(fù)讀的時(shí)候都提到了一個(gè)詞揣苏,叫做快照悯嗓,學(xué)名叫做一致性視圖,這也是可重復(fù)讀和不可重復(fù)讀的關(guān)鍵卸察,可重復(fù)讀是在事務(wù)開始的時(shí)候生成一個(gè)當(dāng)前事務(wù)全局性的快照脯厨,而讀提交則是每次執(zhí)行語句的時(shí)候都重新生成一次快照。

對于一個(gè)快照來說坑质,它能夠讀到那些版本數(shù)據(jù)合武,要遵循以下規(guī)則:

  1. 當(dāng)前事務(wù)內(nèi)的更新,可以讀到涡扼;
  2. 版本未提交稼跳,不能讀到;
  3. 版本已提交吃沪,但是卻在快照創(chuàng)建后提交的汤善,不能讀到;
  4. 版本已提交票彪,且是在快照創(chuàng)建前提交的萎津,可以讀到;

如果僅僅是begin后 是可以讀到的 begin還沒有開啟事務(wù)

利用上面的規(guī)則抹镊,再返回去套用到讀提交和可重復(fù)讀的那兩張圖上就很清晰了锉屈。還是要強(qiáng)調(diào),兩者主要的區(qū)別就是在快照的創(chuàng)建上垮耳,可重復(fù)讀僅在事務(wù)開始是創(chuàng)建一次颈渊,而讀提交每次執(zhí)行語句的時(shí)候都要重新創(chuàng)建一次遂黍。

并發(fā)寫問題

假設(shè)事務(wù)A執(zhí)行 update 操作, update 的時(shí)候要對所修改的行加行鎖俊嗽,這個(gè)行鎖會(huì)在提交之后才釋放雾家。而在事務(wù)A提交之前,事務(wù)B也想 update 這行數(shù)據(jù)绍豁,于是申請行鎖芯咧,但是由于已經(jīng)被事務(wù)A占有,事務(wù)B是申請不到的竹揍,此時(shí)敬飒,事務(wù)B就會(huì)一直處于等待狀態(tài),直到事務(wù)A提交芬位,事務(wù)B才能繼續(xù)執(zhí)行无拗,如果事務(wù)A的時(shí)間太長,那么事務(wù)B很有可能出現(xiàn)超時(shí)異常

image.png

加鎖的過程要分有索引和無索引兩種情況昧碉,比如下面這條語句

update user set age=11 where id = 1

id 是這張表的主鍵英染,是有索引的情況,那么 MySQL 直接就在索引數(shù)中找到了這行數(shù)據(jù)被饿,然后干凈利落的加上行鎖就可以了四康。

而下面這條語句

update user set age=11 where age=10

會(huì)加表鎖?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末狭握,一起剝皮案震驚了整個(gè)濱河市闪金,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌哥牍,老刑警劉巖毕泌,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異嗅辣,居然都是意外死亡撼泛,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門澡谭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來愿题,“玉大人,你說我怎么就攤上這事蛙奖∨诵铮” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵雁仲,是天一觀的道長仔夺。 經(jīng)常有香客問我,道長攒砖,這世上最難降的妖魔是什么缸兔? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任日裙,我火速辦了婚禮,結(jié)果婚禮上惰蜜,老公的妹妹穿的比我還像新娘昂拂。我一直安慰自己,他們只是感情好抛猖,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布格侯。 她就那樣靜靜地躺著,像睡著了一般财著。 火紅的嫁衣襯著肌膚如雪联四。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天瓢宦,我揣著相機(jī)與錄音碎连,去河邊找鬼灰羽。 笑死驮履,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的廉嚼。 我是一名探鬼主播玫镐,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼怠噪!你這毒婦竟也來了恐似?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤傍念,失蹤者是張志新(化名)和其女友劉穎矫夷,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體憋槐,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡双藕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了阳仔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片忧陪。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖近范,靈堂內(nèi)的尸體忽然破棺而出嘶摊,到底是詐尸還是另有隱情,我是刑警寧澤评矩,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布叶堆,位于F島的核電站,受9級特大地震影響斥杜,放射性物質(zhì)發(fā)生泄漏虱颗。R本人自食惡果不足惜俯萌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望上枕。 院中可真熱鬧咐熙,春花似錦、人聲如沸辨萍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锈玉。三九已至爪飘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拉背,已是汗流浹背师崎。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留椅棺,地道東北人犁罩。 一個(gè)月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像两疚,于是被迫代替她去往敵國和親床估。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344