SQL COMMAND · 索引与统计
CREATE INDEX
定义一个新索引
CREATE索引与统计引入 10(基线)现存至 20 devel3 次语法变更
- 动词
CREATE- 对象
INDEX- 分组
- 索引与统计
- 引入版本
- 10(基线)
- 状态
- 现存
- 语法变更次数
- 3
- 手册小节数
- 6
版本轨迹
相对 PostgreSQL 17 语法未变,正文有更新。
语法铁道图 PostgreSQL 18
沿轨道从左向右阅读,分岔表示选择,绕行表示可选,回环表示重复。方框为参数,点击带下划线的参数可展开子规则。
锁模式 PostgreSQL 18
具体锁模式取决于操作变体、目标对象和执行阶段;同一命令可同时取得表锁与行锁。查看完整冲突矩阵
SHARE UPDATE EXCLUSIVE· 表级锁CREATE INDEX CONCURRENTLY:作用于被索引的表;多个事务阶段构建并等待旧事务,不表示无等待。SHARE· 表级锁CREATE INDEX(不含 CONCURRENTLY):作用于被索引的表,阻止表上并发写入。
语法概要
CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ]name] ON [ ONLY ]table_name[ USINGmethod] ( {column_name| (expression) } [ COLLATEcollation] [opclass[ (opclass_parameter=value[, ... ] ) ] ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ] [, ...] ) [ INCLUDE (column_name[, ...] ) ] [ NULLS [ NOT ] DISTINCT ] [ WITH (storage_parameter[=value] [, ... ] ) ] [ TABLESPACEtablespace_name] [ WHEREpredicate]
描述
CREATE INDEX在指定关系的指定列上构建一个索引,该关系可以是表或物化视图。索引主要用于提升数据库性能(但使用不当也可能导致性能下降)。
索引的键字段指定为列名,或者指定为写在圆括号中的表达式。如果索引方法支持多列索引,则可以指定多个字段。
索引字段可以是根据表行中一个或多个列值计算得到的表达式。该特性可用于根据基础数据的某种变换来快速访问数据。例如,在upper(col)上计算的索引可让子句 WHERE upper(col) = 'JIM'使用索引。
PostgreSQL提供了索引方法 B-树、hash、GiST、SP-GiST、GIN 以及 BRIN。用户也可以定义自己的索引方法,但这相当复杂。
当WHERE子句存在时,会创建一个部分索引。部分索引只包含表中一部分行的索引项,通常这一部分比表的其余部分更适合建立索引。例如,如果一个表同时包含已开票和未开票订单,而未开票订单只占整个表的一小部分,但这一部分又经常被访问,就可以只对这部分创建索引来提升性能。另一种可能的应用是将WHERE与UNIQUE结合使用,以便在表的一个子集上强制唯一性。更多讨论请见第 11.8 节。
WHERE子句中使用的表达式只能引用底层表的列,但它可以使用所有列,而不仅仅是被索引的列。当前,WHERE中也禁止使用子查询和聚合表达式。同样的限制也适用于作为表达式的索引字段。
所有在索引定义中使用的函数和操作符都必须是“不可变的”,也就是说,它们的结果只能依赖其参数,而不能受任何外部因素影响(例如另一个表的内容或当前时间)。这种限制确保索引的行为定义明确。要在索引表达式或WHERE子句中使用用户定义的函数,记得在创建该函数时将其标记为不可变。
参数
UNIQUE使系统在创建索引时(如果数据已经存在)以及每次添加数据时,检查表中的重复值。任何会导致重复项的插入或更新操作都会报错。
当唯一索引用于分区表时,还会有额外限制;参见CREATE TABLE。
CONCURRENTLY当使用了这个选项时,PostgreSQL在构建索引时不会取得任何会阻止该表上并发插入、更新或删除的锁。而标准的索引构建会阻止该表上的写入(但不会阻止读取),直到索引构建完成。使用这个选项时有若干注意事项 — 请见Building Indexes Concurrently。
对于临时表,
CREATE INDEX始终是非并发的,因为没有其他会话可以访问它们,而且非并发创建索引的代价更低。IF NOT EXISTS如果同名关系已存在,则不抛出错误,而是发出一个提示。注意,现有索引并不保证与本应创建的索引有任何相似之处。指定
IF NOT EXISTS时,必须提供索引名。INCLUDE可选的
INCLUDE子句指定一个列列表,这些列将作为非键列包含在索引中。非键列不能用作索引扫描的搜索限定条件,在索引强制执行的任何唯一性或排他约束中也会被忽略。不过,非键列的内容可以由仅索引扫描返回,而无需访问索引对应的表,因为它们可直接从索引项中取得。因此,添加非键列可以让原本无法使用仅索引扫描的查询也能使用它。对于向索引中添加非键列,采取保守态度是明智的,尤其是宽列。如果索引元组超过该索引类型允许的最大尺寸,数据插入将失败。无论如何,非键列都会复制索引对应表中的数据并增大索引体积,因此可能拖慢搜索。此外,B-树去重技术绝不会用于带有非键列的索引。
INCLUDE子句中列出的列不需要合适的操作符类;该子句可以包含数据类型尚未为给定访问方法定义操作符类的列。不支持将表达式作为内含列,因为它们不能用于仅索引扫描。
当前,B-树、GiST 和 SP-GiST 索引访问方法支持此特性。在这些索引中,
INCLUDE子句所列出的列的值会包含在与堆元组对应的叶子元组中,但不会包含在用于树导航的上层索引项中。name要创建的索引的名称。这里不能包含模式名称;索引总是在其父表所在的模式中创建。索引的名称必须与该模式中的任何其他关系(表、序列、索引、视图、物化视图或外部表)的名称不同。如果省略名称,PostgreSQL会根据父表的名称和索引列的名称选择一个合适的名称。
ONLY表示如果该表是分区表,则不要递归地在各分区上创建索引。默认会递归。
table_name要建立索引的表名(可以是模式限定名)。
method要使用的索引方法的名称。选择包括
btree、hash、gist、spgist、gin、brin,或用户安装的访问方法,如 bloom。默认方法是btree。column_name一个表列的名称。
expression一个基于表中一个或多个列的表达式。通常必须像语法中所示那样写在外围圆括号中。不过,如果该表达式是函数调用形式,则可以省略圆括号。
collation将用于该索引的排序规则名称。默认情况下,索引使用被索引列声明的排序规则,或者被索引表达式的结果排序规则。对于涉及使用非默认排序规则表达式的查询,使用非默认排序规则的索引可能会很有用。
opclass一个操作符类的名称。详见下文。
opclass_parameter一个操作符类参数的名称。详见下文。
ASC指定升序排序(默认)。
DESC指定降序排序。
NULLS FIRST指定把空值排序在非空值前面。在指定
DESC时,这是默认行为。NULLS LAST指定把空值排序在非空值后面。在没有指定
DESC时,这是默认行为。NULLS DISTINCTNULLS NOT DISTINCT指定对于唯一索引,是否应将空值视为不同(而非相等)。默认情况下,它们是不同的,因此唯一索引可以在列中包含多个空值。
storage_parameter索引方法相关的存储参数的名称。详见Index Storage Parameters。
tablespace_name在其中创建索引的表空间。如果未指定,将查阅default_tablespace;对于临时表上的索引,则查阅temp_tablespaces。
predicate部分索引的约束表达式。
索引存储参数
可选的WITH子句为索引指定存储参数。每一种索引方法都有其各自允许的存储参数集合。
B-树、hash、GiST 和 SP-GiST 索引方法都接受以下参数:
fillfactor(integer)控制索引方法尝试将索引页填充到多满。对于 B-树,在初始构建索引时,以及在右侧扩展索引(加入新的最大键值)时,叶子页都会填充到这一百分比。如果页面随后变成全满,就会发生分裂,从而导致磁盘上的索引结构碎片化。B-树默认使用 90 的填充因子,但也可以选择 10 到 100 之间的任意整数值。
对预计会有大量插入和/或更新的表,在
CREATE INDEX时为 B-树索引设置较低的填充因子会有好处(即在向表批量装载数据之后)。50 到 90 范围内的值可以有效地“平滑”B-树索引生命周期早期页面分裂的速率(这样降低填充因子甚至可能减少页面分裂的绝对数量,不过这一效果高度依赖工作负载)。第 65.1.4.2 节中描述的 B-树自底向上索引删除技术依赖于页面上有一些“额外”空间来存储“额外”的元组版本,因此也会受到填充因子影响(不过通常影响不大)。在某些其他特定情况下,在
CREATE INDEX时将填充因子提高到 100 可能有助于最大化空间利用率。只有在完全确定该表是静态的(即永远不会受到插入或更新影响)时,才应考虑这样做。否则,将填充因子设为 100 有损害性能的风险:即便只有少量更新或插入,也会导致页面突然大量分裂。其他索引方法以不同但大致类似的方式使用填充因子;默认填充因子因方法而异。
B-树索引还接受以下参数:
deduplicate_items(boolean)控制是否使用第 65.1.4.3 节中描述的 B-树去重技术。设置为
ON或OFF可启用或禁用该优化。(ON和OFF的其他拼写形式也被接受,如第 19.1 节所述。)默认值为ON。注意
通过
ALTER INDEX关闭deduplicate_items可以防止将来的插入触发去重,但这本身不会让现有倒排列表元组改用标准的元组表示。
GiST 索引还接受以下参数:
buffering(enum)控制是否使用第 65.2.4.1 节中描述的缓冲构建技术来构建索引。设置为
OFF时禁用缓冲构建,设置为ON时启用,设置为AUTO时初始为禁用,但一旦索引大小达到effective_cache_size,就会在运行过程中启用。默认值为AUTO。注意,如果可以使用排序构建,则除非指定buffering=ON,否则会改用排序构建而不是缓冲构建。
GIN 索引接受以下参数:
fastupdate(boolean)控制第 65.4.4.1 节中描述的快速更新技术的使用。
ON启用快速更新,OFF禁用快速更新。默认值为ON。注意
通过
ALTER INDEX关闭fastupdate会阻止后续插入进入待处理索引项列表,但这本身不会将现有条目移出待处理列表。之后可能需要对该表执行VACUUM,或调用gin_clean_pending_list函数,以确保待处理列表被清空。
gin_pending_list_limit(integer)为该索引覆盖gin_pending_list_limit的全局设置。该值以千字节为单位。
BRIN 索引接受以下参数:
pages_per_range(integer)定义每个BRIN索引项对应的一个块范围由多少个表块组成(详见第 65.5.1 节)。默认值为
128。autosummarize(boolean)定义当在下一页范围检测到插入时,是否为前一页范围排队执行一次范围摘要操作(详见第 65.5.1.1 节)。默认值为
off。
并发构建索引
创建索引可能会干扰数据库的正常运行。通常 PostgreSQL会锁住要建立索引的表,阻止其写入,并通过一次扫描完成整个索引构建。其他事务仍可读取该表,但如果它们试图在表中插入、更新或删除行,就会阻塞直到索引构建完成。如果系统是在线生产数据库,这可能产生严重影响。对非常大的表建立索引可能需要很多小时,即便是较小的表,索引构建也可能在一段对生产系统而言不可接受的时间内阻止写入者操作。
PostgreSQL支持在不阻止写入的情况下构建索引。这种方法通过在CREATE INDEX中指定 CONCURRENTLY选项来启用。使用该选项时,PostgreSQL必须对该表执行两次扫描,此外还必须等待所有现有、可能修改或使用该索引的事务结束。因此,这种方法比标准索引构建需要更多总工作量,完成时间也明显更长。不过,由于它允许在构建索引期间继续进行正常操作,所以这种方法适合在生产环境中新增索引。当然,创建索引带来的额外 CPU 和 I/O 负载也可能拖慢其他操作。
在并发索引构建中,索引实际上会先在一个事务中作为“无效”索引录入系统目录,然后在另外两个事务中执行两次表扫描。每次表扫描之前,索引构建都必须等待已修改该表的现有事务结束。第二次扫描之后,索引构建还必须等待所有持有早于第二次扫描的快照(参见第 13 章)的事务结束;如果相关索引是部分索引,或包含并非简单列引用的列,这还包括其他表上并发索引构建任何阶段所使用的事务。最后,索引才能被标记为“有效”并可供使用,而 CREATE INDEX命令随之结束。即便如此,索引也未必能立即用于查询:最坏情况下,只要仍存在早于索引构建开始的事务,它就不能被使用。
如果在扫描表时出现问题,例如死锁或唯一索引中的唯一性违反,CREATE INDEX命令将失败,但会留下一个“无效”索引。这个索引在查询时会被忽略,因为它可能不完整;但它仍会带来更新开销。psql \d命令会将这样的索引报告为 INVALID:
postgres=# \d tab
Table "public.tab"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
col | integer | | |
Indexes:
"idx" btree (col) INVALID
在这种情况下,推荐的恢复方法是删除该索引,然后再次尝试执行 CREATE INDEX CONCURRENTLY。(另一种做法是用 REINDEX INDEX CONCURRENTLY重建索引。)
并发构建唯一索引时的另一项注意事项是,在第二次表扫描开始时,唯一性约束就已经开始对其他事务生效了。这意味着在该索引可供使用之前,其他查询就可能报告约束违规,甚至在索引构建最终失败的情况下也是如此。另外,如果第二次扫描确实失败了,那个“无效”索引之后仍会继续强制执行其唯一性约束。
也支持并发构建表达式索引和部分索引。计算这些表达式时发生的错误,可能导致与上文所述唯一性约束违规类似的行为。
普通索引构建允许在同一张表上同时进行其他普通索引构建,但一张表在同一时刻只能发生一次并发索引构建。无论哪种情况,在索引构建期间都不允许修改该表的模式。另一个区别是,普通CREATE INDEX 命令可以在事务块内执行,而CREATE INDEX CONCURRENTLY 不能。
当前不支持对分区表的索引进行并发构建。不过,可以分别在每个分区上并发构建索引,最后再以非并发方式创建分区索引,从而缩短分区表写入被阻止的时间。在这种情况下,构建分区索引只是元数据操作。
注解
关于索引何时能被使用、何时不被使用以及什么情况下它们有用的信息请见第 11 章。
当前,只有 B-树、GiST、GIN 和 BRIN 索引方法支持多键列索引。能否拥有多个键列,与能否向索引添加INCLUDE列无关。索引最多可以有 32 列,包括INCLUDE列。(可以在构建PostgreSQL时修改这个限制)。当前只有 B-树支持唯一索引。
可以为索引中的每一列指定一个带可选参数的操作符类。操作符类标识该索引在该列上要使用的操作符。例如,一个四字节整数上的 B-树索引会使用int4_ops类;这个操作符类包含用于四字节整数的比较函数。实际上,列数据类型的默认操作符类通常就足够了。引入操作符类的主要原因在于,对于某些数据类型,可能存在不止一种有意义的排序方式。例如,可能需要按绝对值或实部对复数数据类型排序。要做到这一点,可以为该数据类型定义两个操作符类,然后在创建索引时选择合适的类。关于操作符类的更多信息请见第 11.10 节以及第 36.16 节。
当在分区表上调用CREATE INDEX时,默认行为是递归到所有分区,以确保它们都拥有匹配的索引。系统首先检查每个分区是否已经存在等效索引;如果存在,该索引会作为正在创建索引的分区索引附接到其上,而被创建的索引将成为其父索引。若不存在匹配索引,则会创建一个新索引并自动附着;每个分区中新索引的名称会按命令中未指定索引名时的规则确定。如果指定ONLY选项,则不进行递归,且该索引会被标记为无效。(一旦所有分区都获得匹配索引,ALTER INDEX ... ATTACH PARTITION会将该索引标记为有效。)不过请注意,将来使用 CREATE TABLE ... PARTITION OF创建的任何分区都会自动拥有匹配索引,无论是否指定了ONLY。
对于支持有序扫描的索引方法(当前只有 B-树),可以指定可选子句 ASC、DESC、NULLS FIRST 和/或NULLS LAST来修改索引的排序顺序。由于有序索引可以向前或向后扫描,因此创建单列DESC索引通常并无用处 — 常规索引已经提供了这种排序顺序。这些选项的价值在于可以创建与混合排序查询所要求顺序相匹配的多列索引,例如 SELECT ... ORDER BY x ASC, y DESC。如果需要在依靠索引避免排序步骤的查询中支持“空值排在低位”,而不是默认的“空值排在高位”行为,那么NULLS选项就很有用。
系统定期收集表中所有列的统计信息。新创建的非表达式索引可以立即使用这些统计信息来确定索引的有用性。对于新的表达式索引,需要运行 ANALYZE 或等待自动清理守护进程分析表以生成这些索引的统计信息。
在 CREATE INDEX 执行期间,search_path会被临时设置为 pg_catalog, pg_temp。
对于大多数索引方法,索引的创建速度取决于maintenance_work_mem的设置。较大的值将会减少索引创建所需的时间,当然不要把它设置得超过实际可用的内存量(那会迫使机器进行交换)。
PostgreSQL可以在构建索引时利用多个 CPU,以便更快处理表中的行。这项特性被称为并行索引构建。对于支持并行构建索引的索引方法(当前为 B-树、GIN 和 BRIN),maintenance_work_mem指定每次索引构建操作整体最多可使用的内存量,而不管启动了多少工作者进程。通常,代价模型会自动决定是否需要请求工作者进程,以及请求多少个。
增加maintenance_work_mem可能会让并行索引构建受益,而等效的串行索引构建则几乎看不到收益。注意,maintenance_work_mem可能会影响所请求工作者进程的数量,因为每个并行工作者都必须至少从总的 maintenance_work_mem预算中分得32MB。领导者进程也必须保留32MB。增加max_parallel_maintenance_workers可能允许使用更多工作者,从而减少索引创建所需时间,前提是索引构建尚未受到 I/O 限制。当然,还应当有足够的、原本会闲置的 CPU 容量。
通过ALTER TABLE 为parallel_workers设置一个值,可以直接控制 CREATE INDEX对该表会请求多少并行工作者进程。这会完全绕过代价模型,也使maintenance_work_mem不再影响所请求的并行工作者数量。通过ALTER TABLE将 parallel_workers设置为 0,将在所有情况下禁用该表上的并行索引构建。
提示
在把parallel_workers作为索引构建调优的一部分进行设置后,可能需要将其重置。这样可以避免无意间改变查询计划,因为 parallel_workers会影响所有并行表扫描。
虽然带有CONCURRENTLY选项的CREATE INDEX 在没有特殊限制的情况下支持并行构建,但实际上只有第一次表扫描会以并行方式执行。
使用DROP INDEX 可以移除索引。
和任何长时间运行的事务一样,在某个表上执行CREATE INDEX 会影响在其他任何表上并发执行VACUUM时哪些元组可以被移除。
早期版本的PostgreSQL还提供过一种 R-tree 索引方法。该方法已经被移除,因为它相对于 GiST 方法并无明显优势。如果指定了USING rtree,CREATE INDEX 会将其解释为USING gist,以简化旧数据库向 GiST 的转换。
每个运行CREATE INDEX的后端将在 pg_stat_progress_create_index视图中报告其进度。有关详细信息,请参见第 27.4.4 节。
示例
要在表films的列title上创建一个唯一 B-树索引:
CREATE UNIQUE INDEX title_idx ON films (title);
要在表films的列title上创建一个唯一 B-树索引,并将director和rating作为内含列:
CREATE UNIQUE INDEX title_idx ON films (title) INCLUDE (director, rating);
要创建一个禁用去重的 B-树索引:
CREATE INDEX title_idx ON films (title) WITH (deduplicate_items = off);
要在表达式lower(title)上创建一个索引,以便高效执行不区分大小写的搜索:
CREATE INDEX ON films ((lower(title)));
(在这个示例中,索引名称被省略,因此系统会选择一个名字,通常为films_lower_idx。)
要创建一个具有非默认排序规则的索引:
CREATE INDEX title_idx_german ON films (title COLLATE "de_DE");
要创建一个具有非默认空值排序顺序的索引:
CREATE INDEX title_idx_nulls_low ON films (title NULLS FIRST);
要创建一个具有非默认填充因子的索引:
CREATE UNIQUE INDEX title_idx ON films (title) WITH (fillfactor = 70);
要创建一个禁用快速更新的GIN索引:
CREATE INDEX gin_idx ON documents_table USING GIN (locations) WITH (fastupdate = off);
要在表films的列code上创建一个索引,并让该索引驻留在表空间indexspace中:
CREATE INDEX code_idx ON films (code) TABLESPACE indexspace;
要在点属性上创建一个 GiST 索引,以便能够在转换函数的结果上高效地使用 box 操作符:
CREATE INDEX pointloc
ON points USING gist (box(location,location));
SELECT * FROM points
WHERE box(location,location) && '(0,0),(1,1)'::box;
要在不阻止对表执行写操作的情况下创建索引:
CREATE INDEX CONCURRENTLY sales_quantity_index ON sales_table (quantity);
兼容性
CREATE INDEX是 PostgreSQL的语言扩展。SQL 标准中没有关于索引的规定。
另见
语法演化
相邻大版本之间的差异,新的在前。版本号链接到对应快照。
PostgreSQL 18← 17正文更新
正文更新PostgreSQL 17← 16正文更新
正文更新PostgreSQL 16← 15正文更新
正文更新PostgreSQL 15← 14语法变化
+ [ NULLS [ NOT ] DISTINCT ]+ [ WITH ( storage_parameter [= value] [, ... ] ) ]正文更新PostgreSQL 14← 13正文更新
正文更新PostgreSQL 13← 12语法变化
+ ( { column_name | ( expression ) } [ COLLATE collation ] [ opclass [ ( opclass_parameter = value [, ... ] ) ] ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ] [, ...] )正文更新PostgreSQL 12← 11正文更新
正文更新PostgreSQL 11← 10语法变化
+ CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] name ] ON [ ONLY ] table_name [ USING method ]+ [ INCLUDE ( column_name [, ...] ) ]+ [ WITH ( storage_parameter [= value] [, ... ] ) ]正文更新
同组命令
| 命令 | 动词 | 对象 | 版本变动 | 最近变更 |
|---|---|---|---|---|
REINDEX | REINDEX | — | 164 次 | |
| 重建索引 | 现存 | |||
CREATE INDEX | CREATE | INDEX | 153 次 | |
| 定义一个新索引 | 现存 | |||
ALTER INDEX | ALTER | INDEX | 132 次 | |
| 更改索引的定义 | 现存 | |||
DROP INDEX | DROP | INDEX | — | |
| 移除一个索引 | 现存 | |||
CREATE STATISTICS | CREATE | STATISTICS | 193 次 | |
| 定义扩展统计信息 | 现存 | |||
ALTER STATISTICS | ALTER | STATISTICS | 173 次 | |
| 更改扩展统计对象的定义 | 现存 | |||
DROP STATISTICS | DROP | STATISTICS | — | |
| 删除扩展统计对象 | 现存 | |||