MySQL 數據庫設計規范

2021-05-29 v1.0.1

目錄

1. 規范背景與目的

MySQL 數據庫與 Oracle、 SQL Server 等數據庫相比,有其內核上的優勢與劣勢。我們在使用 MySQL 數據庫的時候需要遵循一定規范,揚長避短。
本規范旨在幫助或指導技術人員做出適合線上業務的數據庫設計。在數據庫變更和處理流程、數據庫表設計、SQL 編寫等方面予以規范,從而為業務系統穩定、健康地運行提供保障。

2. 設計規范

2.1 數據庫設計

以下所有規范會按照【強制】、【建議】兩個級別進行標注。

2.1.1 一般命名規則

  1. 【強制】使用小寫,避免因大小寫敏感而導致的錯誤。
  2. 【強制】沒有空格,使用下劃線代替。
  3. 【強制】名稱中沒有數字,只有英文字母,單詞之間用下劃線分隔。
  4. 【強制】有效的可理解的名稱,禁止拼音英文混用,庫名最好與應用名稱一致。
  5. 【強制】名稱應該是自我解釋的。
  6. 【強制】名稱不應超過 32 個字符。
  7. 【強制】使用前綴,便于業務分類。

2.1.2 庫

  1. 【強制】遵守以上全部一般命名規則。
  2. 【強制】使用單數命名。
  3. 【強制】庫的名稱格式:業務系統名稱_子系統名。
  4. 【強制】一般分庫名稱命名格式是 庫通配名_編號,編號從 0 開始遞增,比如 northwind_001,以時間進行分庫的名稱格式是庫通配名_時間
  5. 【強制】開發環境庫的名稱增加后綴 _dev,測試環境增加后綴 _test
  6. 【強制】創建數據庫時必須顯式指定字符集,并且字符集只能是 utf8utf8mb4,優先使用 utf8mb4

2.1.3 表

  1. 【強制】遵守以上全部一般命名規則。
  2. 【強制】使用單數命名。
  3. 【強制】表的名稱格式:業務模塊名稱_表名。視圖增加前綴 v_
  4. 【強制】每個表建議不超過 30-50 個字段。
  5. 【強制】相關模塊的表名與表名之間盡量體現 join 的關系,如 user 表和 user_login 表。
  6. 【強制】創建表時必須顯式指定字符集為 utf8utf8mb4,優先使用 utf8mb4
  7. 【強制】創建表時必須顯式指定表存儲引擎類型,如無特殊需求,一律為 InnoDB。當需要使用除 InnoDB/MyISAM/Memory 以外的存儲引擎時,必須通過 DBA 審核才能在生產環境中使用。因為 InnoDB 表支持事務、行鎖、宕機恢復、MVCC 等關系型數據庫重要特性,為業界使用最多的 MySQL 存儲引擎。而這是其它大多數存儲引擎不具備的,因此首推 InnoDB。
  8. 【強制】建表必須有 comment。
  9. 【建議】關于主鍵:每個表必須有主鍵。(1) 類型為 bigint,使用 snowflake 雪花算法生成,禁止使用自增數值主鍵;(2) 命名格式是 表名_id ,例如 user_idorder_id,禁止使用 id 命名,多表關聯時容易產生混淆。
  10. 【建議】核心表(如用戶表,金錢相關的表)必須有行數據的創建人、創建時間、修改人、修改時間字段 create_useridcreate_timeupdate_useridupdate_time,便于排查問題。
  11. 【建議】表中所有字段必須都是 NOT NULL 屬性,業務可以根據需要定義 DEFAULT 值。因為使用 NULL 值會存在每一行都會占用額外存儲空間、數據遷移容易出錯、聚合函數計算結果偏差等問題。
  12. 【建議】建議對表里的 blobtext 等大字段,垂直拆分到其它表里,僅在需要讀這些對象的時候才去 select。
  13. 【建議】反范式設計:把經常需要 join 查詢的字段,在其它表里冗余一份。如 username 屬性在 user_accountuser_login_log 等表里冗余一份,減少 join 查詢。
  14. 【強制】中間表用于保留中間結果集,名稱必須以 tmp_ 開頭。備份表用于備份或抓取源表快照,名稱必須以 bak_ 開頭。中間表和備份表定期清理。
  15. 【強制】對于超過 100 萬行的大表進行 alter table,必須經過 DBA 審核,并在業務低峰期執行。因為 alter table 會產生表鎖,期間阻塞對于該表的所有寫入,對于業務可能會產生極大影響。
  16. 【建議】盡量控制單表數據量的大小,建議控制在 500 萬以內。500 萬并不是 MySQL 數據庫的限制,數據量過大會對修改表結構,數據備份、恢復產生影響。
  17. 【強制】禁止使用存儲過程、觸發器、Event。
  18. 【建議】建議不使用視圖。
  19. 【強制】禁止嵌套視圖。
  20. 【強制】禁止存儲大文件或者大照片。

