Spring 事務(wù)介紹(一)之 數(shù)據(jù)庫的事務(wù)的基本特性

spring.png

Spring 事務(wù)介紹(一)之 數(shù)據(jù)庫的事務(wù)的基本特性

數(shù)據(jù)庫的事務(wù)的基本特性

事務(wù)是區(qū)分文件存儲(chǔ)系統(tǒng)和Nosql數(shù)據(jù)庫重要特性之一栈顷,其存在的意義是為了保證即時(shí)在并發(fā)的情況下往堡,也能正確的執(zhí)行crud操作咐蝇,怎樣才能算是正確的爆侣?這時(shí)提出了事務(wù)需要保證的四個(gè)特性ACID:

  • A:原子性(atomicity)

事務(wù)中各項(xiàng)操作只损,要么全做要么不做搔涝,任何一項(xiàng)操作的失敗都會(huì)導(dǎo)致整個(gè)事務(wù)的失斈卦睢;

  • C:一致性(consistency)

事務(wù)結(jié)束后系統(tǒng)狀態(tài)是一致的见芹;

  • I:隔離性(isolation)

并發(fā)執(zhí)行的事務(wù)彼此無法看到對(duì)方的中間狀態(tài)剂娄;

  • D:持久性(durability)

事務(wù)完成后所做的改動(dòng)都會(huì)被持久化,即使發(fā)生災(zāi)難性的失斝骸阅懦;

在高并發(fā)的情況下,要完全保證其ACID是非常困難的徘铝,除非把所有的事務(wù)串行化執(zhí)行耳胎,但是后果就是性能大打折扣。很多時(shí)候我們有些業(yè)務(wù)對(duì)事務(wù)的要求是不一樣的惕它,所有數(shù)據(jù)庫中設(shè)計(jì)了四種隔離級(jí)別怕午,供用戶基于業(yè)務(wù)進(jìn)行選擇。

隔離級(jí)別 臟讀(Dirty Read) 不可重復(fù)讀(NonRepeatable Read) 幻讀(Phantom read)
讀未提交(Read Uncommitted) 可能 可能 可能
讀已提交(Read Committed) 不可能 可能 可能
可重復(fù)讀(Repeatable Read) 不可能 不可能 可能
可串行化(Serializable) 不可能 不可能 不可能
  • 臟讀:

一個(gè)事務(wù)讀取到另一個(gè)事務(wù)未提交的更新數(shù)據(jù)淹魄。

  • 不可重復(fù)讀:

在同一事務(wù)中郁惜,多次讀取同一數(shù)據(jù)返回的結(jié)果有所不同,換句話說甲锡,后面讀取可以讀到另一個(gè)事務(wù)已提交的更新數(shù)據(jù)兆蕉,相反羽戒,“可重復(fù)讀”在同一事務(wù)中多次讀取數(shù)據(jù)時(shí),能夠保證所讀數(shù)據(jù)一樣虎韵,也就是后續(xù)讀取不能讀取到另一事務(wù)所提交的更新數(shù)據(jù)易稠。

  • 幻讀

查詢表中一條數(shù)據(jù)如果不存在就插入一條,并發(fā)的時(shí)候卻發(fā)現(xiàn)包蓝,里面居然有兩條相同的數(shù)據(jù)驶社,導(dǎo)致插入失敗,這就是幻讀的問題养晋。

幻讀在mysql中衬吆,在默認(rèn)的可重復(fù)讀的隔離級(jí)別下梁钾,由mvcc(多版本并發(fā)控制)引起的绳泉,其中間隙鎖可以避免幻讀的問題,但是間隙鎖會(huì)引起鎖等待問題姆泻。

MVCC:

MVCC是通過保存數(shù)據(jù)在某個(gè)時(shí)間點(diǎn)的快照來實(shí)現(xiàn)的. 不同存儲(chǔ)引擎的MVCC. 不同存儲(chǔ)引擎的MVCC實(shí)現(xiàn)是不同的,典型的有樂觀并發(fā)控制和悲觀并發(fā)控制.

