mysql數據庫分表分區

防僞碼:博觀而約取,厚積而薄發

爲什麼要分表和分區?

我們的數據庫數據越來越大,隨之而來的是單個表中數據太多。以至於查詢書讀變慢,而且

由於表的鎖機制導致應用操作也搜到嚴重影響,出現了數據庫性能瓶頸。

mysql 中有一種機制是表鎖定和行鎖定,是爲了保證數據的完整性。表鎖定表示你們都不能

對這張表進行操作,必須等我對錶操作完才行。行鎖定也一樣,別的 sql 必須等我對這條數

據操作完了,才能對這條數據進行操作。當出現這種情況時,我們可以考慮分表或分區。

1、分表

什麼是分表?

分表是將一個大表按照一定的規則分解成多張具有獨立存儲空間的實體表,每個表都對應三

個文件,MYD 數據文件,.MYI 索引文件,.frm 表結構文件。這些表可以分佈在同一塊磁盤

上,也可以在不同的機器上。app 讀寫的時候根據事先定義好的規則得到對應的表名,然後

去操作它。

將單個數據庫表進行拆分,拆分成多個數據表,然後用戶訪問的時候,根據一定的算法(如

hash 的方式,也可以用求餘(取模)的方式),讓用戶訪問不同的表,這樣數據分散到多

個數據表中,減少了單個數據表的訪問壓力。提升了數據庫訪問性能。分表的目的就在於此,

減小數據庫的負擔,縮短查詢時間。

Mysql 分表分爲垂直切分和水平切分

垂直切分是指數據表列的拆分,把一張列比較多的表拆分爲多張表

通常我們按以下原則進行垂直拆分:

把不常用的字段單獨放在一張表;

textblobbinary large object,二進制大對象)等大字段拆分出來放在附表中;

經常組合查詢的列放在一張表中;

垂直拆分更多時候就應該在數據表設計之初就執行的步驟,然後查詢的時候用 jion 關鍵起來

即可。

水平拆分是指數據錶行的拆分,把一張的表的數據拆成多張表來存放。

水平拆分原則

通常情況下,我們使用 hash、取模等方式來進行表的拆分

比如一張有 400W 的用戶表 users,爲提高其查詢效率我們把其分成 4 張表 users1users2

users3users4

通過用 ID 取模的方法把數據分散到四張表內 Id%4= [0,1,2,3]

然後查詢,更新,刪除也是通過取模的方法來查詢

部分業務邏輯也可以通過地區,年份等字段來進行歸檔拆分;

進行拆分後的表,這時我們就要約束用戶查詢行爲。比如我們是按年來進行拆分的,這個時

候在頁面設計上就約束用戶必須要先選擇年,然後才能進行查詢。

分表的幾種方式:

1mysql 集羣

它並不是分表,但起到了和分表相同的作用。集羣可分擔數據庫的操作次數,將任務分擔到

多臺數據庫上。集羣可以讀寫分離,減少讀寫壓力。從而提升數據庫性能。

2)預先估計會出現大數據量並且訪問頻繁的表,將其分爲若干個表

根據一定的算法(如用 hash 的方式,也可以用求餘(取模)的方式)讓用戶訪問不同的表。

例如論壇裏面發表帖子的表,時間長了這張表肯定很大,幾十萬,幾百萬都有可能。聊天室裏面信息表,幾十個人在一起一聊一個晚上,時間長了,這張表的數據肯定很大。像這樣的

情況很多。所以這種能預估出來的大數據量表,我們就事先分出個 N 個表,這個 N 是多少,

根據實際情況而定。以聊天信息表爲例:我們事先建 100 個這樣的表,

message_00,message_01,message_02..........message_98,message_99.然後根據用戶的 ID 來判

斷這個用戶的聊天信息放到哪張表裏面,可以用 hash 的方式來獲得,也可以用求餘的方式

來獲得,方法很多。

或者可以設計每張表容納的數據量是 N 條,那麼如何判斷某張表的數據是否容量已滿呢?