2.1.4 字段

  1. 【強制】遵守以上全部一般命名規則。
  2. 【建議】盡可能選擇短的或一兩個單詞。
  3. 【強制】避免使用保留字作為字段名稱:orderdatename 是數據庫的保留字,避免使用它。可以為這些名稱添加前綴使其易于理解,如 user_namesignup_date 等。
  4. 【強制】避免使用與表名相同的字段名,這會在編寫查詢時造成混淆。
  5. 【建議】字符集和庫級保持一致。不單獨定義字段字符集。
  6. 【建議】反范式設計:為了提高數據更新性能,禁止使用外鍵。如果有外鍵完整性約束,需要應用程序控制。外鍵會導致表與表之間耦合,update 與 delete 操作都會涉及相關聯的表,十分影響性能,甚至會造成死鎖。高并發情況下容易造成數據庫性能,大數據高并發業務場景數據庫使用以性能優先。
  7. 【強制】嚴禁在數據庫中明文存儲用戶密碼、身份證、信用卡號(信用卡PIN碼)等核心機密數據。
  8. 【強制】一個字段僅允許表達一種業務含義,例如通過員工職級反映薪資等級是不合適的。
  9. 【強制】多表中的相同列,必須保證列名一致,數據類型一致。

2.1.5 字段數據類型優化

  1. 【建議】文本數據盡量用 varchar 存儲。因為 varchar 是變長存儲,比 char 更省空間。字符數不要超過 2700。
  2. 【建議】時間類型盡量選取 datetime
  3. 【建議】業務中選擇性很少的狀態 status、類型 type 等字段推薦使用 smallint 類型節省存儲空間。
  4. 【建議】金額貨幣科學計數建議采用 decimal 數據類型。
  5. 【建議】禁止使用數據庫私有數據類型,例如 enumset,不利于數據庫遷移。
  • 詳細存儲大小參考下圖:

    類型(同義詞) 存儲長度(BYTES) 最小值(SIGNED/UNSIGNED) 最大值(SIGNED/UNSIGNED)
    整形數字
    TINYINT 1 -128/0 127/255
    SMALLINT 2 -32,768/0 32767/65,535
    MEDIUMINT 3 -8,388,608/0 8388607/16,777,215/
    INT(INTEGER) 4 -2,14,7483,648/0 2147483647/4,294,967,295/
    BIGINT 8 -2^63/0 263-1/264-1
    小數支持
    FLOAT[(M[,D])] 4 or 8 -
    DOUBLE[(M[,D])]
    (REAL, DOUBLE PRECISION)
    8 -
    時間類型
    DATETIME 8 1001-01-01 00:00:00 9999-12-31 23:59:59
    DATE 3 1001-01-01 9999-12-31
    TIME 3 00:00:00 23:59:59
    YEAR 1 1001 9999
    TIMESTAMP 4 1970-01-01 00:00:00

2.1.6 索引設計

  1. 【強制】主鍵類型為 int/bigint,且主鍵值禁止被更新。
  2. 【建議】主鍵索引的名稱以 pk_ 開頭,唯一鍵以 uk_ 開頭,普通索引以 idx_ 開頭,一律使用小寫格式,以 表名_字段的名稱或縮寫 作為后綴。
  3. 【強制】InnoDB 和 MyISAM 存儲引擎表,索引類型必須為 BTREE;MEMORY 表可以根據需要選擇 HASH 或者 BTREE 類型索引。
  4. 【強制】單個索引中每個索引記錄的長度不能超過 64KB。
  5. 【建議】單個表上的索引個數不能超過 5 個。
  6. 【建議】在建立索引時,多考慮建立聯合索引,并把區分度最高的字段放在最前面。如列 user_id 的區分度可由 select count(distinct user_id) 計算出來。
  7. 【建議】在多表 join 的 SQL 里,保證被驅動表的連接列上有索引,這樣 join 執行效率最高。
  8. 【建議】建表或加索引時,保證表里互相不存在冗余索引。如果表里已經存在 key(a, b),則 key(a) 為冗余索引,需要刪除。
  9. 【建議】如果選擇性超過 20%,那么全表掃描比使用索引性能更優,即沒有設置索引的必要。

