PostgreSQL的 create index concurrently

本文涉及的产品
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
简介:

对于PostgreSQL的 "create index concurrently". 我个人认为其中存在一个bug。

我的验证过程如下:

我有两个表,tab01和 tab02,这两个表之间没有任何关联。

我认为 对 tab02执行 "create index concurrently" 不会对 访问tab01的事务有任何影响,然而事实并非尽然。

我第一程序的表现: 通过ecpg执行事务,再通过 "create index concurrently" 给tab02建立索引,成功。

我第二程序的表现:通过ecpg执行事务,再通过 "create index concurrently" 给tab02建立索引,被阻塞。

我第三个测试:      通过psql发起事务,  另一个psql客户端执行 "create index concurrently" 成功。

我第四个测试:    通过psql发起事务 另一个psql客户端执行 "create index concurrently",被阻塞。

无论 PostgreSQL9.1.2,还是PostgreSQL9.2.4,结果是一样的。

数据准备:

复制代码
[postgres@server bin]$ ./psql -U tester -d tester
psql (9.1.2)
Type "help" for help.
tester=> \d tab01;
          Table "public.tab01"
 Column |         Type         | Modifiers
--------+----------------------+-----------
 id     | integer              |
 cd     | character varying(4) |


tester=> \d tab02;
    Table "public.tab02"
 Column |  Type   | Modifiers
--------+---------+-----------
 id     | integer |
 value  | integer |

tester=> select * from tab01;
 id | cd
----+----
  1 | 14
  2 | 15
  3 | 14
(3 rows)

tester=> select * from tab02;
 id | value
----+-------
  1 |   100
  2 |   200
  3 |   300
(3 rows)
tester=>
复制代码

我的测试方法:

对第一个程序和第二个程序:

当我的eccp程序正在睡眠的时候,我另外开一个终端,执行: 

 "create index concurrently idx_tab02_id_new on tab02(id)"

结果是:

第一个程序执行中,我可成功建立索引。 
第二个程序执行中,我无法建立索引,会被阻塞
而我的tab01和tab02之间,没有任何关联。而且我也不认为我的ecpg程序会有潜在的可能去使用tab02的索引。

事实上,如果我去看ecpg预编译后得到的c程序,我可以看到:

复制代码
 { ECPGdo(__LINE__, 0, 1, "db_conn", 0, ECPGst_normal, "select count ( * ) from tab01 where cd = $1 ",

        ECPGt_char,(vcd),(long)4 + 1,(long)1,(4 + 1)*sizeof(char),

        ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT,

        ECPGt_int,&(vCount),(long)1,(long)1,sizeof(int),

        ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);}
复制代码

当我给$1加入引号后,我就可以成功地建立索引了。

复制代码
 { ECPGdo(__LINE__, 0, 1, "db_conn", 0, ECPGst_normal, "select count ( * ) from tab01 where cd = '$1' ",

        ECPGt_char,(vcd),(long)4 + 1,(long)1,(4 + 1)*sizeof(char),

        ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT,

        ECPGt_int,&(vCount),(long)1,(long)1,sizeof(int),

        ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);}
复制代码

下面是我测试程序的源代码:

第一个:

复制代码
[root@server soft]# cat ./test01/test01.pc
int main()
{

   EXEC SQL BEGIN DECLARE SECTION;
         int vCount;
         char vcd[4+1]; 
   EXEC SQL END DECLARE SECTION;
EXEC SQL CONNECT TO
'tester@127.0.0.1:5432' AS db_conn USER tester IDENTIFIED BY tester; EXEC SQL AT db_conn SELECT COUNT(*) INTO :vCount FROM tab01; fprintf(stderr,"count is:%d\n",vCount); fprintf(stderr,"Before disconnect,sleep for 500 seconds\n"); sleep(500); EXEC SQL DISCONNECT db_conn; fprintf(stderr,"After disconnect,sleep for 600 seconds\n"); sleep(600); return 0; } [root@server soft]#
复制代码

第二个:

复制代码
[root@server soft]# cat ./test02/test02.pc