可以在程序段對於要新增數據的表,在插入前先做統計表記錄數量的操作,當<N 條數據,

就直接插入,當已經到達閥值,可以在程序段新創建數據庫表(或者已經事先創建好),再

執行插入操作)。

3)利用 merge 存儲引擎來實現分表

如果要把已有的大數據量表分開比較痛苦,最痛苦的事就是改代碼,因爲程序裏面的 sql

句已經寫好了,用 merge 存儲引擎來實現分表, 這種方法比較適合。

merge 分表,分爲主表和子表,主表類似於一個殼子,邏輯上封裝了子表,實際上數據都是

存儲在子表中的。

我們可以通過主表插入和查詢數據,如果清楚分表規律,也可以直接操作子表。

下面我們來實現一個簡單的利用 merge 存儲引擎來實現分表的演示:

創建一個完整表存儲着所有的成員信息(表名爲 member

                             wKiom1iy2GSSCMJIAAECMZbwKcY123.png-wh_50

加入點數據:

wKiom1iy2HejruToAAJONqP1zu0330.png-wh_50

第二條語句多執行幾次就有了很多數據

wKioL1iy2IqgkSdfAACRzcWLic0932.png-wh_50

下面我們進行分表,這裏我們把 member 分兩個表 tb_member1,tb_member2

wKiom1iy2J2iRAtKAADhOyir69E966.png-wh_50

創建主表 tb_member

wKioL1iy2MjQYmoSAAC8MljijBE607.png-wh_50

注:INSERT_METHOD,此參數 INSERT_METHOD = NO 表示該表不能做任何寫入操作只作爲查

詢使用,INSERT_METHOD= LAST 表示插入到最後的一張表裏面。INSERT_METHOD= first 表示

插入到第一張表裏面。

查看一下 tb_member 表的結構:

wKiom1iy2OqAjF18AACf7OoenM0612.png-wh_50

注:查看子表與主表的字段定義要一致

接下來,我們把數據分到兩個分表中去:

wKioL1iy2P2wZf4qAADxBqzmvuU790.png-wh_50

查看兩個子表的數據

wKiom1iy2RCDDn-HAAB_c6RkoPE392.png-wh_50

wKioL1iy2SWRO8TyAAB4kf1L31w547.png-wh_50

查看一下主表的數據:

wKioL1iy2TjRYKvVAACYQvS6Qdg449.png-wh_50

wKiom1iy2UyDzdelAABgel8xvE4884.png-wh_50

注意:總表只是一個外殼,存取數據發生在一個一個的子表裏面。

注意:每個子表都有自已獨立的相關表文件,而主表只是一個殼,並沒有完整的相關表文件

wKioL1iy2XWjCIGhAADAdusjqGg291.png-wh_50

2、分區

什麼是分區?

分區和分表相似,都是按照規則分解表。不同在於分表將大表分解爲若干個獨立的實體表,

而分區是將數據分段劃分在多個位置存放,分區後,表還是一張表,但數據散列到多個位置

了。app 讀寫的時候操作的還是表名字,db 自動去組織分區的數據。

分區主要有兩種形式:

水平分區(Horizontal Partitioning)這種形式分區是對錶的行進行分區,所有在表中定義的

列在每個數據集中都能找到,所以表的特性依然得以保持。

舉個簡單例子:一個包含十年發票記錄的表可以被分區爲十個不同的分區,每個分區包含的是其中一年的記錄。

垂直分區(Vertical Partitioning)這種分區方式一般來說是通過對錶的垂直劃分來減少目標表

的寬度,使某些特定的列被劃分到特定的分區,每個分區都包含了其中的列所對應的行。

舉個簡單例子:一個包含了大 text BLOB 列的表,這些 text BLOB 列又不經常被訪問,

這時候就要把這些不經常使用的 text BLOB 了劃分到另一個分區,在保證它們數據相關性

的同時還能提高訪問速度。

分區技術支持

5.6 之前,使用這個參數查看當將配置是否支持分區

mysql>SHOW VARIABLES LIKE '%partition%';

+-----------------------+---------------+

|Variable_name| Value |

+-----------------------+---------------+

|have_partition_engine | YES |

+-----------------------+------------------+

如果是 yes 表示你當前的配置支持分區

5.6 及以採用後,則採用如下方式進行查看

mysql>show plugins;

wKiom1iy2Y-zgeV3AACJi1vIrwE367.png-wh_50

在顯示結果中,可以看到 partition ACTIVE 的,表示支持分區

下面我們先演示一個按照範圍(range)方式的表分區

創建 range 分區表

wKiom1iy2aXz7Ms2AAExkQ30Tpo292.png-wh_50

插入些數據

wKioL1iy2b_h-XA4AALJqeBT-hg873.png-wh_50

到存放數據庫表文件的地方看一下

wKioL1iy2dTBdsPsAACkbm5x3ec217.png-wh_50

wKiom1iy2erhIKPeAABfx2bKNIc631.png-wh_50

information_schema 系統庫中的 partitions 表中查看分區信息

mysql>select * from information_schema.partitions where table_schema='test2' and

table_name='user'\G;

從某個分區中查詢數據

mysql>select * from test2.user partition(p0);

新增分區

mysql>alter table test2.user add partition (partition partionname values less than(n));

刪除分區

當刪除了一個分區,也同時刪除了該分區中所有的數據。

ALTERTABLE test2.user DROP PARTITION p3;

 

分區的合併

下面的 SQL,將 p1 p3 合併爲 2 個分區 p01 p02

wKiom1iy2jeiopSyAAEDVj85G24907.png-wh_50

未分區表和分區表性能測試

創建一個未分區的表

mysql>create table test2.tab19(c1 int,c2 varchar(30),c3 date);

創建分區表,按日期的年份拆分

wKioL1iy2k7wwcqGAADf9BH6eC0760.png-wh_50

注意:最後一行,考慮到可能的最大值

通過存儲過程插入 100 萬條測試數據

創建存儲過程:

wKioL1iy2mOzE1PCAACyKzVkGpU624.png-wh_50

注:RAND()函數在 0 1 之間產生一個隨機數,如果一個整數參數 N 被指定,它被用作種

子值。每個種子產生的隨機數序列是不同的。

執行存儲過程 load_part_tab test2.tab19 表插入數據

wKiom1iy2nfj8KNeAABavKvQXSc360.png-wh_50

test2.tab2 表中插入數據

wKioL1iy2oyi7XqtAAB4VwwfuD4363.png-wh_50

測試 SQL 性能

wKiom1iy2qahdIRfAAEBLancQAI895.png-wh_50

wKioL1iy2smzgJyCAAD7o5iK6lo790.png-wh_50

結果表明分區表比未分區表的執行時間少很多。

通過 explain 語句來分析執行情況

wKiom1iy2vjw79rKAAD28RKJMTg112.png-wh_50

wKiom1iy2w_hiKFiAADxDSqfymg210.png-wh_50

explain語句顯示了 SQL 查詢要處理的記錄數目可以看出分區表比未分區表的明顯掃描的記

錄要少很多。

創建索引後情況測試

wKiom1iy2zKALtj7AAFBg63xECk264.png-wh_50

重啓mysqld服務

wKioL1iy20rzs5-0AABxzu7g9Bk305.png-wh_50

創建索引後分區表比未分區表相差不大(數據量越大差別會明顯些)

mysql分區的類型

1.RANGE分區

基於屬於一個給定連續區間的列值,把多行分配給分區。這些區間要連續且不能相互重疊,

使用 VALUES LESS THAN 操作符來進行定義。以下是實例。

wKioL1iy22DQHfeWAAD_iWxmBwo115.png-wh_50

按照這種分區方案,在商店 1 5 工作的僱員相對應的所有行被保存在分區 P0 中,商店 6

10 的僱員保存在 P1 中,依次類推。注意,每個分區都是按順序進行定義,從最低到最高。

對於包含數據(72, 'Michael', 'Widenius','1998-06-25', NULL, 13)的一個新行,可以很容易地確

定它將插入到 p2 分區中,但是如果增加了一個編號爲第 21 的商店,將會發生什麼呢?在這

種方案下,由於沒有規則把 store_id 大於 20 的商店包含在內,服務器將不知道把該行保存

在何處,將會導致錯誤。要避免這種錯誤,可以創建 maxvalue 分區,所有不在指定範圍內

的記錄都會被存儲到 maxvalue 所在的分區中。

mysql>alter table test2.user add partition (partition p4 values less than maxvalue);

2.LIST分區

類似於按 RANGE 分區,區別在於 LIST 分區是基於列值匹配一個離散值集合中的某個值來進

行選擇。

LIST分區通過使用“PARTITION BY LIST(expr)”來實現,其中“expr 是某列值或一個基於某個列

值、並返回一個整數值的表達式,然後通過“VALUES IN (value_list)”的方式來定義每個分區,

其中“value_list”是一個通過逗號分隔的整數列表。

要按照屬於同一個地區商店的行保存在同一個分區中的方式來分割表,可以使用下面的“CREATE TABLE”語句:

wKiom1iy2_Hxe9bCAAEvAzDPubg230.png-wh_50

這使得在表中增加或刪除指定地區的僱員記錄變得容易起來。例如,假定西區的所有音像店都賣給了其他公司。那麼與在西區音像店工作僱員相關的所有記錄(行)可以使用查詢“ALTER

TABLEemployees DROP PARTITION pWest;”來進行刪除,它與具有同樣作用的 DELETE (刪

除)查詢“DELETE query DELETE FROM employeesWHERE store_id IN (4,12,13,14,18);”比起來,

要有效得多。

要點:如果試圖插入列值不在分區值列表中的一行時,那麼“INSERT”查詢將失敗並報錯。例

如,假定 LIST 分區的採用上面的方案,下面的插入將失敗:

INSERTINTO employees VALUES(224, 'Linus', 'Torvalds', '2002-05-01', '2004-10-12', 42,21);

這是因爲“store_id”列值 21 不能在用於定義分區 pNorth, pEast, pWest, pCentral 的值列表中

找到。要重點注意的是,LIST 分區沒有類似如“VALUES LESS THAN MAXVALUE”這樣的包含其

他值在內的定義。將要匹配的任何值都必須在值列表中找到。

3.HASH分區

這種模式允許 DBA 通過對錶的一個或多個列的 Hash Key 進行計算,最後通過這個 Hash 碼不

同數值對應的數據區域進行分區。

hash分區的目的是將數據均勻的分佈到預先定義的各個分區中,保證各分區的數據量大致

一致。在 RANGE LIST 分區中,必須明確指定一個給定的列值或列值集合應該保存在哪個

分區中;而在 HASH 分區中,MYSQL 自動完成這些工作,用戶所要定一個列值或者表達式,

以及指定被分區的表將要被分割成的分區數量。

wKioL1iy3AiQtfSiAABrMul3D2E836.png-wh_50

hash的分區函數頁需要返回一個整數值。partitions 子句中的值是一個非負整數,不加的

partitions子句的話,默認爲分區數爲 1

wKiom1iy3B2RORKvAABQuxQBBQU627.png-wh_50

該記錄會被放入分區 p2 中。因爲插入 2010-04-01 進入表 t_hash,那麼

MOD(YEAR('2010-04-01'),4)=2

mysql>select * from information_schema.partitions where table_schema='test2' andtable_name='t_hash'\G;

***************************1. row ***************************

                TABLE_CATALOG: def

                 TABLE_SCHEMA: test2

                   TABLE_NAME: t_hash

               PARTITION_NAME: p0

            SUBPARTITION_NAME: NULL

   PARTITION_ORDINAL_POSITION: 1

SUBPARTITION_ORDINAL_POSITION:NULL

             PARTITION_METHOD: HASH

          SUBPARTITION_METHOD: NULL

         PARTITION_EXPRESSION: year(b)

      SUBPARTITION_EXPRESSION: NULL

        PARTITION_DESCRIPTION: NULL

                   TABLE_ROWS: 0

               AVG_ROW_LENGTH: 0

                  DATA_LENGTH: 16384

              MAX_DATA_LENGTH: NULL

                 INDEX_LENGTH: 0

                    DATA_FREE: 0

                  CREATE_TIME: 2017-02-2621:11:08

                  UPDATE_TIME: NULL

                   CHECK_TIME: NULL

                     CHECKSUM: NULL

            PARTITION_COMMENT:

                    NODEGROUP: default

              TABLESPACE_NAME: NULL

***************************2. row ***************************

                TABLE_CATALOG: def

                 TABLE_SCHEMA: test2

                   TABLE_NAME: t_hash

               PARTITION_NAME: p1

            SUBPARTITION_NAME: NULL

   PARTITION_ORDINAL_POSITION: 2

SUBPARTITION_ORDINAL_POSITION:NULL

             PARTITION_METHOD: HASH

          SUBPARTITION_METHOD: NULL

         PARTITION_EXPRESSION: year(b)

      SUBPARTITION_EXPRESSION: NULL

        PARTITION_DESCRIPTION: NULL

                   TABLE_ROWS: 0

               AVG_ROW_LENGTH: 0

                  DATA_LENGTH: 16384

              MAX_DATA_LENGTH: NULL

                 INDEX_LENGTH: 0

                    DATA_FREE: 0

                  CREATE_TIME: 2017-02-2621:11:08

                  UPDATE_TIME: NULL

                   CHECK_TIME: NULL

                     CHECKSUM: NULL

            PARTITION_COMMENT:

                    NODEGROUP: default

              TABLESPACE_NAME: NULL

***************************3. row ***************************

                TABLE_CATALOG: def

                 TABLE_SCHEMA: test2

                   TABLE_NAME: t_hash

               PARTITION_NAME: p2

            SUBPARTITION_NAME: NULL

   PARTITION_ORDINAL_POSITION: 3

SUBPARTITION_ORDINAL_POSITION:NULL

             PARTITION_METHOD: HASH

          SUBPARTITION_METHOD: NULL

         PARTITION_EXPRESSION: year(b)

      SUBPARTITION_EXPRESSION: NULL

        PARTITION_DESCRIPTION: NULL

                   TABLE_ROWS: 1

               AVG_ROW_LENGTH: 16384

                  DATA_LENGTH: 16384

              MAX_DATA_LENGTH: NULL

                 INDEX_LENGTH: 0

                    DATA_FREE: 0

                  CREATE_TIME: 2017-02-2621:11:08

                  UPDATE_TIME: 2017-02-2621:11:58

                   CHECK_TIME: NULL

                     CHECKSUM: NULL

            PARTITION_COMMENT:

                    NODEGROUP: default

              TABLESPACE_NAME: NULL

***************************4. row ***************************

                TABLE_CATALOG: def

                 TABLE_SCHEMA: test2

                   TABLE_NAME: t_hash

               PARTITION_NAME: p3

            SUBPARTITION_NAME: NULL

   PARTITION_ORDINAL_POSITION: 4

SUBPARTITION_ORDINAL_POSITION:NULL

             PARTITION_METHOD: HASH

          SUBPARTITION_METHOD: NULL

         PARTITION_EXPRESSION: year(b)

      SUBPARTITION_EXPRESSION: NULL

        PARTITION_DESCRIPTION: NULL

                   TABLE_ROWS: 0

               AVG_ROW_LENGTH: 0

                  DATA_LENGTH: 16384

              MAX_DATA_LENGTH: NULL

                 INDEX_LENGTH: 0

                    DATA_FREE: 0

                  CREATE_TIME: 2017-02-2621:11:08

                  UPDATE_TIME: 2017-02-26 21:11:58

                   CHECK_TIME: NULL

                     CHECKSUM: NULL

            PARTITION_COMMENT:

                    NODEGROUP: default

              TABLESPACE_NAME: NULL

4rows in set (0.03 sec)

可以看到 P2 分區有一條記錄。當前這個例子並不能把數據均勻的分佈到各個分區,因爲按

YEAR 函數進行的,該值本身是離散的。如果對連續的值進行 HASH 分區,如自增長的主

鍵,則可以較好地將數據平均分佈。

4.key分區

key分區和 hash 分區相似,不同在於 hash 分區是用戶自定義函數進行分區,key 分區使用

mysql數據庫提供的函數進行分區,NDB cluster 使用 MD5 函數來分區,對於其他存儲引擎

mysql使用內部的 hash 函數。

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

上面的 RANGELISTHASHKEY 四種分區中,分區的條件必須是×××,如果不是×××需要

通過函數將其轉換爲×××。

5.columns分區

mysql-5.5開始支持 COLUMNS 分區,可視爲 RANGE LIST 分區的進化,COLUMNS 分區可以

直接使用非×××數據進行分區。COLUMNS 分區支持以下數據類型:

所有×××,如 INT SMALLINT TINYINT BIGINTFLOAT DECIMAL 則不支持。

日期類型,如 DATE DATETIME。其餘日期類型不支持。

字符串類型,如 CHARVARCHARBINARY VARBINARYBLOB TEXT 類型不支持。

COLUMNS可以使用多個列進行分區。

mysql 分表和分區有什麼區別呢

1、實現方式上

a mysql 的分表是真正的分表,一張表分成很多表後,每一個小表都是完整的一張表,都

對應三個文件,一個.MYD 數據文件,.MYI 索引文件,.frm 表結構文件。

b分區不一樣,一張大表進行分區後,他還是一張表,不會變成二張表,但是他存放數據

的區塊變多了

2、數據處理上

a)分表後,數據都是存放在分表裏,總表只是一個外殼,存取數據發生在一個一個的分表