2.1.7 分庫分表、分區表

  1. 【強制】分區表的分區字段(partition-key)必須有索引,或者是組合索引的首列。
  2. 【強制】單個分區表中的分區(包括子分區)個數不能超過 1024。
  3. 【強制】上線前 DBA 必須指定分區表的創建、清理策略。
  4. 【強制】訪問分區表的 SQL 必須包含分區鍵。
  5. 【建議】單個分區文件不超過 2G,總大小不超過 50G。建議總分區數不超過 20 個。
  6. 【強制】對于分區表執行 alter table 操作,必須在業務低峰期執行。
  7. 【強制】采用分庫策略的,庫的數量不能超過 1024。
  8. 【強制】采用分表策略的,表的數量不能超過 4096。
  9. 【建議】單個分表不超過 500 萬行,ibd 文件大小不超過 2G,這樣才能讓數據分布式變得性能更佳。
    10.【建議】水平分表盡量用取模方式,日志、報表類數據建議采用日期進行分表。

2.1.8 字符集

  1. 【強制】數據庫本身庫、表、列所有字符集必須保持一致,為 utf8utf8mb4,優先使用 utf8mb4
  2. 【強制】前端程序字符集或者環境變量中的字符集,與數據庫、表的字符集必須一致,統一為 utf8

2.1.9 程序層 DAO 設計建議

  1. 【強制】SQL 使用綁定變量傳入參數的方式,避免 SQL 注入。
  2. 【建議】前端程序連接數據源,必須要有連接超時和失敗重連機制,且失敗重試必須有間隔時間。
  3. 【建議】前端程序報錯里盡量能夠提示原生態的報錯信息,便于排查錯誤。
  4. 【建議】對于有連接池的前端程序,必須根據業務需要配置初始、最小、最大連接數,超時時間以及連接回收機制,否則會耗盡數據庫連接資源,造成線上事故。
  5. 【建議】對于 loghistory 類型的表,隨時間增長容易越來越大,因此上線前 DBA 必須建立表數據清理或歸檔方案。
  6. 【建議】在應用程序設計階段,必須考慮并規避數據庫中主從延遲對于業務的影響。盡量避免從庫短時延遲(20 秒以內)對業務造成影響,建議強制一致性的讀開啟事務走主庫,或更新后過一段時間再去讀從庫。
  7. 【建議】多個并發業務邏輯訪問同一塊數據(InnoDB 表)時,會在數據庫端產生行鎖甚至表鎖導致并發下降,因此建議更新類 SQL 盡量基于主鍵去更新。
  8. 【建議】業務邏輯之間加鎖順序盡量保持一致,否則會導致死鎖。
  9. 【建議】對于單表讀寫比大于 10:1 的數據行或單個列,可以將熱點數據放在緩存里(如 Memcached 或 Redis),加快訪問速度,降低數據庫壓力。

2.1.10 一個規范的建表語句示例

  • 一個較為規范的建表語句為:
    create table user 
    ( 
        `id`            bigint(11) not null, 
        `user_id`       bigint(11) not null comment '用戶 ID', 
        `username`      varchar(45) not null comment '登錄名', 
        `email`         varchar(30) not null comment '郵箱', 
        `nickname`      varchar(45) not null comment '昵稱', 
        `avatar`        int(11) not null comment '頭像', 
        `birthday`      date not null comment '生日', 
        `gender`        tinyint(4) default '0' comment '性別', 
        `intro`         varchar(150) default null comment '簡介', 
        `resume_url`    varchar(300) not null comment '簡歷存放地址', 
        `register_ip`   int not null comment '用戶注冊時的源 IP', 
        `review_status` tinyint not null comment '審核狀態,1-通過,2-審核中,3-未通過,4-尚未提交審核', 
        `create_time`   timestamp not null comment '記錄創建的時間', 
        `update_time`   timestamp not null comment '資料修改的時間', 
        
        primary key (`id`), 
        unique key `idx_user_id` (`user_id`), 
        key `idx_username`(`username`), 
        key `idx_create_time`(`create_time`, `review_status`) 
    ) 
    engine = InnoDB
    default charset = utf8 
    comment = '用戶基本信息'; 
    

