mysql高級之表分區
下列說明為個人見解,歡迎交流指正。
1、表分區簡介
1.1 問題概述
問題引出:假設一個商城訂單系統,每年一個總表為order_year_2018,最近三個月有一個分表order_mouth_3。用戶按年份選擇訂單就到年表中查詢,按最近三個月選擇訂單就到最近三個月的分表中查詢,問如何設計年表。
為保持讀寫穩定,有兩種設計方案:
-
年表不包含近三個月的表,這樣出現的問題是查詢本年訂單時,需要跨兩張表查詢,如果后面還要連表查詢其它商品等信息再排序可能會更復雜。
-
年表包含近三個月的表數據,相當於冗余三個月的數據來提高查詢效率,這樣的問題是訂單數據發生變化時,要同時更新年表和月表,比較麻煩。
問題解決方案:表分區。
1.2 mysql對數據的存儲
mysql數據庫中的數據是以文件的形式存在磁盤上的,默認放在/mysql/var下面(可以通過my.cnf中的datadir來查看), 對存儲引擎為myisam來說,一張表主要對應着三個文件,一個是frm存放表結構的,一個是myd存放表數據的,一個是myi存表索引。
如果一張表的數據量太大的話,那么myd,myi就會變的很大,查找數據就會變的很慢,這個時候我們可以利用mysql的分區功能,在物理上將這一張表對應的三個文件,分割成許多個小塊,這樣呢,我們查找一條數據時,就不用全部查找了,只要知道這條數據在哪一塊,然后在那一塊找就行了。如果表的數據太大,可能一個磁盤放不下,這個時候,我們可以把數據分配到不同的磁盤里面去。
表分區是mysql被Oracle收購后推出的一個新特性。
1.3 mysql的表分區
1.3.1 表分區
1、分區表是什么
通俗地講表分區是將一大表,根據條件分割成若干個小表。mysql5.1開始支持數據表分區。
如:某用戶表的記錄超過了600萬條,那么就可以根據入庫日期將表分區,也可以根據所在地將表分區。當然也可根據其他的條件分區。
2、分區表的原理
分區表是由多個相關的底層表實現,這些底層表也是由句柄對象表示,所以我們也可以直接訪問各個分區,存儲引擎管理分區的各個底層表和管理普通表一樣(所有的底層表都必須使用相同的存儲引擎),分區表的索引只是在各個底層表上各自加上一個相同的索引,從存儲引擎的角度來看,底層表和一個普通表沒有任何不同,存儲引擎也無須知道這是一個普通表還是一個分區表的一部分。
在分區表上的操作按照下面的操作邏輯進行:
-
select查詢:當查詢一個分區表的時候,分區層先打開並鎖住所有的底層表,優化器判斷是否可以過濾部分分區,然后再調用對應的存儲引擎接口訪問各個分區的數據。
-
insert操作:當寫入一條記錄時,分區層打開並鎖住所有的底層表,然后確定哪個分區接受這條記錄,再將記錄寫入對應的底層表。
-
delete操作:當刪除一條記錄時,分區層先打開並鎖住所有的底層表,然后確定數據對應的分區,最后對相應底層表進行刪除操作。
-
update操作:當更新一條數據時,分區層先打開並鎖住所有的底層表,mysql先確定需要更新的記錄在哪個分區,然后取出數據並更新,再判斷更新后的數據應該放在哪個分區,然后對底層表進行寫入操作,並對原數據所在的底層表進行刪除操作
雖然每個操作都會打開並鎖住所有的底層表,但這並不是說分區表在處理過程中是鎖住全表的,如果存儲引擎能夠自己實現行級鎖,如:innodb,則會在分區層釋放對應的表鎖,這個加鎖和解鎖過程與普通Innodb上的查詢類似。
1.3.2 表分區的優劣勢
1、優勢
為了改善大型表以及具有各種訪問模式的表的可伸縮性,可管理性和提高數據庫效率。
分區的一些優點包括:
- 與單個磁盤或文件系統分區相比,可以存儲更多的數據。
- 對於那些已經失去保存意義的數據,通常可以通過刪除與那些數據有關的分區,很容易地刪除那些數據。相反地,在某些情況下,添加新數據的過程又可以通過為那些新數據專門增加一個新的分區,來很方便地實現。
通常和分區有關的其他優點包括下面列出的這些。MySQL分區中的這些功能目前還沒有實現,但是在我們的優先級列表中,具有高的優先級;我們希望在5.1的生產版本中,能包括這些功能。
- 一些查詢可以得到極大的優化,這主要是借助於滿足一個給定WHERE語句的數據可以只保存在一個或多個分區內,這樣在查找時就不用查找其他剩余的分區。因為分區可以在創建了分區表后進行修改,所以在第一次配置分區方案時還不曾這么做時,可以重新組織數據,來提高那些常用查詢的效率。
- 涉及到例如SUM()和COUNT()這樣聚合函數的查詢,可以很容易地進行並行處理。這種查詢的一個簡單例子如 “SELECT salesperson_id, COUNT (orders) as order_total FROM sales GROUP BY salesperson_id;”。通過“並行”,這意味着該查詢可以在每個分區上同時進行,最終結果只需通過總計所有分區得到的結果。
- 通過跨多個磁盤來分散數據查詢,來獲得更大的查詢吞吐量。
2、劣勢
限制暫且歸位劣勢。
- 一個表最多只能有1024個分區(mysql5.6之后支持8192個分區)。
- 在mysql5.1中分區表達式必須是整數,或者是返回整數的表達式,在5.5之后,某些場景可以直接使用字符串列和日期類型列來進行分區(使用varchar字符串類型列時,一般還是字符串的日期作為分區)。
- 如果分區字段中有主鍵或者唯一索引列,那么所有主鍵列和唯一索引列都必須包含進來,如果表中有主鍵或唯一索引,那么分區鍵必須是主鍵或唯一索引。
- 分區表中無法使用外鍵約束。
- mysql數據庫支持的分區類型為水平分區,並不支持垂直分區,因此,mysql數據庫的分區中索引是局部分區索引,一個分區中既存放了數據又存放了索引,而全局分區是指的數據庫放在各個分區中,但是所有的數據的索引放在另外一個對象中
- 目前mysql不支持空間類型和臨時表類型進行分區。不支持全文索引。
1.3.3 表分區的類型
- RANGE分區:基於屬於一個給定連續區間的列值,把多行分配給分區。
- LIST分區:類似於按RANGE分區,區別在於LIST分區是基於列值匹配一個離散值集合中的某個值來進行選擇。
- HASH分區:基於用戶定義的表達式的返回值來進行選擇的分區,該表達式使用將要插入到表中的這些行的列值進行計算。這個函數可以包含MySQL 中有效的、產生非負整數值的任何表達式。
- KEY分區:類似於按HASH分區,區別在於KEY分區只支持計算一列或多列,且MySQL 服務器提供其自身的哈希函數。必須有一列或多列包含整數值。
1、RANGE分區(常用)
基於屬於一個給定連續區間的列值,把多行分配給分區。這些區間要連續且不能相互重疊,使用VALUES LESS THAN操作符來進行定義。以下是實例。
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 not null, store_id int not null ) partition by range (store_id) ( partition p0 values less than (6), partition p1 values less than (11), partition p2 values less than (16), partition p3 values less than (21), partition p3 values less than maxvalue );
按照這種分區方案,在商店1到5工作的雇員相對應的所有行被保存在分區P0中,商店6到10的雇員保存在P1中,依次類推。注意,每個分區都是按順序進行定義,從最低到最高。這是PARTITION BY RANGE 語法的要求;在這點上,它類似於C或Java中的“switch ... case”語句。
2、LIST分區
類似於按RANGE分區,區別在於LIST分區是基於列值匹配一個離散值集合中的某個值來進行選擇。
LIST分區通過使用“PARTITION BY LIST(expr)”來實現,其中“expr”是某列值或一個基於某個列值、並返回一個整數值的表達式,然后通過“VALUES IN (value_list)”的方式來定義每個分區,其中“value_list”是一個通過逗號分隔的整數列表。
注釋:在MySQL 5.1中,當使用LIST分區時,有可能只能匹配整數列表。
假定有20個音像店,分布在4個有經銷權的地區,如下所示:
====================================
地區 商店ID 號
------------------------------------
北區 3, 5, 6, 9, 17
東區 1, 2, 10, 11, 19, 20
西區 4, 12, 13, 14, 18
中心區 7, 8, 15, 16
====================================
要按照屬於同一個地區商店的行保存在同一個分區中的方式來分割表,可以使用下面的“CREATE TABLE”語句:
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 not null, store_id int not null ) partition by list(store_id) partition pNorth values in (3,5,6,9,17), partition pEast values in (1,2,10,11,19,20), partition pWest values in (4,12,13,14,18), partition pCentral values in (7,8,15,16) );
這使得在表中增加或刪除指定地區的雇員記錄變得容易起來。例如,假定西區的所有音像店都賣給了其他公司。那么與在西區音像店工作雇員相關的所有記錄(行)可以使用查詢“ALTER TABLE employees DROP PARTITION pWest;”來進行刪除,它與具有同樣作用的DELETE (刪除)查詢“DELETE query DELETE FROM employees WHERE store_id IN (4,12,13,14,18);”比起來,要有效得多。
要重點注意的是,LIST分區沒有類似如“VALUES LESS THAN MAXVALUE”這樣的包含其他值在內的定義。將要匹配的任何值都必須在值列表中找到。
LIST分區除了能和RANGE分區結合起來生成一個復合的子分區,與HASH和KEY分區結合起來生成復合的子分區也是可能的。
3、HASH分區
基於用戶定義的表達式的返回值來進行選擇的分區,該表達式使用將要插入到表中的這些行的列值進行計算。這個函數可以包含MySQL 中有效的、產生非負整數值的任何表達式。
要使用HASH分區來分割一個表,要在CREATE TABLE 語句上添加一個“PARTITION BY HASH (expr)”子句,其中“expr”是一個返回一個整數的表達式。它可以僅僅是字段類型為MySQL 整型的一列的名字。此外,你很可能需要在后面再添加一個“PARTITIONS num”子句,其中num 是一個非負的整數,它表示表將要被分割成分區的數量。
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 not null, store_id int not null ) partition by hash(store_id) partitions 4;
如果沒有包括一個“PARTITIONS num”子句,那么分區的數量將默認為1。
4、KSY分區
類似於按HASH分區,區別在於KEY分區只支持計算一列或多列,且MySQL 服務器提供其自身的哈希函數。必須有一列或多列包含整數值。
create table tk ( col1 int not null, col2 char(5), col3 date ) partition by linear key (col1) partitions 3;
在KEY分區中使用關鍵字LINEAR和在HASH分區中使用具有同樣的作用,分區的編號是通過2的冪(powers-of-two)算法得到,而不是通過模數算法。
1.4 表分區的相關操作
1.4.1 建立表分區
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 not null, store_id int not null ) partition by range (store_id) ( partition p0 values less than (6), partition p1 values less than (11), partition p2 values less than (16), partition p3 values less than (21), partition p3 values less than maxvalue );
1.4.2 增加表分區
PARTITION p1 VALUES LESS THAN (MAXVALUE) 這句要去掉,才可以增加分區。 ALTER TABLE sale_data ADD PARTITION (PARTITION s20100402 VALUES LESS THAN (20100403));
1.4.3 刪除表分區
ALTER TABLE sale_data DROP PARTITION s20100406 ;
1.4.4 正常使用
insert into sale_data values('2010-04-01','11',11.11); insert into sale_data values('2010-04-02','22',22.22);
1.4.5 查看分區
SELECT PARTITION_NAME,TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'sale_data';