索引通过帮助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选择的索引。 SELECT
,DELETE
和UPDATE
语句支持指定的索引。
Hubble将索引直接存储在键值对中。
索引是一种权衡的选择:它极大提高了查询速度,但会稍微降低写入的速度(因为必须复制和对新值排序)。
我们建议为所有常用查询创建索引。通过对每个查询的 WHERE
和SELECT
子句来设计最有用的索引,可以创建以下索引:
WHERE
子句中的所有列。 SELECT
子句中的列。在设计索引时,重要的是要考虑索引哪些列以及列出它们的顺序。以下是一些指南,可帮助您做出最佳选择:
primary
)不能更改,也不能在创建表后更改其主键,因此在建表时,确定如何创建主键索引是非常重要的。(A,B,C)
,则查询过滤有(A)
或(A,B)
的列都可以使用该索引。但是,未过滤带有(A)
列的查询将不会从索引中得到性能提升的体现。此功能还可以避免使用单列索引,使用该列作为多列索引中的第一列,这对于更多查询很有用。
=
或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)