索引

索引通过帮助SQL无需遍历表快速定位数据,从而提高数据库的性能。

索引工作原理

创建一个索引时,Hubble对你指定的列进行索引,它创建了列的备份,然后对它们的值进行排序(而不对表本身的值进行排序)。

列索引建立好后,SQL可以便捷得使用索引来过滤值,而不需要对表进行一行行扫描。在一张大表中,这极大减少了SQL语句执行需要用到的行,执行查询的速度会指数级增长。

例如,如果您为INT列建立索引,SQL通过WHERE <indexed column> = 10子句进行过滤,就可以使用索引查找从10开始但小于11的值。如果没有索引,SQL将必须对表中的每一行等于10的值进行统计。这也称为全表扫描,这可能会降低查询性能。

索引创建

每个表都会自动创建一个primary索引,它对主键进行索引,如果没有主键,则为每个行唯一的rowid进行索引。我们建议始终创建一个主键,而不要使用rowid,因为创建的主键将提供更好的性能。

primary索引有助于过滤表的主键,但对于查找其他列并没有帮助。不过可以使用二级索引来提高查询的性能, 您可以创建它们:

  • CREATE TABLE建表时的INDEX子句中使用。除了明确定义的索引之外,Hubble还会自动为具有Unique约束的列创建二级索引。
  • 对于已存在的表,使用CREATE INDEX
  • 使用ALTER TABLE添加UNIQUE约束,它会为约束列自动创建索引。

如何创建更有用的二级索引,你可以查看我们的最佳实践。

索引选择

由于一个表中只能使用一个索引,因此Hubble选择它计算时需要扫描最少行的索引(即最快的行)。

你还可以使一个查询强制使用一个指定的索引,来覆盖Hubble选择的索引。 SELECTDELETEUPDATE语句支持指定的索引。

索引存储

Hubble将索引直接存储在键值对中。

性能

索引是一种权衡的选择:它极大提高了查询速度,但会稍微降低写入的速度(因为必须复制和对新值排序)。

最佳实践

我们建议为所有常用查询创建索引。通过对每个查询的 WHERESELECT子句来设计最有用的索引,可以创建以下索引:

  • 索引WHERE子句中的所有列。
  • 存储仅在SELECT子句中的列。
  • 出现回表查询时,优化相关索引

索引列

在设计索引时,重要的是要考虑索引哪些列以及列出它们的顺序。以下是一些指南,可帮助您做出最佳选择:

  • 每个表的主键(我们建议始终定义主键)会自动建立索引。它创建的索引(称为primary)不能更改,也不能在创建表后更改其主键,因此在建表时,确定如何创建主键索引是非常重要的。
  • 索引对查询很有帮助,即使仅过滤其列的前缀。例如,如果您创建列的索引(A,B,C),则查询过滤有(A)(A,B)的列都可以使用该索引。但是,未过滤带有(A)列的查询将不会从索引中得到性能提升的体现。

此功能还可以避免使用单列索引,使用该列作为多列索引中的第一列,这对于更多查询很有用。

  • 在WHERE子句中用等号运算符(=IN)过滤的列应在索引中排在第一位,并且在使用不等号运算符(<>)引用的列之前。
  • 对于顺序不同,列相同的索引可以为每个查询成生不同的结果。

存储字段

STORING子句不属于索引,但存储在索引中。这优化了查询这些列而不对它们进行过滤,它避免了读取主索引。

示例

TPC-C标准测试自动生成的表order的建表如下:

CREATE TABLE order (
	o_id integer NOT NULL,
	o_d_id integer NOT NULL,
	o_w_id integer NOT NULL,
	o_c_id integer NULL,
	o_entry_d TIMESTAMP NULL,
	o_carrier_id integer NULL,
	o_ol_cnt integer NULL,
	o_all_local integer NULL,
	CONSTRAINT "primary" PRIMARY KEY (o_w_id ASC, o_d_id ASC, o_id DESC),
	CONSTRAINT fk_o_w_id_ref_customer FOREIGN KEY (o_w_id, o_d_id, o_c_id) REFERENCES customer(c_w_id, c_d_id, c_id),
	UNIQUE INDEX order_idx (o_w_id ASC, o_d_id ASC, o_c_id ASC, o_id DESC),
	FAMILY "primary" (o_id, o_d_id, o_w_id, o_c_id, o_entry_d, o_carrier_id, o_ol_cnt, o_all_local)
)

例如,SQL对索引列进行筛选但检索未定义索引的列o_ol_cnt,可以通过使用索引order_idx筛选出o_d_id > 5 且 o_w_id > 5 且 o_c_id >2000的值,通过index join从主索引读取o_ol_cnt

explain select o_ol_cnt from order where o_d_id > 5 and o_w_id > 5 and o_c_id >2000;
       tree       |    field    |           description             
+-----------------+-------------+----------------------------------+
                  | distributed | true                              
                  | vectorized  | false                             
  render          |             |                                   
   └── index-join |             || table       | order@primary                     
        └── scan  |             |                                   
                  | table       | order@order_idx                   
                  | spans       | /6/6/2001-                        
                  | filter      | (o_d_id > 5) AND (o_c_id > 2000)  
(9 rows)

但是,如果没有索引,SQL将必须对表中的每一行进行统计,这也称为全表扫描,这会降低查询性能。

CREATE TABLE order (
	o_id integer NULL,
	o_d_id integer NULL,
	o_w_id integer NULL,
	o_c_id integer NULL,
	o_entry_d TIMESTAMP NULL,
	o_carrier_id integer NULL,
	o_ol_cnt integer NULL,
	o_all_local integer NULL,
	FAMILY "primary" (o_id, o_d_id, o_w_id, o_c_id, o_entry_d, o_carrier_id, o_ol_cnt, o_all_local, rowid)
)
explain select o_ol_cnt from order where o_d_id > 5 and o_w_id > 5 and o_c_id >2000;
     tree    |    field    |                     description                      
+-----------+-------------+-----------------------------------------------------+
            | distributed | true                                                 
            | vectorized  | false                                                
  render    |             |                                                      
   └── scan |             |                                                      
            | table       | order@primary                                       
            | spans       | ALL                                                  
            | filter      | ((o_d_id > 5) AND (o_w_id > 5)) AND (o_c_id > 2000)  
(7 rows)

但是如果我们将o_ol_cnt通过STORING存储在索引中,则不再需要索引联接index join。这意味着我们的查询仅需要从二级索引中读取,因此效率更高。

CREATE TABLE order (
	o_id integer NOT NULL,
	o_d_id integer NOT NULL,
	o_w_id integer NOT NULL,
	o_c_id integer NULL,
	o_entry_d TIMESTAMP NULL,
	o_carrier_id integer NULL,
	o_ol_cnt integer NULL,
	o_all_local integer NULL,
	CONSTRAINT "primary" PRIMARY KEY (o_w_id ASC, o_d_id ASC, o_id DESC),
	UNIQUE INDEX order_idx (o_w_id ASC, o_d_id ASC, o_c_id ASC, o_id DESC) STORING (o_ol_cnt),
	FAMILY "primary" (o_id, o_d_id, o_w_id, o_c_id, o_entry_d, o_carrier_id, o_ol_cnt, o_all_local)
)
explain select o_ol_cnt from order where o_d_id > 5 and o_w_id > 5 and o_c_id >2000;
    tree    |    field    |           description        
+-----------+-------------+----------------------------------+
            | distributed | true                              
            | vectorized  | false                             
  render    |             |                                   
   └── scan |             |                                   
            | table       | order@order_idx                  
            | spans       | /6/6/2001-                        
            | filter      | (o_d_id > 5) AND (o_c_id > 2000)  
(7 rows)