概述
業(yè)務初期數(shù)據(jù)庫設(shè)計大多采用單庫單表的結(jié)構(gòu),隨著業(yè)務發(fā)展QPS和數(shù)據(jù)量增長到一定瓶頸,單庫單表的架構(gòu)就支撐不住了,這時就要進行數(shù)據(jù)庫層面的擴展。數(shù)據(jù)庫的擴展可以分為如下場景:
- 讀性能擴展
- 寫性能擴展
- 分庫分表
- 表結(jié)構(gòu)擴展(大表DDL)
本篇文章只系統(tǒng)的介紹基于DB的方案。和其他中間件配合的方案,之后有可能也會系統(tǒng)寫一篇。
讀性能擴展
最基本的表設(shè)計指導思想:
- 業(yè)務需求決定表的字段
-
查詢需求決定表的索引
數(shù)據(jù)庫擴展-讀性能擴展.jpg
業(yè)內(nèi)常用的讀擴展方案是主從架構(gòu)。業(yè)務上,我們通常至少有兩套系統(tǒng):運營后臺和C端應用。兩者的查詢維度一般不一樣,這意味著要有兩套不同的索引結(jié)構(gòu)。而我們又不希望運營后臺的請求影響到C端,所以數(shù)據(jù)要隔離。
所以,我們要搞兩組slave。slave1用于處理C端讀請求,假設(shè)C端的請求多以uid為查詢條件,創(chuàng)建uid索引。slave2用于處理運營后臺讀請求,假設(shè)運營后臺請求多以日期為查詢條件,創(chuàng)建date索引。
這種方案既提高了讀性能,也實現(xiàn)了請求互不影響,而且實現(xiàn)簡單。缺點是不同組的slave存在著不同的索引,對于DBA來說存在一定的維護成本。又引入了主從延時的問題,參考我的另一篇文章Mysql主從延時。
寫性能擴展

增加從庫擴展讀性能,很容易想到增加主庫擴展寫性能。數(shù)據(jù)庫層面的負載均衡通常用LVS做,成本低,性能好。關(guān)于負載均衡,參考我的另一篇文章聊聊負載均衡。
雙主同時對外提供服務,需要冗余全部數(shù)據(jù)。冗余數(shù)據(jù),就會有數(shù)據(jù)不一致的問題。比如:master1生成一條id為3的數(shù)據(jù),這條數(shù)據(jù)同步到master2之前,master2此時收到一個insert請求,也會生成一條id為3的數(shù)據(jù)。這時,就出現(xiàn)了雙主id沖突的問題。這個問題的解決思路是保證雙主生成的id不重復,方案有兩種
- 數(shù)據(jù)庫層解決方案。master1,master2設(shè)置不同的初始值,相同的步長。如圖master1生成1,3,5,7。master2生成2,4,6,8。這種方案缺點是:兩臺機器配置不一樣,會提高運維的復雜性。
- 應用層解決方案。主鍵有應用生成,采用全局id生成器來保證id不重復。
分庫分表
參考聊聊分庫分表
表結(jié)構(gòu)擴展(大表DDL)
DDL操作一定要在業(yè)務低峰期做!切記!切記!切記!
Online DDL原理
MySQL 的 DDL(Data Definition Language) 包括增減字段、增減索引等操作。在 MySQL 5.6 之前,MySQL 的 DDL 操作會按照原來的表復制一份,并做相應的修改,例如,對表 A 進行 DDL 的具體過程如下:
- 按照表 A 的定義新建一個表 B
- 對表 A 加 exclusive metadata lock
- 在表 B 上執(zhí)行 DDL 指定的操作
- 將 A 中的數(shù)據(jù)拷貝到 B
- 釋放 A 的 exclusive metadata lock
- 刪除表 A
- 將表 B 重命名為 A
如果表 A 數(shù)據(jù)量比較大,拷貝到表 B 的過程會消耗大量時間,并占用額外的存儲空間。此外,由于 DDL 操作占用了表 A 的exclusive metadata lock,所以表 A 上的 DDL 和 DML 都將阻塞無法提供服務。
MySQL 5.6之后支持了Online DDL。大致過程如下:
- 按照表 A 的定義新建一個表 B
- 對表 A 加 exclusive metadata lock
- 生成rows_log,用于記錄期間發(fā)生的DML
- 將exclusive metadata lock 降級為shared
- 在表 B 上執(zhí)行 DDL 指定的操作
- 將 A 中的數(shù)據(jù)拷貝到 B
- 將rows_log應用到表B
- 將表A的shared metadata lock 升級為exclusive
- 如果前兩步之間又有新的數(shù)據(jù)產(chǎn)生,再次應用row_log的數(shù)據(jù)到表B
- 釋放表A的exclusive metadata lock
- 刪除表 A
- 將表 B 重命名為 A
可以看出Online DDL和DDL原理的區(qū)別主要在rows_log和metadata lock的降級,這保證了這個過程中最耗時的部分-復制表數(shù)據(jù)是允許DML操作的。這也是Online的由來。
Online DDL用法
ALTER TABLE tbl_name ADD PRIMARY KEY (column), ALGORITHM=INPLACE, LOCK=NONE;
ALGORITHM=INPLACE 表示執(zhí)行DDL的過程中server和engine間不發(fā)生表copy,過程中允許并發(fā)執(zhí)行DML(INPLACE表的copy是在engine內(nèi)部執(zhí)行的,不需要像COPY一樣占用大量的磁盤I/O和CPU,減少了數(shù)據(jù)庫負載)。
LOCK=NONE 表示對 DML 操作不加鎖,DDL 過程中允許所有的 DML 操作。此外還有 EXCLUSIVE(持有排它鎖,阻塞所有的請求,適用于需要盡快完成DDL或者服務庫空閑的場景)、SHARED(允許SELECT,但是阻塞INSERT UPDATE DELETE,適用于數(shù)據(jù)倉庫等可以允許數(shù)據(jù)寫入延遲的場景)和 DEFAULT(根據(jù)DDL的類型,在保證最大并發(fā)的原則下來選擇LOCK的取值)
不過并不是所有的 DDL 操作都能用 INPLACE 的方式執(zhí)行,具體的支持情況可以在 Mysql5.7官方文檔中查看。

業(yè)界也有開源的成熟的大表DDL工具:gh-ost
注意:Mysql寫鎖優(yōu)先于讀鎖。Online DDL可能存在如下問題。
sessionA
select * from A where id =1;
sessionB
select * from A where id =1;
sessionC online ddl
sessionD
select * from A where id =1;
假如sessionA和sessionB還未提交時,開始online ddl,也就是sessionC。由于sessionA和sessionB持有 shared metadata lock。sessionC申請 exclusive metadata lock會被阻塞。之后sessionD開始,由于Mysql寫鎖優(yōu)先于讀鎖,所以sessionD獲取shared metadata lock的請求也會阻塞,直到sessionA, sessionB執(zhí)行完,sessionC獲得exclusive metadata lock,之后釋放exclusive metadata lock。
這個問題準確來說不是Online DDL的問題,讀寫鎖的實現(xiàn)機制很多都有這個問題。寫下來是為了提醒讀者注意。
