MySQL之KEY分區(qū)引發(fā)的血案

需求背景

業(yè)務表tb_image部分數據如下所示逛拱,其中id唯一,image_no不唯一公浪。image_no表示每個文件的編號劳曹,每個文件在業(yè)務系統(tǒng)中會生成若干個文件,每個文件的唯一ID就是字段id:

tb_image

業(yè)務表tb_image的一些情況如下:

  1. 根據image_no查詢和根據id查詢蔬螟;
  2. 存量數據2kw此迅;
  3. 日增長4w左右;
  4. 日查詢量20w左右旧巾;
  5. 非ToC系統(tǒng)耸序,所以并發(fā)的天花板可見;

方案選擇

根據上面對業(yè)務的分析鲁猩,分庫分表完全沒有必要坎怪。單庫分表的話,由于要根據image_no和id查詢廓握,所以搅窿,一種方案是冗余分表(即一份數據以image_no為分片鍵保存,另一份數據以id為分片鍵保存)隙券;另一種方案是只以image_no為分片鍵男应,而基于id的查詢需求,業(yè)務層進行結果歸并或者引入第三方中間件娱仔。

考慮到單庫分表比較復雜沐飘,所以決定使用分區(qū)特性,而且容量評估分區(qū)表方案128個分區(qū)(每個分區(qū)數據量kw級別)完全能保證業(yè)務至少穩(wěn)定運行15年(圖中橙色部分是比較貼合自身業(yè)務實際增長情況):


容量評估

另外,由于RANGE, LIST, HASH分區(qū)都不支持VARCHAR列耐朴,所以決定采用KEY分區(qū)借卧,官方介紹它的原理是以MySQL內置hash算法然后對分區(qū)數取模。

性能測試

選定分片鍵為image_no筛峭,并且決定分區(qū)數為128后铐刘,就要灌入數據進行可行性和性能測試了。分區(qū)數選擇128的原因是:11億/1kw=110≈128蜒滩,另外程序員情節(jié)滨达,喜歡用2的N次方,你懂的俯艰。然而捡遍,這個分區(qū)數128就是一切噩夢的開始

我嘗試先插入10w數據到128個分區(qū)中竹握,插入后画株,讓我驚訝的現(xiàn)象出現(xiàn)了:所有奇數編號分區(qū)(p1, p3, p5, ... , p2n-1)中居然沒有一條數據,同時啦辐,任何一個偶數編號分區(qū)卻有很多的數據谓传,而且還不是很均勻。如下圖所示:

128 partition summary

說明:奇數編號分區(qū)的ibd文件大小都是112k芹关,這是創(chuàng)建分區(qū)表時初始化大小续挟,實際并沒有任何數據。我們可以通過SQL:select partition_name, partition_expression, table_rows from information_schema.partitions where table_schema = schema() and table_name='image_subpart';驗證侥衬,其部分結果如下圖所示:

partition table summary

難道10w條數據還不夠說明問題诗祸?平均下來每個分區(qū)可是有近800條數據!好吧轴总,來點猛的:我再插入990w條數據直颅,總計1kw數據。結果還是一樣怀樟,奇數編號分區(qū)沒有數據功偿,偶數編號都有分區(qū)。

問題思考

我們再來回想一下KEY分區(qū)的原理:通過MySQL內置hash算法對分片鍵計算hash值后再對分區(qū)數取模往堡。這個原理也可以從MySQL官網找到械荷,請戳鏈接:22.2.5 KEY Partitioning: https://dev.mysql.com/doc/refman/5.7/en/partitioning-key.html,截取原文如下:

Partitioning by key is similar to partitioning by hash, except that where hash partitioning employs a user-defined expression, the hashing function for key partitioning is supplied by the MySQL server. NDB Cluster uses MD5() for this purpose; for tables using other storage engines, the server employs its own internal hashing function which is based on the same algorithm as PASSWORD().

這個世界上不會有這么渣渣的hash算法吧虑灰?隨便寫個什么算法也不至于這么不均勻吧养葵?這時候我懷疑是否有一些什么配置引起的。但是show variables中并沒有任何與partition相關的變量瘩缆。

這個時候,一萬匹馬奔騰而過佃蚜。會不會是文檔和源碼不同步導致的庸娱?好吧着绊,看MySQL的源碼,畢竟熟尉,源碼才是最接近真相的地方归露。KEY分區(qū)相關源碼在文件sql_partition.cc中,筆者截取部分關鍵源碼斤儿,如下所示剧包,初略觀察,并沒有什么不妥往果,先計算分區(qū)字段的hash值然后對分區(qū)數取模:

/**
  Calculate part_id for (SUB)PARTITION BY KEY
  @param file                Handler to storage engine
  @param field_array         Array of fields for PARTTION KEY
  @param num_parts           Number of KEY partitions
  @param func_value[out]     Returns calculated hash value
  @return Calculated partition id
*/
inline
static uint32 get_part_id_key(handler *file,
                              Field **field_array,
                              uint num_parts,
                              longlong *func_value)
{
  DBUG_ENTER("get_part_id_key");
  // 計算分區(qū)字段的hash值
  *func_value= file->calculate_key_hash_value(field_array);
  // 對分區(qū)數取模
  DBUG_RETURN((uint32) (*func_value % num_parts));
}

懷著絕望的心情疆液,請出搜索引擎搜索:"KEY分區(qū)數據不均勻",搜索結果中的CSDN論壇(https://bbs.csdn.net/topics/390857704)里有個民間高手華夏小卒回答如下:

一個同事根據password函數陕贮,分析并測出堕油,key分區(qū),只能指定分區(qū)數目為質數肮之,才能保證每個分區(qū)都有數據掉缺。我測了下,從11個分區(qū)戈擒,到17個分區(qū)眶明。 只有11,13,17 ,這3個分區(qū)的數據是基本平均分布的筐高。

這個時候犬辰,又是一萬匹馬奔騰而過。不過F**K的同時冰单,心里也是有點小激動,因為可能找到解決辦法了(雖然還不知道MySQL內置hash算法為毛會這樣)诫欠,最后筆者再次對KEY分區(qū)測試并總結如下:

  1. 如果設置40涵卵,64,128等偶數個分區(qū)數(PARTITIONS 64)荒叼,會導致編號為奇數的分區(qū)(p1, p3, p5, p7, ... p2n-1)完全插不進數據;
  2. 如果設置63,121(PARTITIONS 63)這種奇數但非質數個分區(qū)數看疙,所有分區(qū)都會有數據施禾,但是不均勻拓巧;
  3. 如果設置137承耿,31這種質數個分區(qū)數(PARTITIONS 137),所有分區(qū)都會有數據捷泞,并且非常均勻;

如下圖所示,是筆者把分區(qū)數調整為127并插入100w數據后的情況劈狐,通過SQL證明每個分區(qū)的數據量幾乎一樣:


partitions 127 summary

總結回顧

MySQL的KEY分區(qū)這么大的使用陷阱谒兄,居然在官方上沒有任何說明燕鸽,這讓筆者感到非常震驚麸锉。筆者還嘗試Google搜索mysql partition key uneven磷脯,也有很多結果,例如stackoverflow:https://stackoverflow.com/questions/38454354/mysql-uneven-distribution-of-data-into-partitions-when-using-key-partitioning娩脾,此外還有MySQL bug:Bug #72428 Partition by KEY() results in uneven data distribution

正在看此文并有很強烈興趣的同學赵誓,可以嘗試更深入這個問題。筆者接下來也會找個時間柿赊,根據MySQL源碼深入挖掘其hash算法的實現(xiàn)為什么對分區(qū)數如此敏感俩功。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市碰声,隨后出現(xiàn)的幾起案子诡蜓,更是在濱河造成了極大的恐慌,老刑警劉巖胰挑,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔓罚,死亡現(xiàn)場離奇詭異,居然都是意外死亡瞻颂,警方通過查閱死者的電腦和手機豺谈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蘸朋,“玉大人核无,你說我怎么就攤上這事∨号鳎” “怎么了团南?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長炼彪。 經常有香客問我吐根,道長,這世上最難降的妖魔是什么辐马? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任拷橘,我火速辦了婚禮,結果婚禮上喜爷,老公的妹妹穿的比我還像新娘冗疮。我一直安慰自己,他們只是感情好檩帐,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布术幔。 她就那樣靜靜地躺著,像睡著了一般湃密。 火紅的嫁衣襯著肌膚如雪诅挑。 梳的紋絲不亂的頭發(fā)上四敞,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音拔妥,去河邊找鬼忿危。 笑死,一個胖子當著我的面吹牛没龙,可吹牛的內容都是我干的铺厨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼兜畸,長吁一口氣:“原來是場噩夢啊……” “哼努释!你這毒婦竟也來了?” 一聲冷哼從身側響起咬摇,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎煞躬,沒想到半個月后肛鹏,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡恩沛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年在扰,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雷客。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡芒珠,死狀恐怖,靈堂內的尸體忽然破棺而出搅裙,到底是詐尸還是另有隱情皱卓,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布部逮,位于F島的核電站娜汁,受9級特大地震影響,放射性物質發(fā)生泄漏兄朋。R本人自食惡果不足惜掐禁,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望颅和。 院中可真熱鬧傅事,春花似錦、人聲如沸峡扩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽有额。三九已至般又,卻和暖如春彼绷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背茴迁。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工寄悯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人堕义。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓猜旬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親倦卖。 傳聞我的和親對象是個殘疾皇子洒擦,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內容