1. 程式人生 > 其它 >MySQL 分割槽表實踐

MySQL 分割槽表實踐

對使用者來說,分割槽表是一個獨立的邏輯表,但是底層由多個物理子表組成。實現分割槽的程式碼實際上是對一組底層表的控制代碼物件(Handler Object)的封裝。對分割槽表的請求,都會通過控制代碼物件轉化成對儲存引擎的介面呼叫。所以分割槽表對於 SQL 層來說是一個完全封裝底層實現的黑盒子,對應用是透明的,但是從底層的檔案系統來看就很容易發現,每個分割槽表都有一個使用 # 分隔命名的表文件。 MySQL 實現分割槽表的方式——對底層表的封裝——意味著索引頁是按照分割槽的子表定義的,而沒有全域性索引。 MySQL 在建立表時使用 PARTITION BY 子句定義每個分割槽存放的資料。在執行查詢的時候,優化器會根據分割槽定義過濾那些沒有我們需要資料的分割槽,這樣查詢就無須掃描所有分割槽。 分割槽的一個主要目的是將資料按照一個較粗的粒度分在不同的表中。這樣做可以將相關的資料存放在一起,另外,如果想一次批量刪除整個分割槽的資料也會變得很方便。

1、分割槽表

官方手冊:
https://dev.mysql.com/doc/refman/5.7/en/partitioning.html

對使用者來說,分割槽表是一個獨立的邏輯表,但是底層由多個物理子表組成。實現分割槽的程式碼實際上是對一組底層表的控制代碼物件(Handler Object)的封裝。對分割槽表的請求,都會通過控制代碼物件轉化成對儲存引擎的介面呼叫。所以分割槽表對於 SQL 層來說是一個完全封裝底層實現的黑盒子,對應用是透明的,但是從底層的檔案系統來看就很容易發現,每個分割槽表都有一個使用 # 分隔命名的表文件。

MySQL 實現分割槽表的方式——對底層表的封裝——意味著索引頁是按照分割槽的子表定義的,而沒有全域性索引。

MySQL 在建立表時使用 PARTITION BY 子句定義每個分割槽存放的資料。在執行查詢的時候,優化器會根據分割槽定義過濾那些沒有我們需要資料的分割槽,這樣查詢就無須掃描所有分割槽。

分割槽的一個主要目的是將資料按照一個較粗的粒度分在不同的表中。這樣做可以將相關的資料存放在一起,另外,如果想一次批量刪除整個分割槽的資料也會變得很方便。

應用場景:

表非常大以至於無法全部都放在記憶體中,或者只在表的最後部分有熱點資料,其他均是歷史資料。
分割槽表的資料更容易維護。分割槽獨立優化,檢查,修復等。
分割槽表的資料可以分佈在不同的物理裝置上,從而高效地利用多個硬體裝置。
可以使用分割槽表來避免某些特殊的瓶頸。例如 InnoDB 的單個索引的互斥訪問、ext3 檔案系統的 inode 鎖競爭等。
如果需要,還可以備份和恢復獨立的分割槽,這在非常大的資料集的場景下效果非常好。
可以閱讀 MySQL 官方手冊中的 “ 分割槽 ” 一節:

https://dev.mysql.com/doc/refman/5.7/en/partitioning.html

分割槽表本身的一些限制:

一個表最多隻能有 1024 個分割槽。
在 MySQL 5.1 中,分割槽表示式必須是整數,或者是返回整數的表示式。在 MySQL 5.5 中,某些場景中可以直接使用列來進行分割槽。
如果分割槽欄位中有主鍵或者唯一索引的列,那麼所有主鍵列和唯一索引列都必須包含進來。
分割槽表中無法使用外來鍵約束。
1.1、分割槽表的原理
如前所述,分割槽表由多個相關的底層表實現,這些底層表也是由控制代碼物件(Handler Object)表示,所以我們也可以直接訪問各個分割槽。

儲存引擎管理分割槽的各個底層表和管理普通表一樣(所有的底層表都必須使用相同的儲存引擎),分割槽表的索引只是在各個底層表上各自加上一個完全相同的索引。

從儲存引擎的角度來看,底層表和普通表沒有任何不同,儲存引擎也無須知道這是一個普通表還是一個分割槽表的一部分。

分割槽表上的操作按照下面的操作邏輯進行:

