索引
📄字数 11.0K
👁️阅读量 加载中...
索引是数据库中重要的对象,主要用于快速定位满足条件的行。如果没有索引,数据库必须执行全表扫描,即依次读取表的每一行来找到满足条件的数据。如果表在数据定位列上有索引,则可以使用索引来快速定位满足条件的行在数据文件中的位置,而无需读取所有的行。相比于执行全表扫描,使用索引来定位数据行的方式一般会提升数据查找的效率,尤其是对数据量较大的表。
数据库中可以使用索引的查询操作有:
- 查询满足 WHERE 子句条件的行。
- 多表连接(JOIN)查询。
- IN、EXISTS 等可以优化成半连接(SemiJoin)方式执行的查询:半连接优化。
一个查询操作是否会使用索引,取决于多个因素,例如查询条件是否涉及索引列、索引的类型与组织方式、表中数据的分布、返回结果的数量(选择性)、统计信息的准确性、以及数据库优化器的成本估算策略等。优化器会综合评估这些因素,选择是否使用索引以达到最佳执行效率。在一些情况下,使用索引对查询效率的提升很小,甚至会降低查询的效率。
索引在提升数据查找的效率时,也会带来额外的开销。索引结构本身会占用额外的存储空间,并且在对表执行数据插入、更新和删除等操作时,也需要对索引执行额外的维护操作来保持它的有效性。
因此,设计索引时应综合考虑查找效率的提升与由此带来的资源开销,具体可参考以下原则:
- 在常用的数据定位列上创建索引(即 WHERE 子句中出现的列)。
- 在经常用于表与表之间连接的列上创建索引。
- 不在重复度过高的列上创建索引。
- WHERE 子句条件选择性很差时(即返回结果的数量过多)不创建索引。
- 相比于查询性能,对表写性能的要求更高时,应少建或者不建索引。
除了显式创建索引,在创建主键、外键等约束时,一般也会隐式创建索引。这类隐式创建的索引会用于快速检查是否违反约束。
在实际应用中,为应对不同的查询模式和数据特性,并在不同场景中平衡性能与存储开销,索引在设计层面也有多种构造方式。例如:
- 联合索引:通过多个列的组合构造联合索引,以优化多条件联合过滤。
- 函数索引:通过对表达式或函数结果建索引形成函数索引,支持复杂筛选条件。
- 唯一索引:通过定义唯一索引,用于数据一致性检查与精准查找。
- 分区索引:在分区表上定义局部索引或全局索引,配合分区机制提升性能。
上述内容从使用目的和构造方式出发,介绍了索引的常见设计策略。除此之外,从底层实现的角度,XuguDB 还支持了以下常用索引类型,用户可根据数据特征和访问模式进行选择:
- B树索引:适用于高基数列(如主键、唯一编号),在频繁更新或并发访问较多的事务型场景中表现良好。
- 位图索引:适用于低基数列(如性别、状态等),可在多条件查询中通过位图运算提高效率,同时其压缩结构通常能够显著节省索引存储空间,适用于查询频繁、更新较少的分析型场景。
一、创建索引
创建索引时可指定索引类型、作用列、排序规则以及是否唯一等属性,支持单列、联合列、函数表达式等多种形式,同时也支持为分区表创建局部或全局索引。
1.1 语法总览
1.1.1 语法格式
1.1.2 参数说明
UNIQUE
:指定该参数时,表示创建的是唯一索引,等效于给表添加了一个对应的唯一值约束。if_not_exists
:指定该参数时,如果存在同名的索引,则不会执行创建操作,而是打印警告信息,从而避免报错。index_name
:索引的名称。schema_name
:表所属的模式的名称。省略时,表示当前模式。table_name
:索引所属表的名称。index_keys
:索引键定义。index_tail_opts
:创建索引语句尾部的可选参数。
1.1.3 示例
提示
本小节只展示了创建索引的部分示例,其他示例见语法详解章节。
唯一索引:
sqlSQL> CREATE TABLE tab_test_1 (id INT); SQL> CREATE UNIQUE INDEX idx_unique ON tab_test_1 (id); SQL> INSERT INTO tab_test_1 VALUES (1),(2),(3); SQL> INSERT INTO tab_test_1 VALUES (1); Error: [E13001] 违反唯一值约束
IF NOT EXISTS
:sqlSQL> CREATE TABLE tab_test_2 (id INT,name VARCHAR(100),birth DATE); SQL> CREATE INDEX idx_exists ON tab_test_2 (name); -- 存在同名索引时,不使用IF NOT EXISTS会报错 SQL> CREATE INDEX idx_exists ON tab_test_2 (name); Error: [E12008] 已存在同名IDX_EXISTS或相同定义的索引 -- 存在同名索引时,使用IF NOT EXISTS会打印警告信息,不报错 SQL> CREATE INDEX IF NOT EXISTS idx_exists ON tab_test_2 (name); Warning: [E12008] 已存在同名IDX_EXISTS或相同定义的索引
1.2 语法详解
1.2.1 索引键定义
1.2.1.1 语法格式
1.2.1.2 参数说明
index_keys
:索引键定义,由一个或多个索引键列组成。索引键由多个列组成的索引为联合索引。index_key
:单个索引键列的定义。col_name
:普通列名形式的索引键列,直接引用表的列。func_expr
:函数表达式形式的索引键列,必须间接引用表的列,详细说明见函数索引。ASC | DESC
:索引键列的排序方式,默认为升序。
1.2.1.3 示例
函数索引:
sqlSQL> CREATE TABLE tab_test_2 (id INT,name VARCHAR(100),birth DATE); SQL> CREATE INDEX idx_func ON tab_test_2 (len(name));
升序和降序索引:
sql-- 升序索引 SQL> CREATE INDEX idx_asc ON tab_test_2 (id ASC); -- 降序索引 SQL> CREATE INDEX idx_desc ON tab_test_2 (id DESC);
注意
默认为升序索引。
联合索引:
sqlSQL> CREATE INDEX idx_comp_1 ON tab_test_2 (id,name DESC); SQL> CREATE INDEX idx_comp_2 ON tab_test_2 (id,name DESC,birth ASC); SQL> CREATE INDEX idx_comp_3 ON tab_test_2 (id DESC,name DESC,birth); SQL> CREATE INDEX idx_comp_4 ON tab_test_2 (id ASC,len(name) DESC,birth);
1.2.2 索引类型选项
1.2.2.1 语法格式
1.2.2.2 参数说明
- 省略参数时:等同于
DEFAULT
。 DEFAULT
:默认为B树索引。BTREE
:B树索引。BITMAP
:位图索引。
1.2.2.3 示例
sql
SQL> CREATE TABLE tab_test_2 (id INT,name VARCHAR(100),birth DATE);
-- idx_bt_1、idx_bt_2和idx_bt_3都是B树索引
SQL> CREATE INDEX idx_bt_1 ON tab_test_2 (id);
SQL> CREATE INDEX idx_bt_2 ON tab_test_2 (name) INDEXTYPE IS BTREE;
SQL> CREATE INDEX idx_bt_3 ON tab_test_2 (len(name));
-- idx_bm_1是位图索引
SQL> CREATE INDEX idx_bm_1 ON tab_test_2 (birth) INDEXTYPE IS BITMAP;
1.2.3 索引分区选项
1.2.3.1 语法格式
(opt_partitioning_clause::=
,opt_subpartitioning_clause::=
)
1.2.3.2 参数说明
- 省略参数时:根据表和索引的类型自动选择是否创建局部分区索引。如果表是分区表,并且索引不是唯一索引(或者索引是唯一索引,但是表分区键是索引键前缀),会创建局部分区索引,其他情况不会创建分区索引。
LOCAL
:局部分区索引,即索引按表的规则划分分区,和表分区对齐。如果表不是分区表,索引也不会分区。GLOBAL
:全局分区索引,即索引按自己的规则划分分区,不一定和表分区对齐。如果不指定分区子句,索引不会分区。opt_partitioning_clause
:一级分区子句,具体语法和分区表一致,参见表一级分区。opt_subpartitioning_clause
:二级分区子句,具体语法和分区表一致,参见表二级分区。
1.2.3.3 示例
- 局部分区索引:
sql
-- 二级分区表:一级分区键为city_name,分区类型为列表分区,二级分区键为age,分区类型为范围分区
SQL> CREATE TABLE tab_parti (user_id INT,name VARCHAR,age INT,city_name VARCHAR)
PARTITION BY LIST (city_name) PARTITIONS
(
partition_chengdu VALUES ('成都'),
partition_chongqing VALUES ('重庆'),
partition_shanghai VALUES ('上海'),
partition_beijing VALUES ('北京'),
partition_guangzhou VALUES ('广州'),
partition_taiwan VALUES ('台湾'),
partition_hongkong VALUES ('香港')
)
SUBPARTITION BY RANGE(age) SUBPARTITIONS
(
partition_others VALUES LESS THAN (MAXVALUES),
partition_60 VALUES LESS THAN (60),
partition_45 VALUES LESS THAN (45),
partition_30 VALUES LESS THAN (30),
partition_18 VALUES LESS THAN (18),
partition_10 VALUES LESS THAN (10),
partition_3 VALUES LESS THAN (3)
);
-- 局部分区索引
SQL> CREATE INDEX idx_lparti_1 ON tab_parti (name) LOCAL;
- 全局分区索引:
sql
SQL> CREATE TABLE tab_test_3 (user_id INT,name VARCHAR,age INT,city_name VARCHAR);
-- 全局分区索引:一级分区键为city_name,分区类型为列表分区
SQL> CREATE INDEX idx_gparti_1 ON tab_test_3 (name,age) GLOBAL PARTITION BY LIST (city_name) PARTITIONS
(
partition_chengdu VALUES ('成都'),
partition_chongqing VALUES ('重庆'),
partition_shanghai VALUES ('上海'),
partition_beijing VALUES ('北京'),
partition_guangzhou VALUES ('广州'),
partition_taiwan VALUES ('台湾'),
partition_hongkong VALUES ('香港'),
partition8 VALUES (OTHERVALUES)
);
-- 全局分区索引:一级分区键为city_name,分区类型为列表分区,二级分区键为age,分区类型为范围分区
SQL> CREATE INDEX idx_gparti_2 ON tab_test_3 (name) GLOBAL PARTITION BY LIST (city_name) PARTITIONS
(
partition_chengdu VALUES ('成都'),
partition_chongqing VALUES ('重庆'),
partition_shanghai VALUES ('上海'),
partition_beijing VALUES ('北京'),
partition_guangzhou VALUES ('广州'),
partition_taiwan VALUES ('台湾'),
partition_hongkong VALUES ('香港'),
(OTHERVALUES)
)
SUBPARTITION BY RANGE(age) SUBPARTITIONS
(
partition_60 VALUES LESS THAN (60),
partition_45 VALUES LESS THAN (45),
partition_30 VALUES LESS THAN (30),
partition_18 VALUES LESS THAN (18),
partition_10 VALUES LESS THAN (10),
partition_3 VALUES LESS THAN (3)
);
1.2.4 其他选项
1.2.4.1 语法格式
1.2.4.2 参数说明
opt_online
:- 省略参数时:等同于
OFFLINE
。 OFFLINE
:离线创建索引,即创建索引时阻塞表上的读写操作。ONLINE
:在线创建索引,即创建索引时不阻塞表上的读写操作。
- 省略参数时:等同于
opt_parallel
:并行选项,控制索引创建和数据扫描的并行度。省略参数时:等同于
NOPARALLEL
。NOPARALLEL
:不并行。PARALLEL
:并行。int_value
表示非负整数参数。支持以下两种方式指定索引构建任务和表数据扫描任务的并行度:- 两个整数参数:第一个表示索引构建任务并行度,取值范围为
[0,1024]
;第二个表示表数据扫描任务并行度,取值范围为[0,128]
。 - 一个整数参数:以 32 位非负整数表示并行度,其中低 16 位表示索引构建任务并行度,高 16 位表示表数据扫描任务并行度。各部分取值范围与上述一致。
若任一并行度设为 0,表示使用系统参数的值:索引构建任务并行度使用 max_parallel;表数据扫描任务的并行度使用 para_eje_seqscan_num。
- 两个整数参数:第一个表示索引构建任务并行度,取值范围为
opt_wait
:- 省略参数时:等同于
WAIT
。 NOWAIT
:不等待,若无法立即获得锁等资源,则操作失败。WAIT
:最长等待时间使用系统参数 ddl_timeout 的值,超时则操作失败。WAIT wait_ms
:最长等待时间为wait_ms
毫秒,超时则操作失败。wait_ms
的取值范围为[0,2147483647]
:- 当
wait_ms = 0
时,表示使用系统参数ddl_timeout
的值。 - 当
wait_ms > 300000
时,等待时间被限制为 300000 毫秒。
- 当
- 省略参数时:等同于
二、修改索引
当前支持的索引修改操作有:
- 重命名索引:只更改索引的名称,不影响索引的功能和性能。
- 添加索引分区。
2.1 语法格式
2.2 参数说明
schema_name
:表所属的模式的名称。省略时,表示当前模式。table_name
:待执行修改操作的索引所属表的名称。index_name
:待执行修改操作的索引的名称。alter_operation
:索引修改操作。- 重命名索引:
new_index_name
:索引新的名称。
- 添加索引分区:
parti_name
:添加的一级分区的名称。parti_values
:分区条件值列表,具体语法和分区表一致,参见表一级分区。LESS THAN
:用于定义范围分区。省略时,表示列表分区。
- 重命名索引:
opt_wait
:用于指定修改索引过程中获取锁等资源时的最长等待时间。参见等待选项。
2.3 示例
- 重命名索引:
sql
SQL> CREATE TABLE tab_test_2 (id INT,name VARCHAR(100),birth DATE);
SQL> CREATE INDEX idx_1 ON tab_test_2 (id);
SQL> ALTER INDEX tab_test_2.idx_1 RENAME TO idx_2;
-- 查询修改后的索引信息
SQL> SELECT
uc.index_name,
uc.index_type,
uc.is_primary,
uc.is_unique,
uc.is_local,
uc.parti_type,
uc.slow_modify,
uc.keys,
uc.parti_key
FROM
SYS_INDEXES uc
JOIN SYS_TABLES ut ON
uc.table_id=ut.table_id
WHERE
ut.table_name='tab_test_2';
+------------+------------+------------+-----------+----------+------------+-------------+------+-----------+
| INDEX_NAME | INDEX_TYPE | IS_PRIMARY | IS_UNIQUE | IS_LOCAL | PARTI_TYPE | SLOW_MODIFY | KEYS | PARTI_KEY |
+------------+------------+------------+-----------+----------+------------+-------------+------+-----------+
| IDX_2 | 0 | F | F | F | 0 | <NULL> | "ID" | <NULL> |
+------------+------------+------------+-----------+----------+------------+-------------+------+-----------+
- 添加索引分区:
sql
SQL> CREATE TABLE tab_test_3 (user_id INT,name VARCHAR,age INT,city_name VARCHAR);
-- 全局分区索引:一级分区键为city_name,分区类型为列表分区
SQL> CREATE INDEX idx_gparti_1 ON tab_test_3 (name,age) GLOBAL PARTITION BY LIST (city_name) PARTITIONS
(
partition_chengdu VALUES ('成都'),
partition_chongqing VALUES ('重庆'),
partition_shanghai VALUES ('上海'),
partition_beijing VALUES ('北京'),
partition_guangzhou VALUES ('广州'),
partition_taiwan VALUES ('台湾'),
partition_hongkong VALUES ('香港')
);
-- 成功添加新分区
SQL> ALTER INDEX tab_test_3.idx_gparti_1 ADD PARTITION partition_wuhan VALUES ('武汉');
-- 全局分区索引:一级分区键为age,分区类型为范围分区
SQL> CREATE INDEX idx_gparti_2 ON tab_test_3 (name) GLOBAL PARTITION BY RANGE(age) PARTITIONS
(
partition_60 VALUES LESS THAN (60),
partition_45 VALUES LESS THAN (45),
partition_30 VALUES LESS THAN (30),
partition_18 VALUES LESS THAN (18),
partition_10 VALUES LESS THAN (10),
partition_3 VALUES LESS THAN (3)
);
-- 成功添加新分区
SQL> ALTER INDEX tab_test_3.idx_gparti_2 ADD PARTITION partition_100 VALUES LESS THAN (100);
重要
- 不支持局部分区索引。
- 新分区的类型要和索引分区类型一致。
- 新分区不能和已有分区的值范围重叠。
三、删除索引
删除索引用于移除指定表上的已有索引,不影响表中的数据。
3.1 语法格式
3.2 参数说明
IF EXISTS
:指定该参数时,如果索引不存在,则不会执行删除操作,而是打印警告信息,从而避免报错。schema_name
:表所属的模式的名称。省略时,表示当前模式。table_name
:待执行删除操作的索引所属表的名称。index_name
:待执行删除操作的索引的名称。
3.3 示例
IF EXISTS
:
sql
SQL> CREATE TABLE tab_test_2 (id INT,name VARCHAR(100),birth DATE);
SQL> CREATE INDEX idx_exists ON tab_test_2 (id);
SQL> DROP INDEX tab_test_2.idx_exists;
-- 索引不存在,不使用IF EXISTS会报错
SQL> DROP INDEX tab_test_2.idx_exists;
Error: [E12007] 索引IDX_EXISTS不存在
-- 索引不存在,使用IF EXISTS会打印警告信息,不报错
SQL> DROP INDEX IF EXISTS tab_test_2.idx_exists;
Warning: [E12007] 索引IDX_EXISTS不存在
四、重建索引
重建索引是指保留索引原定义,删除索引的物理存储,并重新生成索引数据。在重建索引的过程中,系统将会对表对象加排他锁,影响业务访问,所以不应在访问频繁时执行此操作。
4.1 语法格式
4.2 参数说明
schema_name
:表所属的模式的名称。省略时,表示当前模式。table_name
:待执行重建操作的索引所属表的名称。index_name
:待执行重建操作的索引的名称。*
:重建指定表的所有索引。parti_name
:待执行重建操作的索引一级分区的名称。reindex_tail_opts
:重建索引语句尾部的可选参数。
4.3 示例
- 重建表的一个索引:
sql
SQL> CREATE TABLE tab_test_2 (id INT,name VARCHAR(100),birth DATE);
SQL> CREATE INDEX idx_1 ON tab_test_2 (id);
SQL> CREATE INDEX idx_2 ON tab_test_2 (name);
SQL> CREATE INDEX idx_3 ON tab_test_2 (birth);
SQL> REINDEX tab_test_2.idx_1;
- 重建表的所有索引:
sql
SQL> REINDEX tab_test_2.*;
- 重建索引的一个分区:
sql
-- 二级分区表:一级分区键为city_name,分区类型为列表分区,二级分区键为age,分区类型为范围分区
SQL> CREATE TABLE tab_parti (user_id INT,name VARCHAR,age INT,city_name VARCHAR)
PARTITION BY LIST (city_name) PARTITIONS
(
partition_chengdu VALUES ('成都'),
partition_chongqing VALUES ('重庆'),
partition_shanghai VALUES ('上海'),
partition_beijing VALUES ('北京'),
partition_guangzhou VALUES ('广州'),
partition_taiwan VALUES ('台湾'),
partition_hongkong VALUES ('香港')
);
-- 一级局部分区索引
SQL> CREATE INDEX idx_lparti_1 ON tab_parti (age,name) LOCAL;
-- 重建一级局部分区索引的一个分区
SQL> REINDEX tab_parti.idx_lparti_1 PARTITION partition_chengdu;
重要
索引一级分区的重建操作存在一定的使用限制:
- 对二级局部分区索引,不支持重建一级分区,因为一级分区只是二级分区的逻辑组合。
- 不支持重建全局分区索引的一个分区。
附录:索引设计与执行优化
联合索引
联合索引是指索引键由多个列组成的索引,通常用于加速组合使用多个列的查询。
假设有一个用户订单表:
sql
CREATE TABLE tab_orders (id INT,user_id INT,order_date DATE,status VARCHAR(20));
在该表上创建一个联合索引:
sql
CREATE INDEX idx_user_date ON tab_orders (user_id,order_date);
联合索引遵循最左前缀匹配原则,只有从左往右连续使用了索引键列,才能使用索引来加速查询:
sql
-- 可以使用索引来加速的查询
-- 完全匹配索引键列
SELECT * FROM tab_orders WHERE user_id=123 AND order_date='2025-06-18';
SELECT * FROM tab_orders WHERE user_id>1 AND user_id<200 AND order_date='2025-06-18';
-- 匹配索引键列最左前缀
SELECT * FROM tab_orders WHERE user_id=123;
-- 匹配索引键列最左前缀,order_date列不会用于索引定位
SELECT * FROM tab_orders WHERE user_id=123 AND (order_date='2025-06-17' OR order_date='2025-06-18');
-- 不能使用索引来加速的查询
SELECT * FROM tab_orders WHERE order_date='2025-06-18';
即使满足最左前缀匹配原则,查询也可能无法使用联合索引,例如:
sql
SELECT * FROM tab_orders WHERE user_id=123 OR order_date='2025-06-18';
该类查询的优化见索引合并。
设计联合索引时,除了要考虑查询频率以及查询是否可以使用索引外,另一个重要的因素是过滤条件的选择性,它影响索引键列的顺序,过滤条件选择性高的列应该在索引键的左侧。
函数索引
函数索引是指在一个函数结果上创建的索引,而不是直接在原始列上。
假设有一个用户表:
sql
CREATE TABLE tab_users (id INT,email VARCHAR(255));
普通索引无法优化下面的查询:
sql
SELECT * FROM tab_users WHERE lower(email)='user@xugudb.com';
如下定义的函数索引可以优化该查询:
sql
CREATE INDEX idx_lower_email ON tab_users(lower(email));
索引合并
索引合并是一种由优化器采用的执行策略,它允许在一个查询中同时使用多个单列索引,并将它们的结果集合并,以避免全表扫描。目前只支持并集合并。
并集合并:
假设有一个用户订单表:
sql
CREATE TABLE tab_orders (id INT,user_id INT,order_date DATE,status VARCHAR(20));
在该表上创建索引:
sql
CREATE INDEX idx_user_id ON tab_orders (user_id);
CREATE INDEX idx_order_date ON tab_orders (order_date);
优化器可能会使用并集合并来加速查询:
sql
SELECT * FROM tab_orders WHERE user_id=123 OR order_date='2025-06-18';
- 使用
idx_user_id
查找user_id=123
的行; - 使用
idx_order_date
查找order_date='2025-06-18'
的行; - 对两个结果集执行并集合并;
- 回表取出完整数据。
半连接优化
半连接是一种用于优化 IN (SELECT ...)
、EXISTS
等子查询的执行方式:它判断外表中的某行是否在内表中有匹配项,只保留外表的行,不返回内表的内容。
在执行这类语句时,优化器通常会结合索引,在子查询表中进行快速定位,以避免全表扫描,从而高效完成“是否存在”的判断。
假设有下面的表和索引:
sql
CREATE TABLE tab_orders (id INT,customer_id INT);
CREATE TABLE tab_customers (id INT,region VARCHAR(20));
CREATE INDEX idx_id ON tab_customers (id);
优化器可能采用半连接的方式来加速查询:
sql
SELECT * FROM tab_orders
WHERE customer_id IN (
SELECT id FROM tab_customers WHERE region = 'CN'
);
- 扫描外表
tab_orders
; - 针对每行的
customer_id
,使用索引idx_id
快速在内表tab_customers
中判断是否存在匹配; - 输出存在匹配的行。