裏面。

b)分區呢,不存在分表的概念,分區只不過把存放數據的文件分成了許多小塊,分區後的

表呢,還是一張表,數據處理還是由自己來完成。

3、提高性能上

a)分表後,單表的併發能力提高了,磁盤 I/O 性能也提高了。併發能力爲什麼提高了呢,

因爲查尋一次所花的時間變短了,如果出現高併發的話,總表可以根據不同的查詢,將併發

壓力分到不同的小表裏面。

bmysql 提出了分區的概念,主要是想突破磁盤 I/O 瓶頸,想提高磁盤的讀寫能力,來增加

mysql 性能。

在這一點上,分區和分表的測重點不同,分表重點是存取數據時,如何提高 mysql 併發能力

上;而分區呢,如何突破磁盤的讀寫能力,從而達到提高 mysql 性能的目的。

4、實現的難易度上

a)分表的方法有很多,用 merge 來分表,是最簡單的一種方式。這種方式跟分區難易度差

不多,並且對程序代碼來說可以做到透明的。如果是用其他分表方式就比分區麻煩了。

b)分區實現是比較簡單的,建立分區表,根建平常的表沒什麼區別,並且對開代碼端來說

是透明的。

mysql 分表和分區有什麼聯繫?

1.都能提高 mysql 的性高,在高併發狀態下都有一個良好的表現。

2.分表和分區不矛盾,可以相互配合的,對於那些大訪問量,並且表數據比較多的表,我

們可以採取分表和分區結合的方式,訪問量不大,但是表數據很多的表,我們可以採取分區

的方式等。

3.分表技術是比較麻煩的,需要手動去創建子表,app 服務端讀寫時候需要計算子表名。採

merge 好一些,但也要創建子表和配置子表間的 union 關係。

4.表分區相對於分表,操作方便,不需要創建子表。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章