MySql分割槽、分表和分庫

資料庫的資料量達到一定程度之後,為避免帶來系統性能上的瓶頸。需要進行資料的處理,採用的手段是分割槽、分片、分庫、分表

一些問題的解釋:

1.為什麼要分表和分割槽?
日常開發中我們經常會遇到大表的情況,所謂的大表是指儲存了百萬級乃至千萬級條記錄的表。
這樣的表過於龐大,導致資料庫在查詢和插入的時候耗時太長,效能低下,
如果涉及聯合查詢的情況,效能會更加糟糕。
分表和表分割槽的目的就是減少資料庫的負擔,提高資料庫的效率,通常點來講就是提高表的增刪改查效率。 2.什麼是分表?
分表是將一個大表按照一定的規則分解成多張具有獨立儲存空間的實體表,
我們可以稱為子表,每個表都對應三個檔案,MYD資料檔案,.MYI索引檔案,.frm表結構檔案。
這些子表可以分佈在同一塊磁碟上,也可以在不同的機器上。
app讀寫的時候根據事先定義好的規則得到對應的子表名,然後去操作它。 3.什麼是分割槽?
分割槽和分表相似,都是按照規則分解表。
不同在於分表將大表分解為若干個獨立的實體表,而分割槽是將資料分段劃分在多個位置存放,
可以是同一塊磁碟也可以在不同的機器。
分割槽後,表面上還是一張表,但資料雜湊到多個位置了。
app讀寫的時候操作的還是大表名字,db自動去組織分割槽的資料。 4.mysql分表和分割槽有什麼聯絡呢?
(1)都能提高mysql的性高,在高併發狀態下都有一個良好的表現。
(2)分表和分割槽不矛盾,可以相互配合的,對於那些大訪問量,並且表資料比較多的表,
我們可以採取分表和分割槽結合的方式,訪問量不大,但是表資料很多的表,我們可以採取分割槽的方式等。
(3)分表技術是比較麻煩的,需要手動去建立子表,app服務端讀寫時候需要計運算元表名。
採用merge好一些,但也要建立子表和配置子表間的union關係。
(4)表分割槽相對於分表,操作方便,不需要建立子表。

分割槽

MySQL的物理資料,儲存在表空間檔案(.ibdata1和.ibd)中,這裡講的分割槽的意思是指將同一表中不同行的記錄分配到不同的物理檔案中,幾個分割槽就有幾個.idb檔案

MySQL在5.1時添加了對水平分割槽的支援。

分割槽是將一個表或索引分解成多個更小,更可管理的部分。

每個區都是獨立的,可以獨立處理,也可以作為一個更大物件的一部分進行處理。這個是MySQL支援的功能,業務程式碼無需改動。

可以通過使用SHOW VARIABLES命令來確定MySQL是否支援分割槽。

MySQL分割槽型別

  1. RANGE分割槽:基於一個給定區間邊界,得到若干個連續區間範圍,按照分割槽鍵的落點,把資料分配到不同的分割槽;
  2. LIST分割槽:類似RANGE分割槽,區別在於LIST分割槽是基於枚舉出的值列表分割槽,RANGE是基於給定連續區間範圍分割槽;
  3. HASH分割槽:基於使用者自定義的表示式的返回值,對其根據分割槽數來取模,從而進行記錄在分割槽間的分配的模式。這個使用者自定義的表示式,就是MySQL希望使用者填入的雜湊函式。
  4. KEY分割槽:類似於按HASH分割槽,區別在於KEY分割槽只支援計算一列或多列,且使用MySQL 伺服器提供的自身的雜湊函式。

RANGE分割槽

把連續區間按範圍劃分,是實戰最常用的一種分割槽型別,行資料基於屬於一個給定的連續區間的列值被放入分割槽。

但是記住,當插入的資料不在一個分割槽中定義的值的時候,會拋異常。

RANGE分割槽主要用於日期列的分割槽,比如交易表啊,銷售表啊等。可以根據年月來存放資料。