SELECT 查詢

當查詢一個分割槽表的時候,分割槽層先開啟並鎖住所有的底層表,優化器先判斷是否可以過濾部分分割槽,然後再呼叫對應的儲存引擎介面訪問各個分割槽的資料。

INSERT 操作

當寫入一條記錄時,分割槽層先開啟並鎖住所有的底層表,然後確定哪個分割槽接收這條記錄,再將記錄寫入對應底層表。

DELETE 操作

當刪除一條記錄時,分割槽層先開啟並鎖住所有的底層表,然後確定資料對應的分割槽,最後對相應底層表進行刪除操作。

UPDATE 操作

當更新一條記錄時,分割槽層先開啟並鎖住所有的底層表,MySQL 先確定需要更新的記錄在哪個分割槽,然後取出資料並更新,再判斷更新後的資料應該放在哪個分割槽,最後對底層表進行寫入操作,並對原資料所在的底層表進行刪除操作。

有些操作是可以支援過濾的。

例如,當刪除一條記錄時,MySQL 需要先找到這條記錄,如果 WHERE 條件恰好和分割槽表示式匹配,就可以將所有不包含著條記錄的分割槽都過濾掉。這個對 UPDATE 語句同樣有效。如果是 INSERT 操作,則本身就是隻命中一個分割槽,其他分割槽都會過濾掉。MySQL 先確定這條記錄屬於哪個分割槽,再將記錄寫入對應的底層分割槽表,無須對任何其他分割槽進行操作。

雖然每個操作都會 “ 先開啟並鎖住所有的底層表 ”,但這並不是說分割槽表在處理過程中是鎖住全表的。如果儲存引擎能夠自己實現行級鎖,例如 InnoDB,則會在分割槽層釋放對應表鎖。這個加鎖和解鎖過程與普通 InnoDB 上的查詢相似。

1.2、分割槽表的型別
官方手冊文件 :

https://dev.mysql.com/doc/refman/5.7/en/partitioning-types.html

MySQL 支援多種分割槽表。

我們看到最多的是根據範圍進行分割槽,每個分割槽表儲存落在某個範圍的記錄,分割槽表示式可以是列,也可以是包含列的表示式。

CREATE TABLE members (
    firstname VARCHAR(25) NOT NULL,
    lastname VARCHAR(25) NOT NULL,
    username VARCHAR(16) NOT NULL,
    email VARCHAR(35),
    joined DATE NOT NULL
)
PARTITION BY RANGE( YEAR(joined) ) (
    PARTITION p0 VALUES LESS THAN (1960),
    PARTITION p1 VALUES LESS THAN (1970),
    PARTITION p2 VALUES LESS THAN (1980),
    PARTITION p3 VALUES LESS THAN (1990),
    PARTITION p4 VALUES LESS THAN MAXVALUE
);

PARTITION 分割槽子句中可以使用各種函式。但是返回必須是一個明確的整數,不能是常數。

MySQL 還支援鍵值、雜湊和列表分割槽,這其中有些還支援子分割槽,不過我們在生產環境中很少見到。

我們還看到的一些其他的分割槽技術包括:

根據鍵值進行分割槽,來減少 InnoDB 的互斥量競爭。
使用數學模函式來進行分割槽,然後將資料輪詢放入不同的分割槽。
假設表有一個自增的主鍵列 id,系統根據時間將最近的熱點資料集中存放。那麼必須將時間戳包含在主鍵當中才行,而這和主鍵本身的意義相矛盾。這種情況下也可以使用這樣的分割槽表示式來實現相同的目的:HASH(id DIV 1000000) ,這將為100萬資料建立一個分割槽。這樣一方面實現了當初的分割槽目的,另一方面比起使用時間範圍分割槽還避免了一個問題,就是超過一定閾值時,如果使用時間範圍分割槽就必須新增分割槽。
1.3、如何使用分割槽表
資料量巨大,肯定不能在每次查詢的說話都掃描全表。

在資料量超大的時候,B-Tree 索引就無法起作用了。除非是索引覆蓋查詢,否則資料庫伺服器需要根據索引掃描的結果回表,查詢所有符合條件的記錄,如果資料量巨大,這將產生大量隨機 I/O ,隨之,資料庫的響應時間將大到不可接收的成都。另外,索引維護(磁碟空間、I/O 操作)的代價也非常高。

