目的
分區表的主要目的是方便數據的維護,而不是提升 MySQL 數據庫的性能。
《高性能MySQL》中:分區的一個主要目的是將數據按照一個較粗的粒度分在不同的表中,這樣做可以將相關的數據放在一起,另外,如果想一次批量刪除整個分區的數據也會變得很方便。
定義
對用戶來說,分區表是一個獨立的邏輯表,但是底層由多個物理子表組成。實現分區的代碼實際上是對一組底層表的句柄對象(Handle Object)的封裝。對分區表的請求,都是通過句柄對象轉化成對存儲引擎的接口的調用。當前 MySQL 數據庫支持的分區函數類型有 RANGE、LIST、HASH、KEY、COLUMNS。無論選擇哪種分區函數,都要指定相關列成為分區算法的輸入條件,這些列就叫“分區列”。
MySQL實現分區的方式——對底層表的封裝——意味着索引也是按照分區的子表定義的,而沒有全局索引。MySQL在創建表時使用PARTITION BY子句定義每個分區存放的數據。在執行查詢的時候,優化器會根據分區定義過濾那些沒有我們需要數據的分區,這樣查詢就無須掃描所有分區—只需要查詢包含需要數據的分區就可以了。
理解分區時可以將其當作索引的最初形態,以代價非常小的方式定位到需要的數據在哪一片“區域”。
優點
(1)查詢優化:分區最大的優點是在執行查詢的時候,優化器會根據分區定義過濾那些沒有我們需要數據的分區,可以讓查詢掃描更少的數據(在某些情況)。所以對於訪問分區表來說,很重要的一點就是要在WHERE條件中帶入分區列,有時候即使看似多余也要帶上,這樣就可以讓優化器能過過濾掉無須訪問的分區。如果沒有這些條件,就會訪問所有分區。
注意:MySQL只能在使用分區函數的列本身進行比較時才能過濾分區,而不能根據表達式的值去過濾分區,即使這個表達式就是分區函數也不行。這就和查詢中使用獨立的列才能使用索引的道理一樣。
(2)分區表的數據更容易維護。例如想批量刪除大量數據可以使用清除整個分區的方式。另外,還可以對一個獨立分區進行優化、檢查、修復等操作。
(3)分區表的數據可以分布在不同的物理設備上,從而高效地利用多個硬件設備。
(4)可以使用分區表來避免某些特殊的瓶頸,例如InnoDB的單個索引的互斥訪問,ext3文件系統的inode鎖競爭等。
(5)如果需要,還可以備份和恢復獨立的分區,這在非常大的數據集的場景下效果非常好。
使用場景
表非常大以至無法全部都放在內存中,或者只在表的最后部分有熱點數據,其他都是歷史數據。Eg:假設我們希望從一個非常大的表中查詢出一段時間的記錄(好比查詢10億條記錄的表中最近幾個月的數據),而這個表中包含了很多年的歷史數據,數據是按照時間排序的。因為數據量巨大,肯定不能在每次查詢的時候都掃描全表。考慮到索引在空間和維護上的消耗,也不希望使用索引。
注意:當數據量超大的時候,B-Tree索引就無法起作用了。除非是索引覆蓋查詢,否則數據庫服務器需要根據索引掃描的結果回表,查詢所有符合條件的記錄,如果數據量巨大,這將產生大量隨機I/O,隨之,數據庫的響應時間將大到不可接受的程度。另外,索引維護(磁盤空間、I/O操作)的代價也非常高。
重要限制
(1)一個表最多只能有1024個分區。
(2)在MySQL5.1中分區表達式必須是整數,或者是返回整數的表達式,MySQL5.5之后,可以直接使用列(RANGE COLUMNS類型)來進行分區,這樣即使是基於時間的分區也無需再將其轉成一個整數。
(3)如果分區字段中有主鍵或者唯一索引列,那么所有主鍵列和唯一索引列都必須包含進來。(若不理解請看下面分區表使用注意事項)
(4)分區表中無法使用外鍵約束。
原理
分區表是由多個相關的底層表實現,這些底層表也是由句柄對象表示,所以我們也可以直接訪問各個分區,存儲引擎管理分區的各個底層表和管理普通表一樣(所有的底層表都必須使用相同的存儲引擎),分區表的索引只是在各個底層表上各自加上一個相同的索引,從存儲引擎的角度來看,底層表和一個普通表沒有任何不同,存儲引擎也無須知道這是一個普通表還是一個分區表的一部分。
在分區表上進行增刪改查記錄時,分區表先打開並鎖住所有的底層表,MySQL先確定這條記錄屬於哪個分區,再對相應底層表進行操作。雖然每個操作都有“先打開並鎖住所有的底層表”,但這並不是說分區表在處理過程中是鎖住全表的。如果存儲引擎能夠自己實現行級鎖,例如innoDb,則會在分區層釋放對應表鎖。這個加鎖和解鎖過程與普通InnoDB上的查詢類似。
分區表使用注意事項
(1)主鍵中必須包含表的分區函數中的所有列
在創建分區時如果表中存在主鍵,那么分區列必須是主鍵或包含於主鍵中。否則會報
意思是主鍵中必須包含表的分區函數中的所有列。所以如果我們在使用創建時間作為分區列進行分區的時候,就需要將創建時間和主鍵id當作聯合主鍵。
所以,要創建基於列c 的數據分片的分區表,主鍵必須包含列 c,比如下面的建表語句:
創建完表后,在物理存儲上會看到四個分區所對應 ibd 文件,也就是把數據根據時間列 c 存儲到對應的 4 個文件中:
所以,你要理解的是:MySQL 中的分區表是把一張大表拆成了多張表,每張表有自己的索引,從邏輯上看是一張表,但物理上存儲在不同文件中。
(2)唯一索引必須包含分區函數中所有列
在 MySQL 數據庫中,分區表的索引都是局部,而非全局。也就是說,索引在每個分區文件中都是獨立的,所以分區表上的唯一索引必須包含分區列信息,否則創建會報錯,比如:
你可以看到錯誤提示: 唯一索引必須包含分區函數中所有列。而下面的創建才能成功:
但是,正因為唯一索引包含了分區列,唯一索引也就變成僅在當前分區唯一,而不是全局唯一了。那么對於上面的表 t,插入下面這兩條記錄都是可以的:
你可以看到,列 d 都是字符串‘aaa’,但依然可以插入。這樣帶來的影響是列 d 並不是唯一的,所以你要由當前分區唯一實現全局唯一。
那如何實現全局唯一索引呢?
和之前表結構設計時一樣,唯一索引使用全局唯一的字符串(如類似 UUID 的實現),這樣就能避免局部唯一的問題。
分區表在業務上的設計
而為了讓你更好理解分區表的使用,我們繼續看一個真實業務的分區表設計。
以電商中的訂單表 Orders 為例,如果在類似淘寶的海量互聯網業務中,Orders 表的數據量會非常巨大,假設一天產生 5000 萬的訂單,那么一年表 Orders 就有近 180 億的記錄。
所以對於訂單表,在數據庫中通常只保存最近一年甚至更短時間的數據,而歷史訂單數據會入歷史庫。除非存在 1 年以上退款的訂單,大部分訂單一旦完成,這些數據從業務角度就沒用了。
那么如果你想方便管理訂單表中的數據,可以對表 Orders 按年創建分區表,如:
你可以看到,這時 Orders 表的主鍵修改為了(o_orderkey,O_ORDERDATE),數據按照年進行分區存儲。那么如果要刪除 1 年前的數據,比如刪除 1998 年的數據,之前需要使用下面的 SQL,比如:
可這條 SQL 的執行相當慢,產生大量二進制日志,在生產系統上,也會導致數據庫主從延遲的問題。而使用分區表的話,對於數據的管理就容易多了,你直接使用清空分區的命令就行:
上述 SQL 執行速度非常快,因為實際執行過程是把分區文件刪除和重建。另外產生的日志也只有一條 DDL 日志,也不會導致主從復制延遲問題。