一、分表
在日常開發或維護中經常會遇到大表的情況,所謂的大表是指存儲了百萬級乃至千萬級條記錄的表。這樣的表過於龐大,導致數據庫在查詢和插入的時候耗時太長,性能低下,如果涉及聯合查詢的情況,性能會更加糟糕。分表和表分區的目的就是減少數據庫的負擔,提高數據庫的效率,通常點來講就是提高表的增刪改查效率。
1. 什麼是分表
分表是將一個大表按照一定的規則分解成多張的實體表
,我們可以稱爲子表
。對於myisam
引擎,每個表都對應三個文件,MYD數據文件,.MYI索引文件,.frm表結構文件。根據分表技術對海量數據的優化方式目前有2種方法。
1.1 垂直分割
把一個數據量很大的表,可以把不同的列劃分到不同的子表中,子表的結構各不相同。垂直分割的理由一般是根據數據的活躍度進行分離,不同活躍的數據,處理方式是不同的。
【案例】
- 對於一個博客系統,文章標題,作者,分類,創建時間等,是變化頻率慢,查詢次數多,我們把它叫做冷數據。
- 而博客的瀏覽量,回覆數等,類似的統計信息,或者別的變化頻率比較高的數據,我們把它叫做活躍數據/熱數據。
所以,在進行數據庫結構設計的時候,就應該考慮分表,首先是縱向分表的處理。
(1)數據庫引擎的選擇
這樣縱向分表後:首先存儲引擎的使用不同,冷數據使用MyIsam 可以有更好的查詢效果;活躍數據,可以使用Innodb ,可以有更好的更新速度。
(2)讀寫優化
其次,對冷數據進行更多的從庫配置,因爲更多的操作是查詢,這樣來加快查詢速度。對熱數據,可以相對有更多的主庫的橫向分表處理。
(3)熱數據加緩存
其實,對於一些特殊的活躍數據,也可以考慮使用memcache、redis之類的緩存,等累計到一定量再去更新數據庫。或者mongodb 一類的nosql 數據庫,這裏只是舉例,就先不說這個。
1.2 水平分割
根據一列或者多列的值把數據行放到多個獨立的子表裏,字表的結構相同。水平分表方式可以通過多個低配置主機整合起來,實現高性能。分表理由:保證單表的容量不會太大,從而來保證單表的查詢等處理能力。
下面要介紹的“分表”主要指水平分表。
2. 利用merge存儲引擎實現分表
2.1 步驟
假如我有一張用戶表user,有50W條數據,現在要拆成二張表user1和user2,每張表25W條數據。
(1)新建子表
mysql> CREATE TABLE IF NOT EXISTS `user1` (
-> `id` int(11) NOT NULL AUTO_INCREMENT,
-> `name` varchar(50) DEFAULT NULL,
-> `sex` int(1) NOT NULL DEFAULT '0',
-> PRIMARY KEY (`id`)
-> ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
Query OK, 0 rows affected (0.05 sec)
mysql> CREATE TABLE IF NOT EXISTS `user2` (
-> `id` int(11) NOT NULL AUTO_INCREMENT,
-> `name` varchar(50) DEFAULT NULL,
-> `sex` int(1) NOT NULL DEFAULT '0',
-> PRIMARY KEY (`id`)
-> ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
Query OK, 0 rows affected (0.01 sec)
(2)往子表裏插入數據
INSERT INTO user1(user1.id,user1.name,user1.sex)SELECT (user.id,user.name,user.sex)FROM user where user.id <= 250000
INSERT INTO user2(user2.id,user2.name,user2.sex)SELECT (user.id,user.name,user.sex)FROM user where user.id > 250000
(3)建立merge表
mysql> CREATE TABLE IF NOT EXISTS `alluser` (
-> `id` int(11) NOT NULL AUTO_INCREMENT,
-> `name` varchar(50) DEFAULT NULL,
-> `sex` int(1) NOT NULL DEFAULT '0',
-> INDEX(id)
-> ) TYPE=MERGE UNION=(user1,user2) INSERT_METHOD=LAST AUTO_INCREMENT=1 ;
Query OK, 0 rows affected, 1 warning (0.00 sec)
(4)備份user表,然後刪除它
(5)把這個alluser表的表名改成user
2.2 注意事項
(1)一個 merge 表不能在整個表上維持 unique 約束。當你執行一個 insert,數據進入第一個或者最後一個 myisam 表(取決於 insert_method 選項的值)。mysql 確保唯一鍵值在那個 myisam 表裏保持唯一,但不是跨集合裏所有的表。
(2)當你創建一個 merge 表之時,沒有檢查去確保底層表的存在以及有相同的機構。當 merge 表被使用之時,mysql 檢查每個被映射的表的記錄長度是否相等,但這並不十分可靠。如果你從不相似的 myisam 表創建一個 merge 表,你非常有可能撞見奇怪的問題。
3. 常用的分表策略
上面介紹的分表其實是對錶結構進行了更改,對大表進行拆分。實際上我們在設計數據庫表結構和業務邏輯的時候,就應該計算業務需求,提前做好分表,避免大表的出現。下面是基於業務層面的一些常用分表策略。下面以新聞發佈系統
舉例。下面分離出的子表可以位於一個數據實例上,如果位於不同的實例上那麼就存在分庫
了,也就多了表與庫的映射這一步。
(1)按時間結構
類似:
article_201701
article_201702
article_201703
在這個系統中,主鍵是13位帶毫秒的時間戳
(2)歸檔式
類似:
article_old
article_new
一張是舊文章表,一張是新文章表,新文章表放2個月的信息,每天定期把2個月中的最早一天的文章歸入舊錶中。
(3)按版塊結構
news_category
news_article
sports_category
sports_article
(4)按哈希結構
md5取前兩位哈希可以達到1296張表,如果覺得不夠,那就再加一位,總數可達46656張表。
二、分區
1. 什麼是分區?
分表是邏輯層面的分割,分區是物理層面、存儲層面的分割。準確來說,分區是將數據分段劃分在多個位置存放,可以是同一塊磁盤也可以在不同的機器。分區後,表面上還是一張表。
mysql分表和分區有什麼聯繫呢?
(1)都能提高mysql的性高,在高併發狀態下都有一個良好的表現。
(2)分表和分區不矛盾,可以相互配合的,對於那些大訪問量,並且表數據比較多的表,我們可以採取分表和分區結合的方式。
(3)merge這種分表方式,不能和分區配合。
(4)表分區相對於分表,操作方便,不需要創建子表。
2. 分區的類型
2.1 Range
把連續區間按範圍劃分:
create table user(
id int(11),
money int(11) unsigned not null,
date datetime
)
partition by range(YEAR(date))(
partition p2014 values less than (2015),
partition p2015 values less than (2016),
partition p2016 values less than (2017),
partition p2017 values less than maxvalue
);
2.2 List
把離散值分成集合,按集合劃分,適合有固定取值列的表
create table user(
a int(11),
b int(11)
)
partition by list(b)(
partition p0 values in (1,3,5,7,9),
partition p1 values in (2,4,6,8,0)
);
2.3 Hash
隨機分配,分區數固定:
create table user(
a int(11),
b datetime
)
partition by hash(YEAR(b))
partitions 4;
2.4 Key
類似Hash,區別是隻支持1列或多列,且mysql提供自身的Hash函數
create table user(
a int(11),
b datetime
)
partition by key(b)
partitions 4;
3. 分區管理
(1)新增分區
ALTER TABLE sale_data
ADD PARTITION (PARTITION p201710 VALUES LESS THAN (201711));
(2)刪除分區
當刪除了一個分區,也同時刪除了該分區中所有的數據。
ALTER TABLE sale_data DROP PARTITION p201710;
(3)分區的合併
下面的SQL,將p201701 - p201709 合併爲3個分區p2017Q1 - p2017Q3:
ALTER TABLE sale_data
REORGANIZE PARTITION p201701,p201702,p201703,
p201704,p201705,p201706,
p201707,p201708,p201709 INTO
(
PARTITION p2017Q1 VALUES LESS THAN (201704),
PARTITION p2017Q2 VALUES LESS THAN (201707),
PARTITION p2017Q3 VALUES LESS THAN (201710)
);