int main()

{
   EXEC SQL BEGIN DECLARE SECTION;
         int vCount;
         char vcd[4+1];                        
   EXEC SQL END DECLARE SECTION;


   EXEC SQL CONNECT TO 'tester@127.0.0.1:5432' AS db_conn
     USER tester IDENTIFIED BY tester;

   char *pCd="14";
   memset(vcd,'\0',5);
   strncpy(vcd, pCd,4);             
                                                               
   EXEC SQL AT db_conn SELECT COUNT(*)            
        INTO :vCount FROM tab01 WHERE cd = :vcd;
                                                                       
   fprintf(stderr,"count is:%d\n",vCount);
fprintf(stderr,
"Before disconnect,sleep for 500 seconds\n"); sleep(500); EXEC SQL DISCONNECT db_conn; fprintf(stderr,"After disconnect,sleep for 600 seconds\n"); sleep(600); return 0; } [root@server soft]#
复制代码

而且,通过 psql,还可以发现一个与 create index concurrently 相关的现象:

我的第三个测试:

客户端1:

复制代码
[postgres@server pgsql]$ ./bin/psql -d tester -U tester
psql (9.1.2)
Type "help" for help.

tester=> begin;
BEGIN
tester=> select * from tab01 where cd = '14';
 id | cd
----+----
  1 | 14
  3 | 14
(2 rows)
tester=>
复制代码

客户端2: 

[postgres@server pgsql]$ ./bin/psql -d tester -U tester
psql (9.1.2)
Type "help" for help.

tester=> create index concurrently idx_tab02_id_new on tab02(id);

可以很快就成功创建索引。

我的第四个测试:

客户端1:

复制代码
[postgres@server pgsql]$ ./bin/psql -d tester -U tester
psql (9.1.2)
Type "help" for help.
tester=> begin;
BEGIN
tester=> select * from tab01 where cd = '14';
 id | cd
----+----
  1 | 14
  3 | 14
(2 rows)
 

tester=> select pg_sleep(500);
 pg_sleep
----------
(1 row)
tester=>
复制代码

客户端2:

[postgres@server pgsql]$ ./bin/psql -d tester -U tester
psql (9.1.2)
Type "help" for help.

tester=> create index concurrently idx_tab02_id_new on tab02(id);

客户端2的创建索引会被阻塞

根据我对PostgreSQL的源代码的跟踪,可以看到有如下的调用关系:

PortalRunMulti--> PortalRunUtility-->Standard_ProcessUtility-->DefineIndex

而我对DefineIndex作简化后,可以看到:

复制代码
{     
… old_snapshots
= GetCurrentVirtualXIDs(snapshot->xmin, true, false, PROC_IS_AUTOVACUUM | PROC_IN_VACUUM, &n_old_snapshots); for (i = 0; i < n_old_snapshots; i++) { … if (VirtualTransactionIdIsValid(old_snapshots[i])) VirtualXactLockTableWait(old_snapshots[i]); } … }
复制代码

对于我的第一个测试程序,GetCurrentVirtualXIDs 函数执行后,n_old_snapshots 的值为0 ,
for (i = 0; i < n_old_snapshots; i++) 循环不会被执行,索引的生成不会被阻塞。

对我的第二个测试程序,GetCurrentVirtualXIDs 函数执行后,n_old_snapshots 的值为1, 
for (i = 0; i < n_old_snapshots; i++) 循环会被执行。
VirtualXactLockTableWait(old_snapshots[i]) 的执行,导致等待一个锁,所以索引生成被阻塞。

再往下分析:

复制代码
VirtualTransactionId *
GetCurrentVirtualXIDs(TransactionId limitXmin, bool excludeXmin0,
                      bool allDbs, int excludeVacuum,
                      int *nvxids)
{
    VirtualTransactionId *vxids;
    ProcArrayStruct *arrayP = procArray;
    int            count = 0;
    int            index;

    /* allocate what's certainly enough result space */
    vxids = (VirtualTransactionId *)
        palloc(sizeof(VirtualTransactionId) * arrayP->maxProcs);

    LWLockAcquire(ProcArrayLock, LW_SHARED);
for (index = 0; index < arrayP->numProcs; index++)
    {
volatile PGPROC *proc = arrayP->procs[index];

        if (proc == MyProc)
            continue;

        if (excludeVacuum & proc->vacuumFlags)
            continue;

        if (allDbs || proc->databaseId == MyDatabaseId)
        {
 /* Fetch xmin just once - might change on us */
            TransactionId pxmin = proc->xmin;
if (excludeXmin0 && !TransactionIdIsValid(pxmin)) continue; /* * InvalidTransactionId precedes all other XIDs, so a proc that * hasn't set xmin yet will not be rejected by this test. */ if (!TransactionIdIsValid(limitXmin) || TransactionIdPrecedesOrEquals(pxmin, limitXmin)) { VirtualTransactionId vxid; GET_VXID_FROM_PGPROC(vxid, *proc);
if (VirtualTransactionIdIsValid(vxid)) vxids[count++] = vxid; } } } LWLockRelease(ProcArrayLock); *nvxids = count; return vxids; }
复制代码

对于我的第一个程序,测试结果显示:pxmin 为零,TransactionIdIsValid(pxmin) 为假。所以如下代码导致跳过循环一次。

if (excludeXmin0 && !TransactionIdIsValid(pxmin))
                continue;

没有机会执行 vxids[count++]=vxid 这一行。

那么pxmin是如何来的?

看这句: TransactionId pxmin = proc->xmin;

而xmin的含义是:当我们执行程序中对数据进行增删改的时候,会将当前transaction id 赋予给 xmin。

写记录的时候,把这个xmin写入该行记录头。

如此,每个进程看来,它只关心 xmin 小于自己的transaction id的。PostgreSQL用这种方式来保证MVCC。

但此处,proc->xmin为零是很不合理的。

此时,

if (allDbs || proc->databaseId == MyDatabaseId) 里的:
if (excludeXmin0 && !TransactionIdIsValid(pxmin))就会成立,所以会直接continue调回循环开始处,也就没有机会去
vxids[count++] = vxid;

在我的第二个程序里,proc->xmin根本就不为零。故此说,这是一个bug。

另外的佐证:对我的三个测试,运行下列SQL文:

pgsql=# select l.pid, l.mode, sa.procpid, sa.current_query
from pg_locks l
inner join pg_stat_activity sa
        on l.pid = sa.procpid
where l.mode like '%xclusive%';

一开始在pg_sleep(100)执行期间,可以看到:

复制代码
pgsql=# select l.pid, l.mode, sa.procpid, sa.current_query
from pg_locks l
inner join pg_stat_activity sa
        on l.pid = sa.procpid
where l.mode like '%xclusive%';
 pid  |     mode      | procpid |                   current_query                    
------+---------------+---------+----------------------------------------------------
 5356 | ExclusiveLock |    5356 | select l.pid, l.mode, sa.procpid, sa.current_query+
      |               |         | from pg_locks l                                   +
      |               |         | inner join pg_stat_activity sa                    +
      |               |         |         on l.pid = sa.procpid                     +
      |               |         | where l.mode like '%xclusive%';
 5517 | ExclusiveLock |    5517 | select pg_sleep(100);
(2 rows)
复制代码

我开另外的终端,执行 "create index concurrently"的时候,再看:

复制代码
pgsql=# select l.pid, l.mode, sa.procpid, sa.current_query
from pg_locks l
inner join pg_stat_activity sa
        on l.pid = sa.procpid
where l.mode like '%xclusive%';
 pid  |           mode           | procpid |                      current_query                       
