本文介紹了全域二級索引的原理、特性和類型。
概述
全域二級索引(Global Secondary Index,簡稱GSI)是PolarDB-X中的一項重要特性,相比於本地二級索引,全域二級索引中的資料按照指定的分區方式分布在各個儲存節點上。通過全域二級索引,使用者能夠按需增加分區維度、提供全域唯一約束等。
原理和特性
在分散式資料庫的分區表中,資料被按照建表時指定的分區鍵進行路由和儲存,因此包含分區鍵的查詢可以快速定位到一個具體分區,而其它查詢則需要全分區掃描。對於分散式資料庫而言,全分區掃描除了會增加慢查詢數量降低系統吞吐,還可能導致系統喪失線性擴充能力,因此需要盡量避免全分區掃描。
舉例來說,當查詢語句包含分區鍵時,單個邏輯查詢只會被路由到一個具體分區,假設有N個儲存節點,平均來看1個邏輯查詢給單個儲存節點施加的查詢負載僅為1/N;當查詢語句不含分區鍵時會引起全分區掃描,即單個查詢會被路由到所有分區,平均來看1個邏輯查詢給單個儲存節點施加的查詢負載為1,單個儲存節點的效能上限就是整個分散式資料庫的效能上限,系統失去了線性擴充能力。
PolarDB-X提供了全域二級索引以解決上述問題。在PolarDB-X中,GSI可以視為一個特殊的分區表,它冗餘了主表上的部分列的資料。與普通分區表類似,GSI按照使用者指定的分區規則水平分割為若干個分區,分布在各個儲存節點上。當一個查詢不含主表分區鍵但包含GSI的分區鍵時,PolarDB-X通過先檢索GSI的單個分區,然後回表的方式避免全分區掃描。
PolarDB-X使用分散式交易維護主表和GSI之間資料的強一致。
此外,GSI還支援以下特性:
支援線上變更,建立、刪除GSI無需鎖表。
使用者可自訂覆蓋列,減少回表操作開銷。
支援invisible index。
類型
全域二級索引(Global Secondary Index 簡稱GSI)
全域二級索引可以提供和主表不同的分區方式,當查詢SQL的條件中未包含主表的分區鍵但包含了GSI的分區鍵時,仍可以避免全分區掃描。
比如對於下面的使用者表user_tbl, 如果既希望按照user_id查詢,又希望按照使用者名稱name查詢,就可以建立全域二級索引 g_i_name,在按照使用者名稱name查詢的時候避免全分區掃描。
CREATE TABLE user(
user_id bigint,
name varchar(10),
addr varchar(30),
GLOBAL INDEX `g_i_name` (name) PARTITION BY HASH(name),
PRIMARY KEY(user_id)
) PARTITION BY KEY(user_id);
全域唯一索引(Unique Global Secondary Index 簡稱UGSI)
全域唯一索引是特殊的GSI,它不僅有普通GSI的性質,還能實現全域唯一約束。
比如對於下面的使用者表user2,如果要求使用者手機號全域唯一,那麼可以建立一個phone欄位為索引鍵的UGSI。
CREATE TABLE user2(
user_id bigint,
phone varchar(20),
addr varchar(30),
UNIQUE GLOBAL INDEX `g_i_phone`(phone) PARTITION BY HASH(phone),
PRIMARY KEY(user_id)
) PARTITION BY KEY(user_id);
全域聚簇索引 (Clustered Global Secondary Index 簡稱Clustered GSI)
全域聚簇索引是特殊的GSI,它預設冗餘了主表的全部列(該索引所佔磁碟空間等於主表所佔磁碟空間)。如果既希望避免全分區掃描,又希望避免回表開銷,可以使用全域聚簇索引。
比如對於訂單表order_tbl,希望支援按照user_id或order_id來查詢,且希望避免用user_id查詢訂單時回表,就可以建立一個以user_id為索引鍵的全域聚簇索引cg_i_user。以user_id為條件查詢訂單資訊時,PolarDB-X會將查詢路由到cg_i_user上的一個特定分區,又因為cg_i_user上有主表的所有資料,因此無需回表。
CREATE TABLE order_tbl(
order_id bigint,
user_id bigint,
addr varchar(30),
info text,
create_time datetime,
CLUSTERED INDEX `cg_i_user`(user_id) PARTITION BY HASH(user_id),
PRIMARY KEY(order_id)
) PARTITION BY KEY(order_id);
效能
全域索引對讀寫效能的影響,與具體業務情境有比較大關係,本質上是犧牲一部分寫入效能換取讀效能的大幅提升,下面以Sysbench情境為例,展示該情境下GSI對讀寫吞吐的影響。
讀取效能資料
Table | Threads | Sysbench SeIect_random_ranges 情境 | Sysbench SeIect_random_points 情境 | ||||
QPS | Avg Latency | 95% Latency | QPS | Avg Latency | 95% Latency | ||
分區表 | 128 | 2769.17 | 46.21 | 99.33 | 5226.99 | 24.48 | 42.61 |
256 | 3415.64 | 144.97 | 144.97 | 5476.76 | 46.73 | 82.96 | |
512 | 3272.46 | 156.31 | 257.95 | 5290.67 | 96.72 | 179.94 | |
1024 | 2453.16 | 416.12 | 539.71 | 5165.31 | 198.07 | 404.61 | |
分區表+GSI | 128 | 9662.11 | 13.24 | 25.28 | 22584.89 | 5.66 | 9.73 |
256 | 10431.73 | 24.52 | 51.02 | 25558.26 | 10.01 | 17.95 | |
512 | 15634.51 | 32.72 | 73.13 | 27116.56 | 18.86 | 39.65 | |
1024 | 229448.76 | 44.53 | 108.68 | 32509.87 | 31.43 | 73.13 |
增加一個全域索引:
Select_random_ranges情境QPS:3415.64 -> 22948.76, range查詢QPS提升571%。
Select_random_points情境QPS:5476 -> 32509.87, 點查QPS提升493%。
結論:通過全域索引可以提升Sysbench在索引列k上的查詢效能。
寫入效能資料
Table | Threads | Sysbench SeIect_random_ranges 情境 | Sysbench SeIect_random_points 情境 | ||||
QPS | Avg Latency | 95% Latency | QPS | Avg Latency | 95% Latency | ||
分區表 | 128 | 86548.12 | 8.87 | 10.27 | 113655.28 | 22.52 | 26.2 |
256 | 115774.71 | 13.26 | 19.29 | 149677.52 | 34.19 | 44.17 | |
512 | 143928.94 | 20.51 | 34.95 | 14555.16 | 70.28 | 112.67 | |
1024 | 153501.7 | 39.53 | 70.55 | 132150.69 | 131.58 | 287.38 | |
分區表+GSI | 128 | 52069.22 | 14.25 | 18.28 | 90074.59 | 28.41 | 33.72 |
256 | 66250.79 | 23.17 | 32.53 | 114420.32 | 44.73 | 57.87 | |
512 | 75700.74 | 39.1 | 59.99 | 111093.61 | 92.09 | 142.39 | |
1024 | 76557.94 | 80.14 | 134.9 | 101828.32 | 182.51 | 350.33 |
增加一個全域索引:
WriteOnly情境QPS:153501.7 -> 76557.94,寫入QPS下降50%。
ReadWrite情境QPS:149677.52 -> 114420.32,讀寫混合QPS下降23%。
結論:增加一個全域索引,Sysbench寫入效能有下降。