1. 程式人生 > >【MySQL】我必須得告訴大家的MySQL優化原理2(上)分割槽表

【MySQL】我必須得告訴大家的MySQL優化原理2(上)分割槽表

上一篇,後面留下兩個問題,這裡CHEN川  ^_^試著回答,讓我們看看大佬的看法吧?

前言:

如果資料量非常大的情況下,根據業務選擇了合適的欄位,精心設計了表和索引,仔細檢查了所有的SQL,確認沒有什麼問題了,但效能仍然不能滿足要求,這該怎麼辦?接下來討論一些常用的MySQL高階特性及背後的工作原理(註定是滿滿的乾貨),開始了:

正文:

分割槽表

合理使用索引可以提升mysql查詢下效能,但如果單表資料量達到一定程度,索引也無法起到作用,因為資料量超大,除非覆蓋索引,回表查詢產生大量隨機IO,庫的響應時間可能會達到不可接受的程度,且索引維護(磁碟空間、IO操作待機大)

   覆蓋索引:

         索引的葉子節點包含了要查詢的資料,即索引包含或覆蓋所有需要查詢的欄位的值,我們稱這種索引為覆蓋索引。【】不錯的部落格,有例子,講解也挺好的,這個索引主要是不用回表,減I/O;

因此單表資料量達到一定程度時(MySQL4.x時代,myisam業內公認的效能拐點是500W行,MySQL5.x時代效能拐點則為1KW~2KW行,具體情況具體測試),為提升效能,常用方法:分表;

分表策略可以是垂直拆分(不同訂單狀態的訂單拆到不同表),水平拆分(按月將訂單拆分到不同的表),總的來說,分表可以看作是從業務角度來解決大資料量問題,一定程度上提升效能,也提升編碼複雜度(用過mycat的同學先稍安勿躁);

在業務層分表增加編碼複雜程度,處理資料庫的相關程式碼會大量散落在應用各處,維護困難;是否可以將分表的邏輯抽象出,同一處理,業務層專注業務即可,答案是可能的,目前非常多的資料庫中介軟體都可以遮蔽分表後的細節,如果再將抽象的邏輯下移到資料庫的服務層,就到了我們下面要說的分割槽表;

分割槽可以看作是從技術層面解決大資料問題的有效防範,簡單理解:是mysql底層幫我們分表,分割槽表時一個獨立的邏輯表,底層由多個物理子表組成,儲存引擎管理分割槽的各個底層表和管理普通表一樣(所有底層表必須使用相同的儲存引擎),分割槽表的索引也是在各個底層表上各自加上一個完全相同的索引。從儲存引擎的角度來看,底層表和普通表沒有任何不同,儲存引擎也無須知道

。在執行查詢時,優化器會根據分割槽的定義過濾那些沒有我們需要資料的分割槽,這樣查詢就無需掃描所有分割槽,只需要查詢包含需要資料的分割槽就可以了。

示例

一個訂單表,資料量大概有10TB;因為資料量巨大,不能全表掃,使用索引,會發現資料不是按照想要的方式聚集,且產生大量碎片,導致查詢產生大量的隨機I/O,應用假死,so想要選擇更粗粒度且消耗更少的方式來檢索資料,如先根據索引找到一大塊資料,然後再這塊資料上順序掃描

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

對錶分割槽,可以在建立表時,使用如下語句

CREATE TABLE sales {
    order_date DATETIME NOT NULL
    -- other columns
} ENGINE=InnoDB PARTITION BY RANGE(YEAR(order_date)) (
    PARTITION p_2014 VALUES LESS THAN (2014),
    PARTITION p_2015 VALUES LESS THAN (2015)
    PARTITION p_2016 VALUES LESS THAN (2016)
    PARTITION p_2017 VALUES LESS THAN (2017)
    PARTITION p_catchall VALUES LESS THAN MAXVALUE
)

分割槽子句中可以使用各種函式,但表示式的返回值必須是一個確定的整數,且不能是一個常數。MySQL還支援一些其他分割槽,比如鍵值、雜湊、列表分割槽,但在生產環境中很少見到。在MySQL5.5以後可以使用RANGE COLUMNS型別分割槽,這樣即使是基於時間分割槽,也無需再將其轉化成一個整數。

接下來簡單看下分割槽表上的各種操作邏輯:

  • SELECT:當查詢一個分割槽表時,分割槽層先開啟並鎖住所有的底層表,優化器先判斷是否可以過濾部分分割槽,然後在呼叫對應的儲存引擎介面訪問各個分割槽的資料
  • INSERT:當插入一條記錄時,分割槽層先開啟並鎖住所有的底層表,然後確定哪個分割槽接收這條記錄,再將記錄寫入對應的底層表,DELETE操作與其類似
  • UPDATE:當更新一條資料時,分割槽層先開啟並鎖住所有的底層表,然後確定資料對應的分割槽,然後取出資料並更新,再判斷更新後的資料應該存放到哪個分割槽,最後對底層表進行寫入操作,並對原資料所在的底層表進行刪除操作

