作為一個企業或者DBA,我們通常會有這種想法,數據是一個公司的核心命脈,應該需要永久保存,很多時候DBA和開發溝通的時候,開發人員也會這么告訴我們,這份數據非常重要,數據需要永久保存。然而,如果將數據庫的數據永久保存,那么遲早有一天,你會擁有一個非常大的數據庫。作為一個DBA,通常為了業務對數據庫的操作性能考慮和存儲容量的考慮。我們會建議對數據庫里大表進行數據歸檔,例如將使用的高頻數據保留在當前表,對低頻數據保留在歸檔表中。當然有些公司會將數據抽取到Hadoop、Hbase進行歸檔和離線分析,或者使用infobright作為歸檔引擎,這里不們不做具體深入分析。本文我們只從DBA運維人員的角度,是用MySQL現有技術來幫大家分析如何對大表進行歸檔,以提高數據庫性能和解決數據庫容量問題。
例子
首先我們舉個大表的例子,你負責的數據庫有一個交易數據存儲表,已經有了200GB的數據。假設已經存儲了5年的數據,但是在去年,因為業務量的增加,這個表的數據量翻了一翻。現在我們來總結一下,你現在擁有一個存儲5年數據的大表,但是你的應用程序通常只需要查詢1到3個月的數據,在此之前數據程序是不會去查詢,可能有些時候,某個人需要定位問題或對帳會查詢1年之內的數據,或者數據分析部門需要對近三年的數據進行匯總生成報表數據,基於以上考慮,為了滿足這些需求,表里的數據不能丟,也不能刪除 ,我們通常會將這些數據存儲在1個表里,來方便給各個查詢需求提供數據支持。
但是,這么龐大一張表,這種方式會極大的影響了數據庫的性能,所以我們考慮可以嘗試將上面三種應用查詢場景的數據分別放在3個表里,來解決這種性能問題:
第一個表存放近期三個月數據,用於線上高頻業務查詢。
第二個歸檔表保存所有的歷史數據用於低頻查詢,例如排查問題或者對賬。
第三個是用於存儲報告的匯總表。
有了上面這些表,我們就遵循單一責任的原則,為各種查詢的需求提供最好的性能。首先我們有一個最近3個月數據的主表,可以大大提高查詢性能和擴展性。舉個例子,假如數據的容量在未來3-5年內每年都會翻倍,但是你的主表也只是用到這么多數據的子集。比如你三個月的主表數據為20GB,那么下一年就是40GB,再后一年也只有80GB,這些數據量也是非常容易管理的。另外隨着時間的推移,軟件和硬件也在不斷改進和升級,所以可以通過升級和更新,可以不斷保證業務的性能。我們將冷數據和熱數據分別存儲到不同的數據表中,這樣也可以使得擴展性得到長期的保證。
如何實施歸檔
1.建立歸檔表_archive
這種方式是通過建立一個以_archive結尾的歸檔表來實施的。如果使用這種方式,那么一般需要在業務層進行查詢的分離改造,比如基於我們的特定歸檔規則 ,對業務端核心代碼改造或者使用proxy方案等來決定是使用主表還是歸檔表。同時我們還需要一個數據歸檔過程,當數據過時或者變成冷數據時,將該數據從主表遷移到歸檔表中。
在業務層查詢時,我們可以通過時間字段來進行查詢判斷,例如將90天之前的數據在歸檔表中查詢,否則就在主表查詢。另一種方式可以通過增加status列去判斷查詢主表還是歸檔表,如果是inactive則查詢歸檔表,否則就在主表查詢。
2.根據日期進行分區
這種方式是通過對表進行分區來實現,雖然這是一種不同的物理數據模型,但是確實有助於將表的數據進行拆分到不同的物理磁盤,並且不需要代碼的任何改造。作為DBA,一般對表進行分區是比較常用的方式,我們可以通過日期字段很容易確定哪些是冷數據,並根據日期將不同日期的時間分配到不同的分區中,在查詢的時候,我們可以通過日期來從分區中快速定位到對應的數據,同時建立分區表也比較利於DBA對大表進行管理操作。
請看下面的例子,通過下面方式,我們可以將數據按照日期進行分配到正確的分區.
CREATE TABLE `largetable` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `dateCreated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `status` int default 1, `sometext` text, PRIMARY KEY (`id`,`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; Query OK, 0 rows affected (0.03 sec)
mysql> alter table largetable partition by RANGE(YEAR(dateCreated)) ( -> PARTITION p2016 VALUES LESS THAN (2017), -> PARTITION p2017 VALUES LESS THAN (2018), -> PARTITION p2018 VALUES LESS THAN (2019), -> PARTITION p2019 VALUES LESS THAN (2020), -> PARTITION p2020 VALUES LESS THAN (2021), -> PARTITION pmax VALUES LESS THAN MAXVALUE); Query OK, 0 rows affected (0.05 sec) Records: 0 Duplicates: 0 Warnings: 0
在上面的例子中,我們根據數據行的創建時間,按年將數據放入到不同的分區,這里需要注意的是在2020年以后,我們還需要在表里添加新的年份,當然我們可以提前加更多的分區或者部署腳本來自動化創建新的分區。
3.通過狀態進行分區
我們也可以通過status狀態列來進行分區,這種情況下,通常狀態列會包含active/inactive兩種狀態,然后通過update進行狀態列的更新(使用replace或者insert+delete也是可以的),將數據放入到正確的分區當中。請看下面的示例:
mysql> CREATE TABLE `largetable` (
-> `id` bigint unsigned NOT NULL AUTO_INCREMENT,
-> `dateCreated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
-> `status` int default 1, — default active
-> `sometext` text,
-> PRIMARY KEY (`id`,`status`)
-> ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.02 sec)
mysql> alter table largetable partition by list(status) (
-> partition pactive values in (1), — active
-> partition pinactive values in (2) — inactive
-> );
Query OK, 0 rows affected (0.03 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> select * from largetable partition (pactive);
Empty set (0.00 sec)
mysql> select * from largetable partition (pinactive);
Empty set (0.00 sec)
mysql> insert into largetable(sometext) values (‘hello’);
Query OK, 1 row affected (0.01 sec)
mysql> select * from largetable partition (pinactive);
Empty set (0.00 sec)
mysql> select * from largetable partition (pactive);
+—-+———————+——–+———-+
| id | dateCreated | status | sometext |
+—-+———————+——–+———-+
| 1 | 2017-10-30 10:04:03 | 1 | hello |
+—-+———————+——–+———-+
1 row in set (0.00 sec)
mysql> update largetable set status = 2 where id =1 ;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from largetable partition (pactive);
Empty set (0.00 sec)
mysql> select * from largetable partition (pinactive);
+—-+———————+——–+———-+
| id | dateCreated | status | sometext |
+—-+———————+——–+———-+
| 1 | 2017-10-30 10:04:03 | 2 | hello |
+—-+———————+——–+———-+
1 row in set (0.00 sec)
4.通過ID進行分區
最后一種方式,我們介紹下通過自增ID主鍵列進行分區的方式,請看下面的示例:
mysql> CREATE TABLE `largetable` (
-> `id` bigint unsigned NOT NULL AUTO_INCREMENT,
-> `dateCreated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
-> `status` int default 1,
-> `sometext` text,
-> PRIMARY KEY (`id`)
-> ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.02 sec)
mysql> alter table largetable partition by RANGE(id) (
-> PARTITION p1 VALUES LESS THAN (500000000),
-> PARTITION p2 VALUES LESS THAN (1000000000),
-> PARTITION p3 VALUES LESS THAN (1500000000),
-> PARTITION p4 VALUES LESS THAN (2000000000),
-> PARTITION p5 VALUES LESS THAN (2500000000),
-> PARTITION pmax VALUES LESS THAN MAXVALUE);
Query OK, 0 rows affected (0.06 sec)
Records: 0 Duplicates: 0 Warnings: 0
上面的例子中,我們通過ID列進行了range分區,如果業務的查詢基本都是主鍵查找,那么這種方式會比較有用。 與按照日期分區相比,按照ID分區還會有助於數據平均分配到各個分區。
最后總結
上述簡單介紹了歸檔表和分區的一些方法,但是關鍵的一點是要如何選擇正確的分區鍵和分區方式,這一點並不容易,我們需要和業務端進行充分溝通業務場景,並根據場景來選擇合適的分區鍵,我們選擇的分區鍵要能正確分區,並且不會降低表的查詢速度。
另外一點,為了讓優化器能將查詢發送到正確的分區鍵,在創建分區表的時候,我們需要將分區鍵添加到主鍵里,並且理想情況下,該分區鍵能被包含在所有select/update/delete等語句的where條件里面,否則的話,你的查詢將會按照順序查找表對應的每個分區,這種情況下查詢性能就沒有那么好了。
---------------------
轉自:https://blog.csdn.net/yajie_12/article/details/78798916