間隙鎖:

當(dāng)我們用范圍條件而不是相等條件檢索數(shù)據(jù)零酪,并請(qǐng)求共享或排他鎖時(shí),InnoDB會(huì)給符合條件的已有數(shù)據(jù)記錄的索引項(xiàng)加鎖拇勃;對(duì)于鍵值在條件范圍內(nèi)但并不存在的記錄四苇,叫做“間隙(GAP)”,InnoDB也會(huì)對(duì)這個(gè)“間隙”加鎖方咆,這種鎖機(jī)制就是所謂的間隙鎖(Next-Key鎖)月腋。

幾種隔離級(jí)別的代碼demo:

ReadUncommittedTest.java

package com.demo.spring;

import java.sql.*;

/**
 * com.demo.spring
 *
 * @author Zyy
 * @date 2019/2/13 22:55
 *
 * Connection.TRANSACTION_READ_UNCOMMITTED
 * 允許讀取未提交事務(wù),會(huì)出現(xiàn)臟讀瓣赂,不可重復(fù)讀榆骚,幻讀的問題
 */
public class ReadUncommittedTest {
    private static String jdbcUrl = "jdbc:mysql://192.168.5.104:3306/spring";
    private static String userName = "root";
    private static String password = "root";
    private static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException, SQLException, ClassNotFoundException {
        Thread t1 = run(new Runnable() {
            public void run() {
                insert("001", "test", 100);
            }
        });

        Thread t2 = run(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(500);
                    Connection conn = openConnection();
                    // 將參數(shù)升級(jí)成 Connection.TRANSACTION_READ_COMMITTED 即可解決臟讀的問題
                    conn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
                    select("test", conn);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        t1.join();

    }

    public static Thread run(Runnable runnable) {
        Thread thread = new Thread(runnable);
        thread.start();
        return thread;
    }

    public static Connection openConnection() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection conn = DriverManager.getConnection(jdbcUrl, userName, password);
        return conn;
    }