如果你分割槽走的唯一索引中date型別的資料,
那麼注意了,優化器只能對YEAR(),TO_DAYS(),TO_SECONDS(),UNIX_TIMESTAMP()這類函式進行優化選擇。 實戰中可以用int型別的欄位來存時間戳做分割槽列,那麼只用存yyyyMM就好了,也不用關心函數了。
CREATE TABLE
`Order` (
`id`
INT NOT NULL AUTO_INCREMENT,
`partition_key`
INT NOT NULL,
`amt`
DECIMAL(5) NULL) PARTITION BY RANGE(partition_key)
PARTITIONS 5(
PARTITION part0 VALUES LESS THAN(201901),
PARTITION part1 VALUES LESS THAN(201902),
PARTITION part2 VALUES LESS THAN(201903),
PARTITION part3 VALUES LESS THAN(201904),
PARTITION part4 VALUES LESS THAN(201905),
PARTITION part4 VALUES LESS THAN MAXVALUE; INSERT INTO `Order` (`id`, `partition_key`, `amt`) VALUES ('1', '201901', '1000');
INSERT INTO `Order` (`id`, `partition_key`, `amt`) VALUES ('2', '201902', '800');
INSERT INTO `Order` (`id`, `partition_key`, `amt`) VALUES ('3', '201903', '1200');

RANGE分割槽通過使用PARTITION BY RANGE(expr)實現 , 其中“expr” 可以是某個列值, 或一個基於某個列值並返回一個整數值的表示式,如YEAR(date)。

不過值得注意的是,expr的返回值,不可以為NULL。

VALUES LESS THAN的排列必須從小到大順序列出,這樣MySQL才能識別一個一個的區間段。

涉及聚合函式SUM()、COUNT()的查詢時,如果不指定分割槽,那麼會在每個分割槽上並行處理。

LIST分割槽

MySQL中的LIST分割槽在很多方面類似於RANGE分割槽。

和RANGE分割槽一樣,LIST分割槽的每個分割槽必須明確定義。

它們的主要區別在於,LIST分割槽是基於枚舉出的值列表分割槽,RANGE是基於給定連續區間範圍分割槽;

LIST分割槽通過使用PARTITION BY LIST(expr)來實現 。

例如:

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)
);

如果試圖插入欄位值(或分割槽表示式的返回值)不在分割槽值列表中的任何一行時,那麼“INSERT”查詢將失敗並報錯。

要重點注意的是,LIST分割槽沒有類似如“VALUES LESS THAN MAXVALUE”這樣的包含其他值在內的定義。所以將要匹配的任何值都必須在值列表中能夠找到。

HASH分割槽

HASH分割槽主要用來確保資料在預先確定數目的分割槽中平均分佈。

在RANGE和LIST分割槽中,我們必須明確指定一個給定的區間或列值集合,來指定哪些記錄進入哪些分割槽;

而在HASH分割槽中,MySQL自動完成分配記錄到區間的工作,你所要做的只是確定一個用來做雜湊的欄位或者表示式,以及指定被分割槽的表將要被分割成的分割槽數量。

PARTITION BY HASH

例如:

CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT,
store_id INT
)
PARTITION BY HASH(store_id)
PARTITIONS 4;

如果沒有包括一個PARTITIONS子句,那麼分割槽的數量將預設為1。

最有效率的雜湊函式是隻對單個表列進行計算,並且它的結果值隨欄位值進行一致地增大或減小,因為這考慮了在分割槽範圍上的“修剪”。

也就是說,表示式值和它所基於的列的值變化越接近,MySQL就可以越有效地使用該表示式來進行HASH分割槽。

當使用了“PARTITION BY HASH”時,MySQL將基於使用者提供的函式結果的模數來確定使用哪個編號的分割槽。換句話,對於一個表示式“expr”,將要儲存記錄的分割槽編號為N ,其中“N = MOD(expr, num)”。

KEY分割槽

按照KEY進行分割槽類似於按照HASH分割槽,除了HASH分割槽使用的使用者定義的表示式,而KEY分割槽的雜湊函式是由MySQL 伺服器提供。

MySQLCluster使用函式MD5()來實現KEY分割槽;對於使用其他儲存引擎的表,伺服器使用其自己內部的 雜湊函式,這些函式是基於與PASSWORD()一樣的運演算法則。

“CREATE TABLE ... PARTITION BY KEY”的語法規則類似於建立一個通過HASH分割槽的表的規則。它們唯一的區別在於使用的關鍵字是KEY而不是HASH,並且KEY分割槽只採用一個或多個列名的一個列表。

create table user(
a int(11),
b datetime
)
partition by key(b)
partitions 4;

