
# CREATE INDEX
#### 功能描述
在指定的表上创建索引。
索引可以用来提高数据库查询性能，但是不恰当的使用将导致数据库性能下降。建议仅在匹配如下某条原则时创建索引：
- 经常执行查询的字段。
- 在连接条件上创建索引，对于存在多字段连接的查询，建议在这些字段上建立组合索引。例如，select \* from t1 join t2 on t1.a=t2.a and t1.b=t2.b，可以在t1表上的a，b字段上建立组合索引。
- where子句的过滤条件字段上（尤其是范围条件）。
- 在经常出现在order by、group by和distinct后的字段。
在分区表上创建索引与在普通表上创建索引的语法不太一样，使用时请注意，如当索引带GLOBAL/LOCAL关键字或者创建索引为GLOBAL索引时不支持创建部分索引。
#### 注意事项
- 基表为HASH分布时，若创建不包含基表分布键的主键或唯一索引，需要使用全局二级索引（CREATE GLOBAL INDEX），若创建包含基表分布键的主键或唯一索引，需要使用普通索引（CREATE INDEX），单DN部署形式下，使用全局二级索引或者普通索引均可创建成功；当基表为除HASH分布以外的其他分布形式时，主键或唯一索引只能使用普通索引（CREATE INDEX），即索引键必须包含基表分布键。
- 索引自身也占用存储空间、消耗计算资源，创建过多的索引将对数据库性能造成负面影响（尤其影响数据导入的性能，建议在数据导入后再建索引）。因此，仅在必要时创建索引。
- 索引定义里的所有函数和操作符都必须是immutable类型的，即它们的结果必须只能依赖于它们的输入参数，而不受任何外部的影响（如另外一个表的内容或者当前时间）。这个限制可以确保该索引的行为是定义良好的。要在一个索引上或WHERE中使用用户定义函数，请把它标记为immutable类型函数。
- 被授予CREATE ANY INDEX权限的用户，可以在public模式和用户模式下创建索引。
- 如果表达式索引中调用的是用户自定义函数，按照函数创建者权限执行表达式索引函数。
- 不支持XML类型数据作为普通索引、UNIQUE索引、GLOBAL索引、LOCAL索引、部分索引。
- 在线创建索引的类型只支持btree索引和ubtree索引，。索引创建形式只支持非分区表普通索引及分区表GLOBAL索引、LOCAL索引，不支持在线索引字段增删改、PCR ubtree索引、二级分区与GSI。在线并行创建索引只支持Astore的普通索引、GLOBAL索引、LOCAL索引，Ustore索引不支持在线并行创建。
- 使用CREATE INDEX创建索引可能会改变表的访问方式，从而导致查询执行计划改变。
- 在创建组合索引时，需根据查询条件和组合索引最左匹配原则创建。
  ![](https://support.huaweicloud.com/distributed-devg-v3-gaussdb/public_sys-resources/note_3.0-zh-cn.png)
  - 组合索引最左匹配原则：如果查询条件包含了组合索引的一列或者多列，那么组合索引的最左边开始的连续列需要与查询条件匹配上。
  
  
  
  - 当查询为where a = ?, b = ?,c = ?, d = ? 或者 where a = ?, b = ?,c = ? 等时，都是最佳的索引匹配；当查询为where b = ?,c = ?, d = ? 或者 where c = ?, d = ? 等时，在代价计算后可能也会走索引idx_test_abcd，但是这种情况走索引时会扫描索引的全部页面，导致SQL性能并不理想。类似情况建议根据最左匹配原则创建适合此查询条件的组合索引。
    ```
    --创建表test。
    gaussdb=# CREATE TABLE test(a int, b int, c int, d int, e int, f text);
    创建组合索引。
    gaussdb=# CREATE INDEX idx_test_abcd ON test(a,b,c,d);
    ```
    
    
 
#### 语法格式
- 在表上创建索引。
  ```
  CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [schema_name.] index_name ] ON table_name [ USING method ]
      ({ { column_name | ( expression ) } [ COLLATE collation ] [ opclass ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ] }[, ...] )
      [ INCLUDE ( column_name [, ...] ) ]
      [ WITH ( {storage_parameter = value} [, ... ] ) ]
      [ TABLESPACE tablespace_name ]
      [ WHERE predicate ];
  ```
  
- 在分区表上创建索引。
  ```
  CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [schema_name.] index_name ] ON table_name [ USING method ]
      ( { { column_name | ( expression ) } [ COLLATE collation ] [ opclass ] [ ASC | DESC ] [ NULLS LAST ] } [, ...] )
      [ LOCAL [ ( { PARTITION index_partition_name [ TABLESPACE index_partition_tablespace ] } [, ...] ) ] | GLOBAL ]
      [ INCLUDE ( column_name [, ...] ) ]
      [ WITH ( { storage_parameter = value } [, ...] ) ]
      [ TABLESPACE tablespace_name ];
  ```
  
 
#### 参数说明
- **UNIQUE**
  创建唯一性索引，每次添加数据时检测表中是否有重复值。如果插入或更新的值会引起重复的记录时，将导致一个错误。
  目前只有行存表B-tree及UBtree索引支持唯一索引。
  
- **CONCURRENTLY**
  以不阻塞DML的方式创建索引（加ShareUpdateExclusiveLock锁）。创建索引时，一般会阻塞其他语句对该索引所依赖表的访问。指定此关键字，可以实现创建过程中不阻塞DML。
  - 此选项只能指定一个索引的名称。
  
  - 普通CREATE INDEX命令可以在事务内执行，但是CREATE INDEX CONCURRENTLY不可以在事务内执行。
  
  - 对于临时表，支持使用CONCURRENTLY关键字创建索引，但是实际创建过程中，采用的是阻塞式的创建方式，因为没有其他会话会并发访问临时表，并且阻塞式创建成本更低。
  
  
  ![](https://support.huaweicloud.com/distributed-devg-v3-gaussdb/public_sys-resources/note_3.0-zh-cn.png)
  - 创建索引时指定此关键字，需要执行先后两次对该表的全表扫描来完成build，第一次扫描的时候创建索引，不阻塞读写操作；第二次扫描的时候合并更新第一次扫描到目前为止发生的变更。
  
  - 由于需要执行两次对表的扫描和build，而且必须等待现有的所有可能对该表执行修改的事务结束。这意味着该索引的创建比正常耗时更长，同时因此带来的CPU和I/O消耗对其他业务也会造成影响。
  
  - 如果在索引构建时发生失败，那会留下一个"不可用"的索引。这个索引会被查询忽略，但它仍消耗更新开销。这种情况推荐的恢复方法是通过DROP INDEX IF EXISTS语法删除该索引并尝试再次CONCURRENTLY创建索引。值得注意的是，CLUSTER/TRUNCATE/VACUUM FULL/REINDEX TABLE等重建索引将跳过残留"不可用"索引，ALTER TABLE若涉及表和索引重建将自动清理残留的"不可用"索引。
  
  - 由于在第二次扫描之后，索引构建必须等待任何持有早于第二次扫描拿的快照的事务终止，而且建索引时加的ShareUpdateExclusiveLock锁（4级）会和大于等于4级的锁冲突，在创建这类索引时，容易引发卡住（hang）或者死锁问题。例如：
    - 两个会话对同一个表创建CONCURRENTLY索引，会引起死锁问题。
    
    - 两个会话，一个对表创建CONCURRENTLY索引，一个drop table，会引起死锁问题。
    
    - 三个会话，会话1先对表a加锁，不提交，会话2接着对表b创建CONCURRENTLY索引，会话3接着对表a执行写入操作，在会话1事务未提交之前，会话2会一直被阻塞。
    
    - 创建CONCURRENTLY索引与同一个表的TRUNCATE操作并发，会引起死锁问题。
    
    - 将事务隔离级别设置成可重复读（默认为读已提交），起两个会话，会话1起事务对表a执行写入操作，不提交，会话2对表b创建CONCURRENTLY索引，在会话1事务未提交之前，会话2会一直被阻塞。
     
  
  - 索引构建过程中或者构建失败的情况下，需要确认索引进度或状态，可以通过查询函数gs_get_index_status('schema_name', 'index_name')来确认当前所有节点上索引的状态，其中入参为schema_name和index_name，分别用来指定索引的模式名称和索引名称，返回值为node_name，indisready和indisvalid，分别表示节点名称，索引在该节点上是否可插入，以及索引在该节点上是否可用，只有当所有节点indisready和indisvalid均为true的情况下，索引才是"可用的"，否则请等待索引创建完成，或者构建失败情况下，删除索引重新创建。
  
  - 在IO、CPU不受限的情况下，在线创建索引对业务性能的劣化一般可以控制在10%以内，但在特殊场景下劣化可能会超过此数值。这是因为在线创建索引本身是一种消耗IO、CPU资源较多的长事务，需要比离线创建索引消耗更多的资源。在线创建索引事务持续时间越长，对业务性能的影响越大。在线创建索引时间与基表数据量、并发DML产生的数据量正相关，在IO、CPU不受限的情况下，在线创建索引时间大约是离线创建索引的2\~6倍，但当并发事务量较大（\>10000tps）或存在资源争抢的情况时，可能会超过此数值。在Astore模式下，可以使用并行创建索引来缩短创建索引时间；在线并行创建索引性能随着并行工作线程数量增加而提升到一定值后稳定，相比串行创建索引性能一般可提升30%左右。建议在业务低谷期进行在线创建索引，以避免对业务造成较大影响。虽然在线创建索引在一定程度上提供了业务不中断的能力，但仍然需要谨慎实施。
    
- **schema_name**
  模式的名称。
  取值范围：已存在模式名。
  
- **index_name**
  要创建的索引名，不能包含模式名，索引的模式与表相同。
  取值范围：字符串，要符合[标识符命名规范](https://support.huaweicloud.com/distributed-devg-v3-gaussdb/gaussdb-12-0319.html#ZH-CN_TOPIC_0000002235270221__zh-cn_topic_0000002235098221_section14873217506)。
  
- **table_name**
  需要为其创建索引的表的名称，可以用模式修饰。
  取值范围：已存在的表名。
  
- **USING method**
  指定创建索引的方法。
  取值范围：
  - btree：B-tree索引使用一种类似于B+树的结构来存储数据的键值，通过这种结构能够快速查找索引。B-tree适合支持比较查询以及查询范围。建索引时在表为Ustore时会自动变换为UB-tree。
  
  - ubtree：仅供UStore表使用的多版本B-tree索引，索引页面上包含事务信息，能并自主回收页面。UB-tree索引默认开启insertpt功能。
  
  
  行存表支持的索引类型：ubtree（行存表缺省值）。行存表（Ustore存储引擎）支持的索引类型：UB-tree。
  
- **column_name**
  表中需要创建索引的列的名称（字段名）。
  如果索引方式支持多字段索引，可以声明多个字段。全局索引最多可以声明31个字段，其他索引最多可以声明32个字段。
  
- **expression**
  创建一个基于该表的一个或多个字段的表达式索引，通常必须写在圆括弧中。如果表达式有函数调用的形式，圆括弧可以省略。
  表达式索引可用于获取对基本数据的某种变形的快速访问。比如，一个在upper(col)上的函数索引将允许WHERE upper(col) = 'JIM'子句使用索引。
  在创建表达式索引时，如果表达式中包含IS NULL子句，则这种索引是无效的。此时，建议用户尝试创建一个部分索引。
  
- **COLLATE collation**
  COLLATE子句指定列的排序规则（该列必须是可排列的数据类型）。如果没有指定，则使用默认的排序规则。排序规则可以使用"select \* from pg_collation"命令从pg_collation系统表中查询，默认的排序规则为查询结果中以default开始的行。
  
- **opclass**
  操作符类的名称。对于索引的每一列可以指定一个操作符类，操作符类标识了索引那一列的使用的操作符。例如一个B-tree索引在一个四字节整数上可以使用int4_ops；这个操作符类包括四字节整数的比较函数。实际上对于列上的数据类型默认的操作符类是足够用的。操作符类主要用于一些有多种排序的数据。例如，用户想按照绝对值或者实数部分排序一个复数，可以通过定义两个操作符类，然后在建立索引时选择合适的类。当包含字符串类型（varchar、varchar2、text等）的索引的COLLATE的值不是C或者POSIX，但要求索引能够支持前缀匹配时，则需要指定varchar_pattern_ops选项。
  
- **ASC**
  指定按升序排序 （默认）。
  
- **DESC**
  指定按降序排序。
  
- **NULLS FIRST**
  指定空值在排序中排在非空值之前，当指定DESC排序时，本选项为默认的。
  
- **NULLS LAST**
  指定空值在排序中排在非空值之后，未指定DESC排序时，本选项为默认的。
  
- **WITH ( {storage_parameter = value} \[, ... \] )**
  指定索引方法的存储参数。
  取值范围：
  Psort之外的索引都支持FILLFACTOR参数。只有非分区表的BTREE索引支持DEDUPLICATION参数。
  - FILLFACTOR 一个索引的填充因子（fillfactor）是一个介于10和100之间的百分数。对于大并发插入且键值范围比较密集的场景，插入时同一个索引页面竞争比较大时，请选择较小的填充因子。
    取值范围：10\~100
    
  
  - ACTIVE_PAGES 表示索引的页面数量，可能比实际的物理文件页面少，可以用于优化器调优。目前只对ustore的分区表local索引生效，且会被vacuum、analyze更新（包括auto vacuum）。不建议用户手动设置该参数，该参数在分布式下无效。
    
  
  - DEDUPLICATION 索引参数，设置索引是否对键值重复的元组进行去重压缩。在重复键值的索引较多时，开启参数可以有效降低索引占用空间。对主键索引和唯一索引不生效。非唯一索引且索引键值重复度很低或者唯一的场景，开启参数会使索引插入性能小幅度劣化。暂不支持分区表的local/global索引。
    取值范围：布尔值，默认取GUC参数中enable_default_index_deduplication的值（默认为off）。
    
    
- **TABLESPACE tablespace_name**
  指定索引的表空间，如果没有声明则使用默认的表空间。
  取值范围：已存在的表空间名。
  
- **WHERE predicate**
  创建一个部分索引。部分索引是一个只包含表的一部分记录的索引，通常是该表中比其他部分数据更有用的部分。例如，有一个表，表里包含已记账和未记账的订单，未记账的订单只占表的一小部分而且这部分是最常用的部分，此时就可以通过只在未记账部分创建一个索引来改善性能。另外一个可能的用途是使用带有UNIQUE的WHERE强制一个表的某个子集的唯一性。
  取值范围：predicate表达式只能引用表的字段，它可以使用所有字段，而不仅是被索引的字段。目前，子查询和聚集表达式不能出现在WHERE子句里。不建议使用int等数值类型作为predicate，因为int等数值类型可以隐式转换为bool值（非0值隐式转换为true，0转换为false），可能导致非预期的结果。
  对于分区表索引，当创建索引带GLOBAL/LOCAL关键字，或者最终创建的索引类型为GLOBAL索引时，不支持带WHERE子句创建索引。
  
- **PARTITION index_partition_name**
  索引分区的名称。
  取值范围：字符串，要符合[标识符命名规范](https://support.huaweicloud.com/distributed-devg-v3-gaussdb/gaussdb-12-0319.html#ZH-CN_TOPIC_0000002235270221__zh-cn_topic_0000002235098221_section14873217506)。
  
- **TABLESPACE index_partition_tablespace**
  索引分区的表空间。
  取值范围：如果没有声明，将使用分区表索引的表空间index_tablespace。
  
 
#### 示例
```
--创建表tpcds.ship_mode_t1。
gaussdb=# CREATE SCHEMA tpcds;
gaussdb=# CREATE TABLE tpcds.ship_mode_t1
(
    SM_SHIP_MODE_SK           INTEGER               NOT NULL,
    SM_SHIP_MODE_ID           CHAR(16)              NOT NULL,
    SM_TYPE                   CHAR(30)                      ,
    SM_CODE                   CHAR(10)                      ,
    SM_CARRIER                CHAR(20)                      ,
    SM_CONTRACT               CHAR(20)
) 
DISTRIBUTE BY HASH(SM_SHIP_MODE_SK);
--在表tpcds.ship_mode_t1上的SM_SHIP_MODE_SK字段上创建普通的唯一索引。
gaussdb=# CREATE UNIQUE INDEX ds_ship_mode_t1_index1 ON tpcds.ship_mode_t1(SM_SHIP_MODE_SK);
--在表tpcds.ship_mode_t1上的SM_SHIP_MODE_SK字段上创建指定B-tree索引。
gaussdb=# CREATE INDEX ds_ship_mode_t1_index4 ON tpcds.ship_mode_t1 USING btree(SM_SHIP_MODE_SK);
--在表tpcds.ship_mode_t1上SM_CODE字段上创建表达式索引。
gaussdb=# CREATE INDEX ds_ship_mode_t1_index2 ON tpcds.ship_mode_t1(SUBSTR(SM_CODE,1 ,4));
--在表tpcds.ship_mode_t1上的SM_SHIP_MODE_SK字段上创建SM_SHIP_MODE_SK大于10的部分索引。
gaussdb=# CREATE UNIQUE INDEX ds_ship_mode_t1_index3 ON tpcds.ship_mode_t1(SM_SHIP_MODE_SK) WHERE SM_SHIP_MODE_SK>10;
--在表tpcds.ship_mode_t1上的SM_SHIP_MODE_SK字段上以不阻塞DML的方式创建索引。
gaussdb=# CREATE INDEX CONCURRENTLY ds_ship_mode_t1_index4 ON tpcds.ship_mode_t1(SM_SHIP_MODE_SK);
--重命名一个现有的索引。
gaussdb=# ALTER INDEX tpcds.ds_ship_mode_t1_index1 RENAME TO ds_ship_mode_t1_index5;
--设置索引不可用。
gaussdb=# ALTER INDEX tpcds.ds_ship_mode_t1_index2 UNUSABLE;
--重建索引。
gaussdb=# ALTER INDEX tpcds.ds_ship_mode_t1_index2 REBUILD;
--删除一个现有的索引。
gaussdb=# DROP INDEX tpcds.ds_ship_mode_t1_index2;
--删除表。
gaussdb=# DROP TABLE tpcds.ship_mode_t1;
--创建表空间。
gaussdb=# CREATE TABLESPACE example1 RELATIVE LOCATION 'tablespace1/tablespace_1';
gaussdb=# CREATE TABLESPACE example2 RELATIVE LOCATION 'tablespace2/tablespace_2';
gaussdb=# CREATE TABLESPACE example3 RELATIVE LOCATION 'tablespace3/tablespace_3';
gaussdb=# CREATE TABLESPACE example4 RELATIVE LOCATION 'tablespace4/tablespace_4';
--创建表tpcds.customer_address_p1。
gaussdb=# CREATE TABLE tpcds.customer_address_p1
(
    CA_ADDRESS_SK             INTEGER               NOT NULL,
    CA_ADDRESS_ID             CHAR(16)              NOT NULL,
    CA_STREET_NUMBER          CHAR(10)                      ,
    CA_STREET_NAME            VARCHAR(60)                   ,
    CA_STREET_TYPE            CHAR(15)                      ,
    CA_SUITE_NUMBER           CHAR(10)                      ,
    CA_CITY                   VARCHAR(60)                   ,
    CA_COUNTY                 VARCHAR(30)                   ,
    CA_STATE                  CHAR(2)                       ,
    CA_ZIP                    CHAR(10)                      ,
    CA_COUNTRY                VARCHAR(20)                   ,
    CA_GMT_OFFSET             DECIMAL(5,2)                  ,
    CA_LOCATION_TYPE          CHAR(20)
)
TABLESPACE example1
DISTRIBUTE BY HASH(CA_ADDRESS_SK)
PARTITION BY RANGE(CA_ADDRESS_SK)
( 
   PARTITION p1 VALUES LESS THAN (3000),
   PARTITION p2 VALUES LESS THAN (5000) TABLESPACE example1,
   PARTITION p3 VALUES LESS THAN (MAXVALUE) TABLESPACE example2
)
ENABLE ROW MOVEMENT;
--创建分区表索引ds_customer_address_p1_index1，不指定索引分区的名称。
gaussdb=# CREATE INDEX ds_customer_address_p1_index1 ON tpcds.customer_address_p1(CA_ADDRESS_SK) LOCAL; 
--创建分区表索引ds_customer_address_p1_index2，并指定索引分区的名称。
gaussdb=# CREATE INDEX ds_customer_address_p1_index2 ON tpcds.customer_address_p1(CA_ADDRESS_SK) LOCAL
(
    PARTITION CA_ADDRESS_SK_index1,
    PARTITION CA_ADDRESS_SK_index2 TABLESPACE example3,
    PARTITION CA_ADDRESS_SK_index3 TABLESPACE example4
) 
TABLESPACE example2;
--在线创建分区表索引ds_customer_address_p1_index3，不指定索引分区的名称。
gaussdb=# CREATE INDEX CONCURRENTLY ds_customer_address_p1_index3 ON tpcds.customer_address_p1(CA_ADDRESS_SK) LOCAL; 
--在线创建GLOBAL分区索引ds_customer_address_p1_index4
gaussdb=# CREATE INDEX CONCURRENTLY ds_customer_address_p1_index4 ON tpcds.customer_address_p1(CA_ADDRESS_ID) GLOBAL;
--修改分区表索引CA_ADDRESS_SK_index2的表空间为example1。
gaussdb=# ALTER INDEX tpcds.ds_customer_address_p1_index2 MOVE PARTITION CA_ADDRESS_SK_index2 TABLESPACE example1;
--修改分区表索引CA_ADDRESS_SK_index3的表空间为example2。
gaussdb=# ALTER INDEX tpcds.ds_customer_address_p1_index2 MOVE PARTITION CA_ADDRESS_SK_index3 TABLESPACE example2;
--重命名分区表索引。
gaussdb=# ALTER INDEX tpcds.ds_customer_address_p1_index2 RENAME PARTITION CA_ADDRESS_SK_index1 TO CA_ADDRESS_SK_index4;
--删除索引和分区表。
gaussdb=# DROP INDEX tpcds.ds_customer_address_p1_index1;
gaussdb=# DROP INDEX tpcds.ds_customer_address_p1_index2;
gaussdb=# DROP TABLE tpcds.customer_address_p1;
--删除表空间。
gaussdb=# DROP TABLESPACE example1;
gaussdb=# DROP TABLESPACE example2;
gaussdb=# DROP TABLESPACE example3;
gaussdb=# DROP TABLESPACE example4;
```
#### 相关链接
[ALTER INDEX](https://support.huaweicloud.com/distributed-devg-v3-gaussdb/gaussdb-12-0469.html#ZH-CN_TOPIC_0000002200349994)，[DROP INDEX](https://support.huaweicloud.com/distributed-devg-v3-gaussdb/gaussdb-12-0569.html#ZH-CN_TOPIC_0000002200350110)
#### 优化建议
- create index 建议仅在匹配如下条件之一时创建索引：
  - 经常执行查询的字段。
  
  - 在连接条件上创建索引，对于存在多字段连接的查询，建议在这些字段上建立组合索引。例如，select \* from t1 join t2 on t1.a=t2.a and t1.b=t2.b，可以在t1表上的a，b字段上建立组合索引。
  
  - where子句的过滤条件字段上（尤其是范围条件）。
  
  - 在经常出现在order by、group by和distinct后的字段。
  
  
  约束限制：
  - 普通表的索引支持最大列数为32列；分区表的GLOBAL索引支持最大列数为31列。
  
  - 单个索引大小不能超过索引页面大小（8k），其中B-tree、UBtree索引不能超过页面大小的三分之一。
  
  - 分区表上不支持创建部分索引。
    
 