有些操作是支援條件過濾的。例如,當刪除一條記錄時,MySQL需要先找到這條記錄,如果WHERE條件恰好和分割槽表示式匹配,就可以將所有不包含這條記錄的分割槽都過濾掉,這對UPDATE語句同樣有效。如果是INSERT操作,本身就只命中一個分割槽,其他分割槽都會被過濾。

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

在使用分割槽表時,為了保證大資料量的可擴充套件性,一般有兩個策略:

  • 全量掃描資料,不用索引。即只要能夠根據WHERE條件將需要查詢的資料限制在少數分割槽中,效率是不錯的
  • 索引資料,分離熱點。如果資料有明顯的“熱點”,而且除了這部分資料,其他資料很少被訪問到,那麼可以將這部分熱點資料單獨存放在一個分割槽中,讓這個分割槽的資料能夠有機會都快取在記憶體中。這樣查詢就可以值訪問一個很小的分割槽表,能夠使用索引,也能夠有效的利用快取。

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

上面兩個分割槽策略基於兩個非常重要的前提:查詢都能夠過濾掉很多額外的分割槽、分割槽本身並不會帶來很多額外的代價。而這兩個前提在某些場景下是有問題的,比如:

1、NULL值會使分割槽過濾無效

假設按照PARTITION BY RANGE YEAR(order_date)分割槽,那麼所有order_date為NULL或者非法值時,記錄都會被存放到第一個分割槽。所以WHERE order_date BETWEEN '2017-05-01' AND ‘2017-05-31’,這個查詢會檢查兩個分割槽,而不是我們認為的2017年這個分割槽(會額外的檢查第一個分割槽),是因為YEAR()在接收非法值時會返回NULL。如果第一個分割槽的資料量非常大,而且使用全表掃描的策略時,代價會非常大。為了解決這個問題,我們可以建立一個無用的分割槽,比如:PARTITION p_null values less than (0)。如果插入的資料都是有效的話,第一個分割槽就是空的。

MySQL5.5以後就不需要這個技巧了,因為可以直接使用列本身而不是基於列的函式進行分割槽:PARTITION BY RANGE COLUMNS(order_date)。直接使用這個語法可避免這個問題。

2、分割槽列和索引列不匹配

可能會導致查詢無法進行分割槽過濾,除非每個查詢條件中都包含分割槽列。假設在列a上定義了索引,而在列b上進行分割槽。因為每個分割槽都有其獨立的索引,所以在掃描列b上的索引就需要掃描每一個分割槽內對應的索引,當然這種速度不會太慢,但是能夠跳過不匹配的分割槽肯定會更好。這個問題看起來很容易避免,但需要注意一種情況就是,關聯查詢。如果分割槽表是關聯順序的第2張表,並且關聯使用的索引與分割槽條件並不匹配,那麼關聯時對第一張表中符合條件的每一行都需要訪問並搜尋第二張表的所有分割槽(關聯查詢原理,請參考前一篇文章)

3、選擇分割槽的成本可能很高

分割槽有很多種型別,不同型別的分割槽實現方式也不同,所以它們的效能也不盡相同,尤其是範圍分割槽,在確認這一行屬於哪個分割槽時會掃描所有的分割槽定義,這樣的線性掃描效率並不高,所以隨著分割槽數的增長,成本會越來越高。特別是在批量插入資料時,由於每條記錄在插入前,都需要確認其屬於哪一個分割槽,如果分割槽數太大,會造成插入效能的急劇下降。因此有必要限制分割槽數量,但也不用太過擔心,對於大多數系統,100個左右的分割槽是沒有問題的。

4、開啟並鎖住所有底層表的成本在某些時候會很高

前面說過,開啟並鎖住所有底層表並不會對效能有太大的影響,但在某些情況下,比如只需要查詢主鍵,那麼鎖住的成本相對於主鍵的查詢來說,成本就略高。

5、維護分割槽的成本可能會很高

新增和刪除分割槽的速度都很快,但是修改分割槽會造成資料的複製,這與ALTER TABLE的原理類似,需要先建立一個歷史分割槽,然後將資料複製到其中,最後刪除原分割槽。因此,設計資料庫時,考慮業務的增長需要,合理的建立分割槽表是一個非常好的習慣。在MySQL5.6以後的版本可以使用ALTER TABLE EXCHAGE PARTITION語句來修改分割槽,其效能會有很大提升。

分割槽表還有一些其他限制,比如所有的底層表必須使用相同的儲存引擎,某些儲存引擎也不支援分割槽。分割槽一般應用於一臺伺服器上,但一臺伺服器的物理資源總是有限的,當資料達到這個極限時,即使分割槽,效能也可能會很低,所以這個時候分庫是必須的。但不管是分割槽、分庫還是分表,它們的思想都是一樣的,大家可以好好體會下。



作者:CHEN川
連結:https://www.jianshu.com/p/01b9f028d9c7
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。