在 PostgreSQL 11 之前,创建索引和表数据更新是互斥的,也就是说创建索引时会持有一把锁,这时候任何对表数据的增加、更新、删除操作,都将等待索引创建完成才能继续执行。
创新互联专注为客户提供全方位的互联网综合服务,包含不限于成都网站制作、成都网站建设、外贸营销网站建设、井陉网络推广、微信小程序开发、井陉网络营销、井陉企业策划、井陉品牌公关、搜索引擎seo、人物专访、企业宣传片、企业代运营等,从售前售中售后,我们都将竭诚为您服务,您的肯定,是我们最大的嘉奖;创新互联为所有大学生创业者提供井陉建站搭建服务,24小时服务热线:028-86922220,官方网址:www.cdcxhl.com
如下面的例子:
-- 创建测试表,并向其中插入 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) AS id
) AS x;
ubuntu=# create index idx_a on articles (a);
ubuntu=# insert into articles(a, b, c) values ('1', '2', '3');
可以在事务执行期间,通过 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)
索引创建和表更新操作的互斥,带来一个严重的后果,那便是如果表数据量较大,创建索引的时间可能很长,如果长时间锁表的话,会导致表无法更新,可能会对在线业务产生很大的影响。
于是 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)
在并发创建索引的时候,如果遇到了不符预期的错误,或者手动取消,那么这个索引将会留在表中,但是被标识为 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) INVALID
注意:CREATE INDEX CONCURRENTLY 不能在事务块中执行,也就是说我们不能显式的 begin 开启事务然后执行 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 we're done, in the non-concurrent case */
table_close(rel, NoLock);
/* If this is the top-level index, we're done. */
if (!OidIsValid(parentIndexId))
pgstat_progress_end_command();
return address;
}
接着上面的代码往下看,就是 postgres 的并发创建索引逻辑,主要分为了三个步骤,这部分代码的注释也有一些相应的说明。
此阶段相当于 DefineIndex 的前一部分,和正常的 create index 的逻辑是相同的。
REINDEX 是一个更加复杂的命令,PostgreSQL 中也是支持对 REINDEX 进行 CONCURRENTLY 操作的,了解了 CREATE INDEX 之后,我们再来看看 Reindex Concurrently 是如何在 PostgreSQL 上执行的。
PostgreSQL 的 REINDEX 的主要逻辑在方法 ExecReindex 中,对 Reindex 的处理分为了三种情况:
这个方法是 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 的六个阶段:
ps. 在 Postgres 的官方文档中,也有对 Create Index/Reindex Concurrently 的描述,只是没有深入到代码细节之中,可以参考看下这两个步骤的执行步骤。
https://www.postgresql.org/docs/current/sql-createindex.htmlhttps://www.postgresql.org/docs/current/sql-reindex.html
文章名称:PostgreSQL中的并发创建索引
网站地址:http://www.shufengxianlan.com/qtweb/news29/81729.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联