子分割槽

子分割槽是分割槽表中每個分割槽的再次分割。

例如:

CREATE TABLE ts (id INT, purchased DATE)
PARTITION BY RANGE(YEAR(purchased))
SUBPARTITION BY HASH(TO_DAYS(purchased))
(
PARTITION p0 VALUES LESS THAN (1990)
(
SUBPARTITION s0,
SUBPARTITION s1
),
PARTITION p1 VALUES LESS THAN (2000)
(
SUBPARTITION s2,
SUBPARTITION s3
),
PARTITION p2 VALUES LESS THAN MAXVALUE
(
SUBPARTITION s4,
SUBPARTITION s5
)
);

注意的語法項:

  1. 每個分割槽必須有相同數量的子分割槽。
  2. 如果在一個分割槽表上的某個分割槽上使用SUBPARTITION來明確定義子分割槽,那麼就必須定義其他所有分割槽的子分割槽。

子分割槽可以用於特別大的表,在多個磁碟間分配資料和索引。

然後就可以根據具體的情況來持久化:

CREATE TABLE ts (id INT, purchased DATE)
PARTITION BY RANGE(YEAR(purchased))
SUBPARTITION BY HASH(TO_DAYS(purchased))
(
PARTITION p0 VALUES LESS THAN (1990)
(
SUBPARTITION s0a
DATA DIRECTORY = '/disk0'
INDEX DIRECTORY = '/disk1',
SUBPARTITION s0b
DATA DIRECTORY = '/disk2'
INDEX DIRECTORY = '/disk3'
),
PARTITION p1 VALUES LESS THAN (2000)
(
SUBPARTITION s1a
DATA DIRECTORY = '/disk4/data'
INDEX DIRECTORY = '/disk4/idx',
SUBPARTITION s1b
DATA DIRECTORY = '/disk5/data'
INDEX DIRECTORY = '/disk5/idx'
),
PARTITION p2 VALUES LESS THAN MAXVALUE
(
SUBPARTITION s2a,
SUBPARTITION s2b
)
);
  • DATA DIRECTORY表示資料的物理檔案的存放目錄
  • INDEX DIRECTORY表示索引的物理檔案的存放目錄

分割槽的管理

MySQL提供了許多修改分割槽表的方式。新增、刪除、重新定義、合併或拆分已經存在的分割槽是可能的。

所有這些操作都可以通過使用ALTER TABLE命令的分割槽擴充套件來實現。

新增分割槽

為已建立的未分割槽表建立分割槽:

  • RANGE:ALTER TABLE tb PARTITION BY RANGE (expr) ( range_partitions_exprs(n>0) );
  • LIST:ALTER TABLE tb PARTITION BY LIST (expr) ( list_partitions_exprs(n>0) );
  • HASH:ALTER TABLE tb PARTITION BY HASH(expr) PARTITIONS 2;
  • KEY:ALTER TABLE tb PARTITION BY KEY(expr) PARTITIONS 2;

為分割槽表新增n個分割槽:

  • RANGE:ALTER TABLE tb ADD PARTITION ( range_partitions_exprs(n>0) );
  • LIST:ALTER TABLE tb ADD PARTITION ( list_partitions_exprs(n>0) );
  • HASH & KEY:ALTER TABLE tb ADD PARTITION PARTITIONS n;

調整分割槽

reorganize

資料不丟失的前提下,將m個分割槽合併為n個分割槽(m>n),即減量重新組織分割槽

  • RANGE:ALTER TABLE tb REORGANIZE PARTITION s0,s1,... INTO ( range_partitions_exprs(n) )
  • LIST:ALTER TABLE tb REORGANIZE PARTITION s0,s1,... INTO ( list_partitions_exprs(n) )
  • HASH & KEY:ALTER TABLE clients COALESCE PARTITION n; (n小於原有分割槽數)

資料不丟失的前提下,將分割槽表的m個分割槽拆分為n個分割槽(m<n),即增量重新組織分割槽

  • RANGE:ALTER TABLE tb REORGANIZE PARTITION p0,p1,... INTO ( range_partitions_exprs(n) )
  • LIST:ALTER TABLE tb REORGANIZE PARTITION p0,p1,... INTO ( list_partitions_exprs(n) )

不能使用REORGANIZE PARTITION來改變表的分割槽型別;也就是說。

