你踩過這些坑嗎?謹慎在時間型別列上建立索引
作者: Zeratulll 原文來源:http://tidb.net/blog/9468d259
MySQL中,一般情況下我們不需要關注有序資料的寫入在Innodb的Btree上是否存在熱點,因為它能承擔的吞吐量是比較大的,在單機的範疇內不太容易達到瓶頸。
但是在TiDB中,寫入有序資料很容易導致熱點,這個熱點與單機資料庫不同。如果一個節點成為了熱點(只有它在工作,或者所有請求都需要訪問它),那整個叢集無論增加多少臺機器,都對提升資料庫的效能容量毫無幫助,純純的浪費錢了。這是分散式相對單機額外產生的問題。
一個表包含時間欄位(例如訂單表、日誌表、使用者表等等),並且在時間欄位上建立一個索引是我們使用MySQL時一種很常見的做法。這些時間欄位很多會使用插入或者修改的時間(例如DEFAULT值設為CURRENT_TIMESTAMP或者SQL中使用NOW函式來作為值)。
時間是一種典型的有序資料,那麼在使用TiDB時,我們是否可以保持像在MySQL中一樣的做法來使用時間欄位呢?時間欄位是否會產生熱點,又該如何避免?
本文將從TiDB的原理來解答上述問題。如果你是核心開發者,也有助於幫助讀者進一步理解分散式資料庫中資料的編碼與分佈。
問題
一個有趣的問題,考慮下面四張表(結構上的主要差異在於主鍵是AUTO_INCREMENT或者AUTO_RANDOM,gmt_create列是date型別或者datetime型別):
CREATE TABLE orders1 (
id bigint(11) NOT NULL AUTO_INCREMENT,
gmt_create datetime,
PRIMARY KEY (id) ,
KEY idx_gmt_create (gmt_create)
);
CREATE TABLE orders2 (
id bigint(11) NOT NULL AUTO_INCREMENT,
gmt_create date,
PRIMARY KEY (id) ,
KEY idx_gmt_create (gmt_create)
);
CREATE TABLE orders3 (
id bigint(11) NOT NULL AUTO_RANDOM,
gmt_create datetime,
PRIMARY KEY (id) ,
KEY idx_gmt_create (gmt_create)
);
CREATE TABLE orders4 (
id bigint(11) NOT NULL AUTO_RANDOM,
gmt_create date,
PRIMARY KEY (id) ,
KEY idx_gmt_create (gmt_create)
);
並使用insert into orders (id,gmt_create) values (null,now())
進行進行連續的寫入操作。
問題是:這四張表存在哪幾個熱點?
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
答案是:一共存在5個熱點(你答對了嗎?)
orders1中存在的熱點:gmt_create索引、主鍵; orders2中存在的熱點:gmt_create索引、主鍵; orders3中存在的熱點:gmt_create索引; orders4不存在熱點。
如圖所示:
解讀
AUTO_INCREMENT的熱點
orders1和orders2的主鍵上存在熱點。這個的原因大家都知道的,因為TiDB的資料是按照有序的range進行劃分的,主鍵自增,會導致寫入都發生在做最後的range上,因此最後的range會是熱點。這個在TiDB的文件中也有描述,這裡就不再贅述了:
從 TiDB 編碼規則可知,同一個表的資料會在以表 ID 開頭為字首的一個 range 中,資料的順序按照 RowID 的值順序排列。在表 insert 的過程中如果 RowID 的值是遞增的,則插入的行只能在末端追加。當 Region 達到一定的大小之後會進行分裂,分裂之後還是隻能在 range 範圍的末端追加,永遠只能在一個 Region 上進行 insert 操作,形成熱點。
常見的 increment 型別自增主鍵就是順序遞增的,預設情況下,在主鍵為整數型時,會用主鍵值當做 RowID ,此時 RowID 為順序遞增,在大量 insert 時形成表的寫入熱點。
同時,TiDB 中 RowID 預設也按照自增的方式順序遞增,主鍵不為整數型別時,同樣會遇到寫入熱點的問題。
order3和orders4的主鍵不存在熱點,因為使用AUTO_RANDOM來生成主鍵,將主鍵做了隨機化。這樣的代價也是有的,主鍵失去了巨集觀上的有序性(因為TiDB的AUTO_INCREMENT是按TiDB Server分段的,所以不能說是“有序”)。
DATE與DATETIME
再來看idx_gmt_create。
回顧TiDB中索引的編碼方式:
Key: tablePrefix{tableID}_indexPrefixSep{indexID}_indexedColumnsValue_rowID
Value: null
對於上述表結構,簡化下就是:
Key: {gmt_create}_{id}
這種編碼格式,在比較大小的時候,簡單說就是gmt_create不同則按gmt_create進行比較,gmt_create相同則按照id來比較。
對於orders1與orders3,gmt_create是DATETIME型別,包含了日期與時分秒(微秒)資訊。按時間不停寫入的資料,其gmt_create就是不停的在增長的有序資料,與AUTO_INCREMENT的主鍵類似,它也會不停的往最後一個range進行寫入,因此最後一個range會成為熱點。這裡orders1與orders3的行為是一致的,因為gmt_create作為字首已經是有序的了,編碼出來的key基本就是有序的。後面的id作為字尾,無論是有序的還是隨機的,都無法影響這個結果。
對於orders2與orders4,gmt_create是DATE型別,只包含了日期。對於一天內寫入的資料,其gmt_create的值實際上都是同一個。也就是說,在決定這個資料寫到哪個range的時候,起到比較作用的是id。
由於orders2的id是AUTO_INCREMENT的,因此編碼出來的key也是有序的,所以產生了熱點。
而orders4的id是隨機的,是亂序的,因此編碼出來的key也不具備有序性,寫入就會分散到很多range中,因此沒有熱點。
注意:實際上,當日期發生切換的時候(例如每天的0點0分0秒),orders4會在短時間內出現熱點(這個時間長短取決於你的流量多久能寫滿幾百兆,將這一天資料分裂到多個range內),這個熱點將表現成系統在0點的劇烈抖動,想象下雙十一零點出現這種抖動吧!
優化的可能性
TiDB可以考慮修改DATETIME/TIMESTAMP型別的編碼方式(或者提供一些額外的選項)。例如對於Key的部分,截斷到小時,後面使用隨機數進行補齊(充當了上文中隨機主鍵的作用),將未截斷的資料儲存在value中或者key的結尾。
這樣能很好的將連續寫入的時間資料進行打散,相應的代價是,查詢代價會變大(無論查詢條件多麼精確,都需要查出至少一小時的資料),需要過濾一些無用的資料。
結論
相容性其實包含功能相容性與效能相容性,TiDB雖然功能上與MySQL的相容性做的不錯,但效能上的差異點還是比較多的。
就本例而言,我們可以得出的結論是,使用TiDB時,在時間型別上建立索引需要慎重,如果按照使用單機MySQL的習慣進行建立,很容易出現熱點,導致雖然使用了分散式,但毫無擴充套件性可言。
如需建立,有以下幾個方法(每種方法都不完美,只能做取捨):
-
使用DATE型別,並且主鍵使用AUTO_RANDOM。缺點是無法儲存時分秒,主鍵也失去了巨集觀上的自增性;
-
使用DATE型別,並且和另一個不自增的離雜湊建立組合索引。例如idx_gmt_create;
-
使用DATE型別,並且主鍵使用SHARD_ROW_ID_BITS。缺點是無法儲存時分秒,主鍵失去了巨集觀上的自增性,並且SHARD_ROW_ID_BITS與主鍵使用聚簇相沖突,這會造成寫入的放大以及主鍵查詢需要做回表;
-
注意DATE型別即使在平時沒有熱點,在0點時刻也可能帶來劇烈抖動
-
使用分割槽表,這樣時間索引成為了分割槽內的Local索引,等於按分割槽做了打散。這是目前能想到的DATETIME型別上使用索引又避免熱點的唯一方法,但代價也很大,TiDB目前不支援在分割槽表上建立全域性索引,不帶分割槽鍵的查詢效能上也容易有問題,這對業務程式碼有很強的侵入性。
- TiFlash 儲存層概覽
- TiFlash 計算層概覽
- TiCDC 架構和資料同步鏈路解析
- TiKV & TiFlash 加速複雜業務查詢
- 讓秒殺狂歡更從容:大促背後的資料庫(下篇)
- TiCDC 6.0原理之Sorter演進
- TiDB 之 TiCDC6.0 初體驗
- 帶你全面瞭解compaction 的13個問題
- TiDB 6.1 新特性解讀 | TiDB 6.1 MPP 實現視窗函式框架
- TiFlash 面向編譯器的自動向量化加速
- 你踩過這些坑嗎?謹慎在時間型別列上建立索引
- TiDB和C#的簡單CRUD應用程式
- TiDB VS MySQL
- TIDB監控升級解決panic的漫漫探索之路
- 記憶體悲觀鎖原理淺析與實踐
- TiDB 效能分析&效能調優&優化實踐大全
- TiDB 效能分析和優化
- tiflash 6.0 on k8s擴容與新特性實踐
- 論分散式資料庫TiDB架構的“存”與“算”
- MySQL正常執行的SQL在TiDB中變慢了