本文介紹了全局二級索引的原理、特性和類型。
概述
全局二級索引(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寫入性能有下降。