    static {
        try {
            Connection connection = openConnection();
            deleteAccount(connection);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void insert(String accountName, String name, int money) {
        try {
            Connection conn = openConnection();
            PreparedStatement prepare = conn.
                    prepareStatement("insert into account (accountname,user,money) values (?,?,?)");
            prepare.setString(1, accountName);
            prepare.setString(2, name);
            prepare.setInt(3, money);
            prepare.executeUpdate();
            System.out.println("執(zhí)行插入成功");
            conn.close();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public static void select(String name, Connection conn) {
        try {
            PreparedStatement prepare = conn.
                    prepareStatement("select * from account where user = ?");
            prepare.setString(1, name);
            ResultSet resultSet = prepare.executeQuery();
            System.out.println("執(zhí)行查詢");
            while (resultSet.next()) {
                for (int i = 1; i <= 4; i++) {
                    System.out.print(resultSet.getString(i) + " ");
                }
                System.out.println();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public static void deleteAccount(Connection conn) {
        try {
            PreparedStatement prepare = conn.prepareStatement("delete from account");
            prepare.executeUpdate();
            System.out.println("執(zhí)行刪除");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

執(zhí)行結(jié)果:

執(zhí)行插入成功
執(zhí)行查詢
141 001 test 100 

出現(xiàn)臟讀問題,讀取到未提交的插入數(shù)據(jù)煌集。

ReadCommittedTest.java

package com.demo.spring;

import java.sql.*;

/**
 * com.demo.spring
 *
 * @author Zyy
 * @date 2019/2/13 22:32
 *
 * Connection.TRANSACTION_READ_COMMITTED
 * 允許讀取已提交事務(wù)妓肢,會(huì)出現(xiàn)不可重復(fù)讀,幻讀的問題
 */
public class ReadCommittedTest {
    private static String jdbcUrl = "jdbc:mysql://192.168.5.104:3306/spring";
    private static String userName = "root";
    private static String password = "root";
    private static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = run(new Runnable() {
            public void run() {
                synchronized (lock) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    insert("001", "test", 100);
                }
            }
        });

        Thread t2 = run(new Runnable() {
            public void run() {
                try {
                    Connection connection = openConnection();
                    connection.setAutoCommit(false);
                    // 將參數(shù)升級(jí)成 Connection.TRANSACTION_REPEATABLE_READ 即可解決不可重復(fù)讀問題
                    connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
                    // 第一次讀取不到
                    select("test", connection);
                    // 釋放鎖
                    synchronized (lock) {
                        lock.notify();
                    }
                    // 第二次讀取到(數(shù)據(jù)不一至)
                    Thread.sleep(500);
                    select("test", connection);
                    connection.close();
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (SQLException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.join();
        t2.join();

    }

    public static Thread run(Runnable runnable) {
        Thread thread = new Thread(runnable);
        thread.start();
        return thread;
    }

    public static Connection openConnection() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection conn = DriverManager.getConnection(jdbcUrl, userName, password);
        return conn;
    }

    static {
        try {
            Connection connection = openConnection();
            //deleteAccount(connection);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void insert(String accountName, String name, int money) {
        try {
            Connection conn = openConnection();
            PreparedStatement prepare = conn.
                    prepareStatement("insert into account (accountname,user,money) values (?,?,?)");
            prepare.setString(1, accountName);
            prepare.setString(2, name);
            prepare.setInt(3, money);
            prepare.executeUpdate();
            System.out.println("執(zhí)行插入成功");
            conn.close();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public static void select(String name, Connection conn) {
        try {
            PreparedStatement prepare = conn.
                    prepareStatement("select * from account where user = ?");
            prepare.setString(1, name);
            ResultSet resultSet = prepare.executeQuery();
            System.out.println("執(zhí)行查詢");
            while (resultSet.next()) {
                for (int i = 1; i <= 4; i++) {
                    System.out.print(resultSet.getString(i) + " ");
                }
                System.out.println();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public static void deleteAccount(Connection conn) {
        try {
            PreparedStatement prepare = conn.prepareStatement("delete from account");
            prepare.executeUpdate();
            System.out.println("執(zhí)行刪除");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

執(zhí)行結(jié)果

執(zhí)行查詢
141 001 test 100 
142 001 test 100 
143 001 test 100 
執(zhí)行插入成功
執(zhí)行查詢
141 001 test 100 
142 001 test 100 
143 001 test 100 
144 001 test 100 

出現(xiàn)不可重復(fù)讀的問題苫纤,兩次讀取結(jié)果不一致碉钠。

ReadRepeatableTest.java

package com.demo.spring;

import java.sql.*;

/**
 * com.demo.spring
 *
 * @author Zyy
 * @date 2019/2/13 23:15
 *
 * Connection.TRANSACTION_REPEATABLE_READ
 * 可重復(fù)讀 ,在一個(gè)事務(wù)中同一SQL語句 無論執(zhí)行多少次都會(huì)得到相同的結(jié)果
 * 會(huì)出現(xiàn)幻讀的問題
 */
public class ReadRepeatableTest {

    private static String jdbcUrl = "jdbc:mysql://192.168.5.104:3306/spring";
    private static String userName = "root";
    private static String password = "root";
    private static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException, SQLException, ClassNotFoundException {
        Thread t1 = run(new Runnable() {
            public void run() {
                try {
                    synchronized (lock) {
                        lock.wait();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                update("test");
            }
        });

        Thread t2 = run(new Runnable() {
            public void run() {
                try {
                    Connection conn = openConnection();
                    conn.setAutoCommit(false);
                    conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
                    // 第一次讀取 讀取到的數(shù)據(jù)為未修改前的數(shù)據(jù)
                    select("test", conn);
                    // 釋放鎖
                    synchronized (lock) {
                        lock.notify();
                    }
                    // 第二次讀取 TRANSACTION_REPEATABLE_READ級(jí)別,讀取到的數(shù)據(jù)也為未修改前的數(shù)據(jù) 兩次讀取數(shù)據(jù)一至
                    // 設(shè)置id為主鍵 如果此時(shí)t1做插入(id=1)卷拘,t2按主鍵查詢(id=1)
                    // 因?yàn)榇藭r(shí)為TRANSACTION_REPEATABLE_READ級(jí)別 喊废,所以查詢?yōu)榭眨缓筮M(jìn)行插入(id=1)
                    // 此時(shí)會(huì)出現(xiàn)主鍵沖突的異常栗弟,這種情況為幻讀操禀,有興趣的可以嘗試一下
                    Thread.sleep(500);
                    select("test", conn);
                    conn.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        t1.join();

    }

    public static Thread run(Runnable runnable) {
        Thread thread = new Thread(runnable);
        thread.start();
        return thread;
    }

    public static Connection openConnection() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection conn = DriverManager.getConnection(jdbcUrl, userName, password);
        return conn;
    }

    static {
        try {
            Connection connection = openConnection();
            //deleteAccount(connection);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void insert(String accountName, String name, int money) {
        try {
            Connection conn = openConnection();
            PreparedStatement prepare = conn.
                    prepareStatement("insert into account (accountname,user,money) values (?,?,?)");
            prepare.setString(1, accountName);
            prepare.setString(2, name);
            prepare.setInt(3, money);
            prepare.executeUpdate();
            System.out.println("執(zhí)行插入成功");
            conn.close();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public static void deleteAccount(Connection conn) {
        try {
            PreparedStatement prepare = conn.prepareStatement("delete from account");
            prepare.executeUpdate();
            System.out.println("執(zhí)行刪除成功");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public static void update(String user) {
        try {
            Connection conn = openConnection();
            PreparedStatement prepare = conn.
                    prepareStatement("update account set money = money + 1 where user = ?");
            prepare.setString(1, user);
            prepare.executeUpdate();
            conn.close();
            System.out.println("執(zhí)行修改成功");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public static void select(String name, Connection conn) {
        try {
            PreparedStatement prepare = conn.
                    prepareStatement("select * from account where user = ?");
            prepare.setString(1, name);
            ResultSet resultSet = prepare.executeQuery();
            System.out.println("執(zhí)行查詢");
            while (resultSet.next()) {
                for (int i = 1; i <= 4; i++) {
                    System.out.print(resultSet.getString(i) + " ");
                }
                System.out.println();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

執(zhí)行結(jié)果:

執(zhí)行查詢
141 001 test 100 
142 001 test 100 
143 001 test 100 
144 001 test 100 
執(zhí)行修改成功
執(zhí)行查詢
141 001 test 100 
142 001 test 100 
143 001 test 100 
144 001 test 100 

兩次查詢結(jié)果一致,已解決了不可重復(fù)讀的問題横腿,可是會(huì)出現(xiàn)幻讀的問題颓屑。

幻讀場景描述:

設(shè)置id為主鍵斤寂,在兩個(gè)同時(shí)進(jìn)行的事務(wù)中,如果此時(shí)事務(wù)t1做插入(id=1)揪惦,事務(wù)t2按主鍵查詢(id=1)因?yàn)榇藭r(shí)為TRANSACTION_REPEATABLE_READ級(jí)別 遍搞,所以查詢?yōu)榭眨缓筮M(jìn)行插入(id=1)
此時(shí)會(huì)出現(xiàn)主鍵沖突的異常器腋,這種情況主要是由MVCC導(dǎo)致的溪猿,t2查詢的數(shù)據(jù)因?yàn)闆]有改動(dòng)所以是之前保留的查詢數(shù)據(jù),為快照版本纫塌,但實(shí)際上數(shù)據(jù)庫已經(jīng)新增了一條诊县,此時(shí)進(jìn)行插入,就拋出主鍵沖突異常了措左,明明查詢沒有數(shù)據(jù)然后進(jìn)行插入依痊,可是會(huì)出現(xiàn)插入失敗的情況,這種場景就是幻讀怎披。

數(shù)據(jù)庫默認(rèn)隔離級(jí)別:

Oracle:讀已提交(Read Committed)

Mysql:可重復(fù)讀(Repeatable Read)

另外胸嘁,mysql執(zhí)行一條查詢語句默認(rèn)是一個(gè)獨(dú)立的事務(wù),所以看上去效果與讀已提交一樣凉逛。

Mysql

查看當(dāng)前會(huì)話隔離級(jí)別

select @@tx_isolation;

查看系統(tǒng)當(dāng)前隔離級(jí)別

select @@global.tx_isolation;

設(shè)置當(dāng)前會(huì)話隔離級(jí)別

set session transaction isolatin level repeatable read;

設(shè)置系統(tǒng)當(dāng)前隔離級(jí)別

set global transaction isolation level repeatable read;

Oracle

查看系統(tǒng)默認(rèn)事務(wù)隔離級(jí)別,也是當(dāng)前會(huì)話隔離級(jí)別

#首先創(chuàng)建一個(gè)事務(wù)

declare
     trans_id Varchar2(100);
  begin
     trans_id := dbms_transaction.local_transaction_id( TRUE );
  end; 
  
#查看事務(wù)隔離級(jí)別

SELECT s.sid, s.serial#,

  CASE BITAND(t.flag, POWER(2, 28))
    WHEN 0 THEN 'READ COMMITTED'
    ELSE 'SERIALIZABLE'
  END AS isolation_level
FROM v$transaction t
JOIN v$session s ON t.addr = s.taddr AND s.sid = sys_context('USERENV', 'SID');

github : https://github.com/zhaoyybalabala/spring-test


歡迎留言交流:)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末性宏,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子状飞,更是在濱河造成了極大的恐慌毫胜,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,366評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诬辈,死亡現(xiàn)場離奇詭異酵使,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)自晰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門凝化,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人酬荞,你說我怎么就攤上這事搓劫。” “怎么了混巧?”我有些...
    開封第一講書人閱讀 165,689評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵枪向,是天一觀的道長。 經(jīng)常有香客問我咧党,道長秘蛔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,925評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮深员,結(jié)果婚禮上负蠕,老公的妹妹穿的比我還像新娘。我一直安慰自己倦畅,他們只是感情好遮糖,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著叠赐,像睡著了一般欲账。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上芭概,一...
    開封第一講書人閱讀 51,727評(píng)論 1 305
  • 那天赛不,我揣著相機(jī)與錄音,去河邊找鬼罢洲。 笑死踢故,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的奏路。 我是一名探鬼主播畴椰,決...
    沈念sama閱讀 40,447評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼臊诊,長吁一口氣:“原來是場噩夢啊……” “哼鸽粉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起抓艳,我...
    開封第一講書人閱讀 39,349評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤触机,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后玷或,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體儡首,經(jīng)...
    沈念sama閱讀 45,820評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評(píng)論 3 337
  • 正文 我和宋清朗相戀三年偏友,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蔬胯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,127評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡位他,死狀恐怖氛濒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鹅髓,我是刑警寧澤舞竿,帶...
    沈念sama閱讀 35,812評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站窿冯,受9級(jí)特大地震影響骗奖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評(píng)論 3 331
  • 文/蒙蒙 一执桌、第九天 我趴在偏房一處隱蔽的房頂上張望鄙皇。 院中可真熱鬧,春花似錦仰挣、人聲如沸育苟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽违柏。三九已至,卻和暖如春香椎,著一層夾襖步出監(jiān)牢的瞬間漱竖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評(píng)論 1 272
  • 我被黑心中介騙來泰國打工畜伐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留馍惹,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,388評(píng)論 3 373
  • 正文 我出身青樓玛界,卻偏偏與公主長得像万矾,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子慎框,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評(píng)論 2 355

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