------+--------------------------+---------+----------------------------------------------------------
 5356 | ExclusiveLock            |    5356 | select l.pid, l.mode, sa.procpid, sa.current_query      +
      |                          |         | from pg_locks l                                         +
      |                          |         | inner join pg_stat_activity sa                          +
      |                          |         |         on l.pid = sa.procpid                           +
      |                          |         | where l.mode like '%xclusive%';
 5517 | ExclusiveLock            |    5517 | select pg_sleep(100);
 5527 | ExclusiveLock            |    5527 | create index concurrently idx_tab02_id_new on tab02(id);
 5527 | RowExclusiveLock         |    5527 | create index concurrently idx_tab02_id_new on tab02(id);
 5527 | ShareUpdateExclusiveLock |    5527 | create index concurrently idx_tab02_id_new on tab02(id);
(5 rows)
复制代码

等到 pg_sleep执行完毕的时候:

复制代码
pgsql=# select l.pid, l.mode, sa.procpid, sa.current_query
from pg_locks l
inner join pg_stat_activity sa
        on l.pid = sa.procpid
where l.mode like '%xclusive%';
 pid  |           mode           | procpid |                      current_query                       
------+--------------------------+---------+----------------------------------------------------------
 5356 | ExclusiveLock            |    5356 | select l.pid, l.mode, sa.procpid, sa.current_query      +
      |                          |         | from pg_locks l                                         +
      |                          |         | inner join pg_stat_activity sa                          +
      |                          |         |         on l.pid = sa.procpid                           +
      |                          |         | where l.mode like '%xclusive%';
 5517 | ExclusiveLock            |    5517 | <IDLE> in transaction
 5527 | ExclusiveLock            |    5527 | create index concurrently idx_tab02_id_new on tab02(id);
 5527 | RowExclusiveLock         |    5527 | create index concurrently idx_tab02_id_new on tab02(id);
 5527 | ShareUpdateExclusiveLock |    5527 | create index concurrently idx_tab02_id_new on tab02(id);
(5 rows)
复制代码






相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
相关文章
|
23天前
|
存储 关系型数据库 MySQL
MySQL的优化利器⭐️Multi Range Read与Covering Index是如何优化回表的?
本文以小白的视角使用通俗易懂的流程图深入浅出分析Multi Range Read与Covering Index是如何优化回表
|
23天前
|
SQL 关系型数据库 MySQL
mysql查询语句的访问方法const、ref、ref_or_null、range、index、all
mysql查询语句的访问方法const、ref、ref_or_null、range、index、all
|
23天前
|
关系型数据库 MySQL 索引
mysql中force index强制索引
mysql中force index强制索引
15 0
|
23天前
|
关系型数据库 MySQL
mysql 5.5.62版本建表语句报错: Index column size too large. The maximum column size is 767 bytes
mysql 5.5.62版本建表语句报错: Index column size too large. The maximum column size is 767 bytes
|
23天前
|
关系型数据库 MySQL
MySQL【问题 02】报错 1709 - Index column size too large. The maximum column size is 767 bytes. 可能是最简单的方法
MySQL【问题 02】报错 1709 - Index column size too large. The maximum column size is 767 bytes. 可能是最简单的方法
83 0
|
存储 SQL 缓存
一文带你了解MySQL之Adaptive Hash Index
在InnoDB体系架构图的内存结构中,还有一块区域名为:Adaptive Hash Index,翻译成中文:自适应哈希索引,缩写:AHI,它是一个纯内存结构,我们今天就来了解它。
897 0
|
7月前
|
关系型数据库 MySQL 数据库
Mysql中key与index区别
Mysql中key与index区别
|
8月前
|
存储 SQL 关系型数据库
MySQL 优化 index merge(索引合并)引起的死锁分析(强烈推荐)
生产环境出现死锁流水,通过查看死锁日志,看到造成死锁的是两条一样的update语句(只有where条件中的值不同),如下:
|
11月前
|
关系型数据库 MySQL 索引
不会2023年你还不知道Mysql中index、primary key、unique key、foreign key是什么和如何创建吧?
不会2023年你还不知道Mysql中index、primary key、unique key、foreign key是什么和如何创建吧?
62 0
|
关系型数据库 MySQL 索引
MySQL 8 不可见索引 invisible index
不可见索引 创建不可见索引