PostgreSQL 中的并发创建索引
一、创建初体验 Create Index Concurrently
在 PostgreSQL 11 之前,索引创建索引和表数据更新是创建互斥的,也就是索引说创建索引时会持有一把锁,这时候任何对表数据的创建增加、更新、索引删除操作,创建都将等待索引创建完成才能继续执行。索引
如下面的创建例子:
创建示例表 复制-- 创建测试表,并向其中插入 500w 行随机字符串数据CREATE TABLE articles ( id SERIAL8 NOT NULL PRIMARY KEY,索引 a text, b text, c text);INSERT INTO articles(a, b, c)SELECT md5(random()::text), md5(random()::text), md5(random()::text)from ( SELECT * FROM generate_series(1,5000000) ASid
) AS x;1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17. 打开一个 psql 客户端,执行创建索引操作 复制ubuntu=# create index idx_a on 创建articles (a);1. 索引创建时打开另一个 psql 客户端,并向表中插入数据,索引此时另一个事务已经持有表锁了,创建所以会一直等待事务结束之后才会继续执行 复制ubuntu=# insert into articles(a,索引 b, c) values (1, 2, 3);1.可以在事务执行期间,通过 pg_locks 表查看事务持有的创建锁,可以看到创建索引的操作占据了 ShareLock(5 号锁),插入操作需要获取 RowExclusiveLock 锁,而这两者是互斥的。
复制ubuntu=# select * from pg_locks where relation = articles::regclass; locktype | database | relation | page | tuple | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction | pid | mode | granted | fastpath |waitstart
----------+----------+----------+------+-------+------------+---------------+---------+-------+----------+--------------------+---------+------------------+---------+----------+------------------------------ relation | 2638325 | 2638341 | | | | | | | | 3/22624 | 1236742 | RowExclusiveLock | f | f |2023-01-13 14:08:32.54543+08 relation | 2638325 | 2638341 | | | | | | | | 4/209 | 1236951 | ShareLock | t | f | relation | 2638325 | 2638341 | | | | | | | | 6/20 | 1237182 | ShareLock | t | f |(3 rows)1.2.3.4.5.6.7.8.9.10.索引创建和表更新操作的互斥,带来一个严重的后果,那便是如果表数据量较大,创建索引的服务器租用时间可能很长,如果长时间锁表的话,会导致表无法更新,可能会对在线业务产生很大的影响。
于是 PostgreSQL 在 11 版本中支持了并发创建索引,即 CREATE INDEX CONCURRENTLY,其主要功能是在创建索引的时候,不阻塞表数据的更新。
还是看上面的示例,只需要将第一个事务的 sql 修改为 create index CONCURRENTLY idx_a on articles (a);,那么其他事务的表数据更新操作将会正常执行,不会被阻塞。
然后再看其持有的锁,可以看到已经变成了 ShareUpdateExclusiveLock(4 号锁):
复制ubuntu=# select * from pg_locks where relation = articles::regclass; locktype | database | relation | page | tuple | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction | pid | mode | granted |fas
tpath |waitstart
----------+----------+----------+------+-------+------------+---------------+---------+-------+----------+--------------------+---------+--------------------------+---------+----------+----------- relation | 2638325 | 2638341 | | | | | | | | 4/214 | 1236951 | ShareUpdateExclusiveLock | t |f
|(1 row)1.2.3.4.5.6.7.8.在并发创建索引的时候,如果遇到了不符预期的错误,或者手动取消,那么这个索引将会留在表中,但是被标识为 INVALID,表示这个索引不可用,源码库也就是说将不会使用这个索引进行索引扫描。
后续可以手动将其 DROP 掉,然后重新建立索引,也可以执行 REINDEX CONCURRENTLY 重建索引。
复制ubuntu=# \d articles
Table "public.articles" Column | Type | Collation | Nullable |Default
--------+--------+-----------+----------+-------------------------------------- id | bigint | | not null | nextval(articles_id_seq::regclass) a | text | | | b | text | | | c | text | | |Indexes: "articles_pkey" PRIMARY KEY, btree (id) "idx_body" btree (a) INVALID1.2.3.4.5.6.7.8.9.10.11.注意:CREATE INDEX CONCURRENTLY 不能在事务块中执行,也就是说我们不能显式的 begin 开启事务然后执行 CREATE INDEX CONCURRENTLY。
二、CREATE INDEX CONCURRENTLY 的三个步骤
主要的代码位置在 https://github.com/postgres/postgres/blob/master/src/backend/commands/indexcmds.c#L488
DefineIndex 方法中主要是处理索引创建的逻辑,方法前面部分主要是做一系列校验和参数初始化等,然后调用 index_create 方法将索引的元信息存储到 pg_index、pg_class 等表中。
并且如果判断到不是 concurrently 创建索引的话,这里会直接返回,也就是说这之后的逻辑都是处理 CONCURRENTLY 并发索引创建的部分。
复制if (!concurrent){ /* Close the heap and were done, in the non-concurrent case */ table_close(rel, NoLock); /* If this is the top-level index, were done. */ if (!OidIsValid(parentIndexId)) pgstat_progress_end_command(); return address;}1.2.3.4.5.6.7.8.9.10.11.接着上面的代码往下看,就是 postgres 的免费源码下载并发创建索引逻辑,主要分为了三个步骤,这部分代码的注释也有一些相应的说明。
开启一个事务,通过 index_create 方法,将索引的元数据信息存储到 pg_class、pg_index 表中,并且标识索引的 indisready 和 indisvalid 属性为 false,表示目前索引没有 ready 并且不可用_;_提交事务,并开启一个新的事务进入下一阶段。此阶段相当于 DefineIndex 的前一部分,和正常的 create index 的逻辑是相同的。
1. 进入此阶段时,需要等待系统中其他正在写数据的事务提交,因为必须让新索引的定义对其他所有的事务都可见,保证 HOT 更新满足表的索引定义,调用 WaitForLockers 函数进行等待 2. 通过 index_concurrently_build 创建索引,同时持有 4 号锁,不阻塞表的增/删/改操作;获取一个 MVCC 快照,将此快照下可见的元组写入到索引中;此时如果有其他的事务有新的写入,将不会插入到索引中,只是保证 HOT 满足索引的定义;提交事务,将索引设置为 isready,后续如果有新的数据插入,将会维护这个索引,但是索引仍然是 invalid,不能用于索引扫描。并再开启一个新的事务,进入到下一阶段。1. 又要等待当前所有写事务都提交,保证所有的事务都能看到新索引的定义 2. 在第二阶段的执行过程中,有可能又有新的元组插入到表中,所以需要再获取一个新的 MVCC 快照,并将新的元组插入到索引中,调用函数 validate_index 3. 此时还需要一次等待,将指定 xmin 的之前的事务提交,调用函数 WaitForOlderSnapshots 4. 最后将索引置为 valid,后续其他事务便可以使用该索引进行查询了三、Reindex Concurrently
REINDEX 是一个更加复杂的命令,PostgreSQL 中也是支持对 REINDEX 进行 CONCURRENTLY 操作的,了解了 CREATE INDEX 之后,我们再来看看 Reindex Concurrently 是如何在 PostgreSQL 上执行的。
ExecReindex
PostgreSQL 的 REINDEX 的主要逻辑在方法 ExecReindex 中,对 Reindex 的处理分为了三种情况:
REINDEX_OBJECT_INDEX(针对索引)REINDEX_OBJECT_TABLE(针对表)REINDEX_OBJECT_SCHEMA、REINDEX_OBJECT_SYSTEM、REINDEX_OBJECT_DATABASE(针对 schema、系统表、整个库)
ReindexRelationConcurrently
这个方法是 Reindex Concurrently 的主要实现逻辑,首先会根据传入的 relationOid,找到所有需要进行 Reindex 的 indexId,并且跳过一些不能进行 Reindex 的索引,例如系统 catalog 表不支持 Reindex。
主要的代码位置:https://github.com/greenplum-db/gpdb/blob/main/src/backend/commands/indexcmds.c#L3575
拿到需要进行 Reindex 的索引 Oid 之后,然后进入 Reindex Concurrently 的六个阶段:
创建新的索引,创建后表中有一个临时的新的索引,名称以 idx_ccnew 开头build 新创建的索引,即扫描全表数据,构建索引的内容validate 新创建的索引,将前一个阶段新插入的数据加入到索引中,这个和 create index concurrently 类似交换索引,将新创建的索引和它对应的需要 reindex 的索引进行交换,旧的索引标识为 invalid将旧的索引设置为 dead 状态,即 indislive、indisready、indisvalid 均为 false最后将旧的索引 drop 掉ps. 在 Postgres 的官方文档中,也有对 Create Index/Reindex Concurrently 的描述,只是没有深入到代码细节之中,可以参考看下这两个步骤的执行步骤。
https://www.postgresql.org/docs/current/sql-createindex.htmlhttps://www.postgresql.org/docs/current/sql-reindex.html
相关文章
教你如何组装一整的台式机配置(打造个性化电脑,尽在你的掌握之中)
摘要:在电脑科技日新月异的今天,定制一台个性化的台式机已经成为了许多科技爱好者的追求。不仅可以根据自己的需求选择合适的硬件配置,还可以通过自己的双手完成组装过程,使得整个使用体验更加顺畅...2025-11-04- 复制mysql>GRANTALLPRIVILEGESON*.*TOadmin@localhostIDENTIFIEDBYsomethingWITHGRANTOPTION;2025-11-04

safari浏览器开发者工具怎么打开_Safari浏览器调试与开发工具启用方法
首先启用Safari开发者菜单:进入设置→高级,勾选“在菜单栏中显示‘开发’菜单”;随后可通过开发菜单或快捷键Option+Command+I打开Web检查器;也可右键选择“检查元素”定位DOM;iO2025-11-04- 复制mysql>select*fromtableLIMIT6,10; 1.2025-11-04
- 摘要:启动盘是一种非常有用的工具,可以帮助我们解决电脑系统崩溃、病毒感染等问题。然而,很多人并不知道如何制作启动盘,因此本文将为大家详细介绍以启动盘复制为主题的完整教程。选择适合...2025-11-04
春节期间,Deepseek以其卓越性能赢得众多技术爱好者的关注。用户评价褒贬不一,但国际巨头的震惊足以证明其非凡之处。若你想在本地部署该模型,探索其强大功能,以下指南将为你提供详细指导。本文将介绍Wi2025-11-04

最新评论