重建分割槽,即先刪除分割槽中的所有記錄,然後重新插入。可用於整理分割槽碎片。

  • ALTER TABLE tb REBUILD PARTITION p0, p1;

優化分割槽,整理分割槽碎片 optimize

  • ALTER TABLE tb OPTIMIZE PARTITION p0, p1;

如從分割槽中刪除了大量的行,或者對一個帶有可變長度欄位(VARCHAR、BLOB、TEXT型別)的行作了許多修改,可以使用優化分割槽來收回沒有使用的空間,並整理分割槽資料檔案的碎片。

修復分割槽,修補被破壞的分割槽。

  • ALTER TABLE tb REPAIR PARTITION p0,p1;

檢查分割槽,這個命令可以告訴你分割槽中的資料或索引是否已經被破壞,如果被破壞,請使用修復分割槽來修補

  • ALTER TABLE tb CHECK PARTITION p1;

刪除分割槽

刪除一個分割槽,以及分割槽內的所有資料:

  • ALTER TABLE tb DROP PARTITION p2;

刪除一個分割槽,但保留分割槽內的所有資料(MySQL 5.5引入): truncate

  • ALTER TABLE tb TRUNCATE PARTITION p2;

檢視分割槽

檢視某個schema下某個表的分割槽資訊

  • SELECT * FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_SCHEMA = 'xxx' AND TABLE_NAME LIKE 'xxxx';

分析某個分割槽,主要看行數和名稱以及狀態

  • ALTER TABLE tb ANALYZE PARTITION p3;

分表

分表顧名思義,就是把一張超大的資料表,拆分為多個較小的表,得到有效的緩解。

超大表會帶來如下的影響:

  1. 單表資料量太大,會被頻繁讀寫,加鎖操作密集,導致效能降低。
  2. 單表資料量太大,對應的索引也會很大,查詢效率降低,增刪操作的效能也會降低。

分表和分割槽看起來十分類似,確實,分割槽已經能夠在磁碟層面將一張表拆分成多個檔案了,理論上前面提到的大表的問題都能得到有效解決。因為分割槽就是分表的資料庫實現版本

在MySQL 5.1分割槽功能出現以前,要想解決超大表問題,只能採用分表操作,因為這類問題十分常見,MySQL才自帶了一個分割槽功能,以達到相同的效果。

所以你可以直接說分割槽就是分表的替代,分表是分割槽出現以前的做法。不過這不代表我們就沒有必要學習分表了,相反,水平分表的功能或許可以用更加便捷的分割槽來替代,但是垂直分表的功能,分割槽卻無法替代。

分表只能通過程式程式碼來實現,目前市面上有許多分表的框架。( Apache ShardingSphere )

分表和分割槽的區別

  1. 分割槽只是一張表中的資料和索引的儲存位置發生改變,分表則是將一張表分成多張表,是真實的有多套表的配套檔案
  2. 分割槽沒法突破資料庫層面,不論怎麼分割槽,這些分割槽都要在一個數據庫下。而分表可以將子表分配在同一個庫中,也可以分配在不同庫中,突破資料庫效能的限制。
  3. 分割槽只能替代水平分表的功能,無法取代垂直分表的功能。

分表的型別

分表分為水平分表和垂直分表。

水平分表

水平分表和分割槽很像,或者說分割槽就是水平分表的資料庫實現版本,它們分的都是行記錄。

但是需要注意,如果這些表還是在同一個庫中,所以庫級別的資料庫操作還是有IO瓶頸。分表可以將單張表的資料切分到多個伺服器上去,每個伺服器具有相應的庫與子表,這是分割槽所不能有的優勢。

水平分表的切分規則一般有如下幾種:

範圍切分

  • 可以根據某個欄位的範圍做劃分,比如訂單號欄位,從0到10000一個表,10001到20000一個表。

HASH取模

  • 可以根據某個欄位的HASH取模做劃分,比如將一個使用者表分成10個子表,可以取使用者id,然後hash後取10的模,從而分配到不同的資料庫上。不過這種劃分一旦確定後,就無法改變子表數量了。

地理/國籍/型別等

  • 比如按照華東,華南,華北這樣來區分業務表,或者安卓使用者,IOS使用者等來區分使用者表。