2.2 SQL 編寫

2.2.1 DML 語句

  1. 【強制】select 語句必須指定具體字段名稱,禁止寫成 *。因為 select * 會將不該讀的數據也從 MySQL 里讀出來,造成網卡壓力。
  2. 【強制】insert 語句指定具體字段名稱,不要寫成 insert into t1 values(…),道理同上。
  3. 【建議】insert into … values(xx),(xx),(xx)…,這里 xx 的值不要超過 5000 個。值過多雖然上線很快,但會引起主從同步延遲。
  4. 【建議】select 語句不要使用 union,推薦使用 union all,并且 union 子句個數限制在 5 個以內。因為 union all 不需要去重,節省數據庫資源,提高性能。
  5. 【建議】in 值列表限制在 500 以內。例如 select … where user_id in(…500 個以內…),這么做是為了減少底層掃描,減輕數據庫壓力從而加速查詢。
  6. 【建議】事務里批量更新數據需要控制數量,進行必要的 sleep,做到少量多次。
  7. 【強制】事務涉及的表必須全部是 InnoDB 表。否則一旦失敗不會全部回滾,且易造成主從庫同步終端。
  8. 【強制】寫入和事務發往主庫,只讀 SQL 發往從庫。
  9. 【強制】除靜態表或小表(100 行以內),dml 語句必須有 where 條件,且使用索引查找。
  10. 【強制】生產環境禁止使用 hint,如 sql_no_cacheforce indexignore keystraight join 等。因為 hint 是用來強制 sql 按照某個執行計劃來執行,但隨著數據量變化我們無法保證自己當初的預判是正確的,因此我們要相信 MySQL 優化器。
  11. 【強制】where 條件里等號左右字段類型必須一致,否則無法利用索引。
  12. 【建議】select|update|delete|replace 要有 where 子句,且 where 子句的條件必需使用索引查找。
  13. 【強制】生產數據庫中強烈不推薦大表上發生全表掃描,但對于 100 行以下的靜態表可以全表掃描。查詢數據量不要超過表行數的 25%,否則不會利用索引。
  14. 【強制】where 子句中禁止只使用全模糊的 like 條件進行查找,必須有其它等值或范圍查詢條件,否則無法利用索引。
  15. 【建議】索引列不要使用函數或表達式,否則無法利用索引。如 where length(name) = 'admin'where user_id + 2 = 10023
  16. 【建議】減少使用 or 語句,可將 or 語句優化為 union,然后在各個 where 條件上建立索引。如 where a = 1 or b = 2 優化為 where a = 1 … union … where b = 2, key(a), key(b)
  17. 【建議】分頁查詢,當 limit 起點較高時,可先用過濾條件進行過濾。如 select a, b, c from t1 limit 10000, 20; 優化為: select a, b, c from t1 where id > 10000 limit 20;

2.2.2 多表連接

  1. 【強制】禁止跨 DB 的 join 語句。因為這樣可以減少模塊間耦合,為數據庫拆分奠定堅實基礎。
  2. 【強制】禁止在業務的更新類 SQL 語句中使用 join,比如 update t1 join t2 …
  3. 【建議】不建議使用子查詢,建議將子查詢 SQL 拆開結合程序多次查詢,或使用 join 來代替子查詢。
  4. 【建議】線上環境,多表 join 不要超過 3 個表。
  5. 【建議】多表連接查詢推薦使用別名,且 select 列表中要用別名引用字段,數據庫.表格式,如 select a from db1.table1 alias1 where …
  6. 【建議】在多表 join 中,盡量選取結果集較小的表作為驅動表,來 join 其它表。