理解分割槽時還可以將其當作索引的最初形態,以代價非常小的方式定位到需要的資料在哪一片 “ 區域 ”。在這片 “ 區域 ” 中,你可以做順序掃描,可以建索引,還可以將資料快取大記憶體,等等。因為分割槽無須額外的資料結構記錄每個分割槽有哪些資料——分割槽不需要精確定位每條資料的位置,也就無須額外的資料結構——所以其代價非常低。只需要一個簡單的表示式就可以表達每個分割槽存放的是什麼資料。

為了保證大資料量的可擴充套件性,一般有下面兩個策略:

全量掃描資料,不要任何索引。

可以使用簡單的分割槽方式存放表,不要任何索引,根據分割槽的規則大致定位需要的資料位置。只要能夠使用 WHERE 條件,將需要的資料限制在少數分割槽中,則效率是很高的。當然,也需要做一些簡單的保證查詢的響應時間能夠滿足需要。使用該策略假設不用講資料完全放入到記憶體中,同時還假設選喲的資料全都在磁碟上,因為記憶體相對很小,資料很快會被擠出記憶體,所以快取起不了任何作用。這個策略使用於以正常的方式訪問大量資料的時候。警告:必須將查詢需要掃描的分割槽個數限制在一個很小的資料。
索引資料,並分離熱點。

如果資料有明顯的 “ 熱點 ”,而且除了這部分資料,其他資料很少被訪問到,那麼可以將這部分熱點資料單獨放在一個分割槽中,讓這個分割槽的資料能夠有機會都快取在記憶體中。這樣查詢就可以只訪問一個很小的分割槽表,能夠使用索引,也能夠有效地使用快取。
僅僅知道這些還不夠,MySQL 的分割槽表實現還有很多陷阱。

1.4、什麼情況下會出問題
上面我們介紹的兩個分割槽策略都基於兩個非常重要的假設:查詢都能夠過濾(prunning)掉很多額外的分割槽、分割槽本身並不會帶來很多額外的代價。而事實證明,這兩個假設在某些情況下會有問題。

下面介紹一些可能會遇到的問題:

NULL 值會使分割槽過濾無效
分割槽列和索引列不匹配
選擇分割槽的成本可能很高
開啟並鎖住所有底層表的成本可能很高
維護分割槽的成本可能很高
如上所述,分割槽表不是什麼 “ 銀彈 ”。下面是目前(5.5)分割槽實現中的一些其他限制:

所有分割槽都必須使用相同的儲存引擎。
分割槽函式中可以使用的函式和表示式也有一些限制。
某些儲存引擎不支援分割槽。
對於 MyISAM 的分割槽表,不能使用 LOAD INDEX INTO CACHE 操作。
對於 MyISAM 表,使用分割槽表時需要開啟更多的檔案描述符。
1.5、查詢優化
引入分割槽給查詢優化帶來了一個些新的思路(同時也帶來新的bug)。分割槽最大的有點就是優化器可以根據分割槽函式來過濾一些分割槽。根據粗粒度索引的優勢,通過分割槽過濾通常可以讓查詢掃描更少的資料(在某些場景下)。

所以,對於訪問分割槽表來說,很重要的一點是要在 WHERE 條件中帶入分割槽列,有時候即使看似多餘的也要帶上,這樣就可以讓優化器能夠過濾掉無須訪問的分割槽。如果沒有這些條件,MySQL 就需要讓對應儲存引擎訪問這個表的所有分割槽,如果表非常大的話,就可能會非常慢。

使用 EXPLAIN PARTITIONS 可以觀察優化器是否執行了分割槽過濾。

MySQL 只能在使用分割槽函式的列本身進行比較時才能過濾分割槽,而不能根據表示式的值來過濾分割槽,即使這個表示式就是分割槽函式也不行。

1.6、合併表
合併表(Merge table)是一種早期的、簡單的分割槽實現,和分割槽表相比有一些不同的限制,並且缺乏優化。分割槽表嚴格來說是一個邏輯上的概念,使用者無法訪問底層的各個分割槽,對使用者來說分割槽是透明的。但是合併表允許使用者單獨訪問各個子表。合併表是一種被淘汰的技術。

1.7 分表分庫的區別

引用:
https://www.cnblogs.com/GrimMjx/p/11772033.html

名優特產,盡在百味園商城!