時間

  • 按照時間切分,比如將6個月前,甚至一年前的資料切出去放到另外的一張表,因為隨著時間流逝,這些表的資料被查詢的概率變小,所以沒必要和“熱資料”放在一起,這個也是“冷熱資料分離”。

垂直分表

水平分表分的是行記錄,而垂直分表,分的是列欄位,它就像用一把刀,垂直的將一個表切成多張表一樣。

垂直分表是基於列欄位進行的。一般是表中的欄位較多,或者有資料較大長度較長(比如text,blob,varchar(1000)以上的欄位)的欄位時,我們將不常用的,或者資料量大的欄位拆分到“擴充套件表”上。這樣避免查詢時,資料量太大造成的“跨頁”問題。

垂直分表的切分規則很好理解,一般是“不常用”或者“欄位資料量大”這兩點來做切割

分庫

分庫同樣是為了應對超大資料帶來的巨大的IO需求,如果不拆庫,那麼單庫所能支援的吞吐能力和磁碟空間,就會成為制衡業務發展的瓶頸。

分庫的主要目的是為突破單節點資料庫伺服器的I/O能力限制,解決資料庫水平擴充套件性問題。

分庫作用

分割槽和分表可以把單表分到不同的硬碟上,但不能分配到不同伺服器上。一臺機器的效能是有限制的,用分庫可以解決單臺伺服器效能不夠,或者成本過高問題。

將一個庫分成多個庫,並在多個伺服器上部署,就可以突破單伺服器的效能瓶頸,這是分庫必要性的最主要原因。

分庫的型別

分庫同樣分為水平分庫和垂直分庫。

水平分庫

  • 水平分庫和水平分表相似,並且關係緊密,水平分庫就是將單個庫中的表作水平分表,然後將子表分別置於不同的子庫當中,獨立部署。
  • 因為庫中內容的主要載體是表,所以水平分庫和水平分表基本上如影隨形。
  • 例如使用者表,我們可以使用註冊時間的範圍來分表,將2020年註冊的使用者表usrtb2020部署在usrdata20中,2021年註冊的使用者表usrtb2021部署在usrdata21中。

垂直分庫

  • 同樣的,垂直分庫和垂直分表也十分類似,不過垂直分表拆分的是欄位,而垂直分庫,拆分的是表。
  • 垂直分庫是將一個庫下的表作不同維度的分類,然後將其分配給不同子庫的策略。
  • 例如,我們可以將使用者相關的表都放置在usrdata這個庫中,將訂單相關的表都放置在odrdata中,以此類推。
  • 垂直分庫的分類維度有很多,可以按照業務模組劃分(使用者/訂單...),按照技術模組分(日誌類庫/圖片類庫...),或者空間,時間等等。

問題

事務問題。

  • 問題描述:在執行分庫分表之後,由於資料儲存到了不同的庫上,資料庫事務管理出現了困難。如果依賴資料庫本身的分散式事務管理功能去執行事務,將付出高昂的效能代價;如果由應用程式去協助控制,形成程式邏輯上的事務,又會造成程式設計方面的負擔。
  • 解決方法:利用分散式事務,協調不同庫之間的資料原子性,一致性。

跨庫跨表的join問題。

  • 問題描述:在執行了分庫分表之後,難以避免會將原本邏輯關聯性很強的資料劃分到不同的表、不同的庫上,這時,表的關聯操作將受到限制,我們無法join位於不同分庫的表,也無法join分表粒度不同的表,結果原本一次查詢能夠完成的業務,可能需要多次查詢才能完成。
  • 解決方法:tddl、MyCAT等都支援跨分片join。但是我們應該盡力避免跨庫join,如果一定要整合資料,那麼請在程式碼中多次查詢完成。

額外的資料管理負擔和資料運算壓力。

  • 問題描述:額外的資料管理負擔,最顯而易見的就是資料的定位問題和資料的增刪改查的重複執行問題,這些都可以通過應用程式解決,但必然引起額外的邏輯運算,例如,對於一個記錄使用者成績的使用者資料表userTable,業務要求查出成績最好的100位,在進行分表之前,只需一個order by語句就可以搞定,但是在進行分表之後,將需要n個order by語句,分別查出每一個分表的前100名使用者資料,然後再對這些資料進行合併計算,才能得出結果。
  • 解決方法:無解,這是水平拓展的代價。