2.2.3 事務

  1. 【建議】事務中 insert|update|delete|replace 語句操作的行數控制在 2000 以內,以及 where 子句中 in 列表的傳參個數控制在 500 以內。
  2. 【建議】批量操作數據時,需要控制事務處理間隔時間,進行必要的 sleep,一般建議值 5-10 秒。
  3. 【建議】對于有 auto_increment 屬性字段的表的插入操作,并發需要控制在 200 以內。
  4. 【強制】程序設計必須考慮“數據庫事務隔離級別”帶來的影響,包括臟讀、不可重復讀和幻讀。線上建議事務隔離級別為 repeatable-read
  5. 【建議】事務里包含 SQL 不超過 5 個(支付業務除外)。因為過長的事務會導致鎖數據較久,MySQL 內部緩存、連接消耗過多等雪崩問題。
  6. 【建議】事務里更新語句盡量基于主鍵或 unique key,如 update … where id = XX;,否則會產生間隙鎖,內部擴大鎖定范圍,導致系統性能下降,產生死鎖。
  7. 【建議】盡量把一些典型外部調用移出事務,如調用 Web Service,訪問文件存儲等,從而避免事務過長。
  8. 【建議】對于 MySQL 主從延遲嚴格敏感的 select 語句,請開啟事務強制訪問主庫。

2.2.4 排序和分組

  1. 【建議】減少使用 order by,和業務溝通能不排序就不排序,或將排序放到程序端去做。order bygroup bydistinct 這些語句較為耗費 CPU,數據庫的 CPU 資源是極其寶貴的。
  2. 【建議】order bygroup bydistinct 這些 SQL 盡量利用索引直接檢索出排序好的數據。如 where a = 1 order by 可以利用 key(a, b)
  3. 【建議】包含了 order bygroup bydistinct 這些查詢的語句,where 條件過濾出來的結果集請保持在 1000 行以內,否則 SQL 會很慢。

2.2.5 線上禁止使用的 SQL 語句

  1. 【強制】禁用 update|delete t1 … where a = XX limit XX; 這種帶 limit 的更新語句。因為會導致主從不一致,導致數據錯亂。建議加上 order by PK
  2. 【強制】禁止使用關聯子查詢,如 update t1 set … where name in(select name from user where …);,效率極其低下。
  3. 【強制】禁用 procedure、function、trigger、views、event、外鍵約束。因為他們消耗數據庫資源,降低數據庫實例可擴展性。推薦都在程序端實現。
  4. 【強制】禁用 insert into … on duplicate key update … 在高并發環境下,會造成主從不一致。
  5. 【強制】禁止聯表更新語句,如 update t1, t2 where t1.id = t2.id …

3. MySQL 使用約束及建議

3.1. 關于性能的爭論

最佳實踐通常是為了獲取最佳性能,例如一個支付環境,每秒只需要 3000 個事務就夠了,更看重一致性。
所以對于性能,關建是要明確一下定位,然后再跟據自已的情況來判到底使用那些技術組合。
讀寫分離:對于非特殊要求的業務可以考慮部分 SQL 或是全量讀 SQL 進行讀寫分離操作,從而增加 DB 的處理能力。
關于分庫分表:分庫分表屬于已經在 DB 的設計上無路可走,已經達到了前所未有的業務壓力下才去走的一個技能。
通過常情況可以考慮優先進行硬件層面優化、SQL 簡化兩個方向的工作,最后再來考慮分庫分表,減少業務的復雜度。
建議

簡單的使用 MySQL;
計算機在處理整數比較浮點數快 N 倍,慎用浮點數;
盡量不要在DB里做運算,大的計算可以考慮中間件完成;
數據庫拆分中適合冗余;
單庫容量:SAS 盤 Raid 10 控制在 500G 左右,PCI-E 卡,可以控制在卡的大小 80% 左右。日志或是歷史庫除外。

3.2. MySQL 特點

MySQL 是單進程多線程,不像 Oracle 是多進程的;
每個 MySQL 內部線程同時只能用到一個邏輯 CPU 線程;
每個 SQL 同時只能用到一個邏輯 CPU 線程;
無執行計劃緩存(無類似 ORACLE 的 library cache),不過 MySQL 的執行計劃解析比較輕量級,效率還不錯,這方面不會是瓶頸;
query cache 的更新需要持有全局 mutex,數據有任何更新都需要等待該 mutex 效率低,且整個表的 query cache 也會失效,因此強烈建議關閉 query cache
沒有 thread pool 時,如果有瞬間大量連接請求,性能會急劇下降。
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念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

推薦閱讀更多精彩內容