PostgreSQL 子事务 id & command id 溢出问题分析

PostgreSQL 需要为每个savepoint或者函数的exception section分配子事务号,递增。

即使这个exception没有被触发,也需要一个子事务号。

PushTransaction@src/backend/access/transam/xact.c

        /*

         * Assign a subtransaction ID, watching out for counter wraparound.

         */

        currentSubTransactionId += 1;

        if (currentSubTransactionId == InvalidSubTransactionId)

        {

                currentSubTransactionId -= 1;

                pfree(s);

                ereport(ERROR,

                                (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),

                                 errmsg("cannot have more than 2^32-1 subtransactions in a transaction")));

        }


command id则是记录一个事务中产生写操作(例如ddl,dml)的SQL ID,递增。

CommandCounterIncrement@src/backend/access/transam/xact.c

        if (currentCommandIdUsed)

        {

                currentCommandId += 1;

                if (currentCommandId == InvalidCommandId)

                {

                        currentCommandId -= 1;

                        ereport(ERROR,

                                        (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),

                                         errmsg("cannot have more than 2^32-2 commands in a transaction")));

                }

                currentCommandIdUsed = false;


子事务 id和command id都是unsigned int类型,最大允许分配2^32-1个子事务,单个事务中最大允许分配2^32-2条COMMAND。

typedef uint32 SubTransactionId;

typedef uint32 CommandId;


子事务什么情况下可能溢出呢?

1. 在事务中累计使用的savepoint = n。

2. 在事务中有exception的函数,每个exception需要申请一个子事务,如果函数被多次调用,则需要计算多个子事务。假设函数exception需要的子事务个数=m。

如果n+m大于2^32-1,溢出。


command id什么情况下可能溢出呢?

一个事务中,包含的ddl,dml SQL超过2^32-2时。


跟踪方法:

                currentCommandId += 1;

// 添加如下

                ereport(NOTICE,

                        (errmsg("currentCommandId: %d", currentCommandId)));

                if (currentCommandId == InvalidCommandId)

                {

                        currentCommandId -= 1;

                        ereport(ERROR,

                                        (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),

                                         errmsg("cannot have more than 2^32-2 commands in a transaction")));

                }

                currentCommandIdUsed = false;


...

        /*

         * Assign a subtransaction ID, watching out for counter wraparound.

         */

        currentSubTransactionId += 1;

// 添加如下

        ereport(NOTICE,

                (errmsg("currentSubTransactionId: %d", currentSubTransactionId)));

        if (currentSubTransactionId == InvalidSubTransactionId)

        {

                currentSubTransactionId -= 1;

                pfree(s);

                ereport(ERROR,

                                (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),

                                 errmsg("cannot have more than 2^32-1 subtransactions in a transaction")));

        }


重新编译安装,重启数据库。

psql

设置notice消息级别

postgres=# set client_min_messages='notice';

SET

创建测试函数

postgres=# create or replace function f() returns void as $$

declare

begin

exception           

  when others then

  raise exception 'a';

end;                  

$$ language plpgsql;

测试子事务号申请。

postgres=# select f();

NOTICE:  currentSubTransactionId: 2

CONTEXT:  PL/pgSQL function f() line 3 during statement block entry

 f 

---

 

(1 row)

volatile函数,每条tuple都会触发调用

postgres=# select f() from generate_series(1,10);

NOTICE:  currentSubTransactionId: 2

CONTEXT:  PL/pgSQL function f() line 3 during statement block entry

NOTICE:  currentSubTransactionId: 3

CONTEXT:  PL/pgSQL function f() line 3 during statement block entry

NOTICE:  currentSubTransactionId: 4

CONTEXT:  PL/pgSQL function f() line 3 during statement block entry

NOTICE:  currentSubTransactionId: 5

CONTEXT:  PL/pgSQL function f() line 3 during statement block entry

NOTICE:  currentSubTransactionId: 6

CONTEXT:  PL/pgSQL function f() line 3 during statement block entry

NOTICE:  currentSubTransactionId: 7

CONTEXT:  PL/pgSQL function f() line 3 during statement block entry

NOTICE:  currentSubTransactionId: 8

CONTEXT:  PL/pgSQL function f() line 3 during statement block entry

NOTICE:  currentSubTransactionId: 9

CONTEXT:  PL/pgSQL function f() line 3 during statement block entry

NOTICE:  currentSubTransactionId: 10

CONTEXT:  PL/pgSQL function f() line 3 during statement block entry

NOTICE:  currentSubTransactionId: 11

CONTEXT:  PL/pgSQL function f() line 3 during statement block entry

没有exception的话,不会产生子事务。

postgres=# select * from generate_series(1,10);

 generate_series 

-----------------

               1

               2

               3

               4

               5

               6

               7

               8

               9

              10

(10 rows)

postgres=# create or replace function f1() returns void as $$

postgres$# declare

postgres$# begin

postgres$# end;

postgres$# $$ language plpgsql;

NOTICE:  currentCommandId: 1

CREATE FUNCTION

postgres=# select f1() from generate_series(1,10);

 f1 

----

 

 

 

 

 

 

 

 

 

 

(10 rows)


接下来跟踪一下command id:

DDL,DML会产生command

postgres=# create table t(id int);

NOTICE:  currentCommandId: 1

CREATE TABLE

postgres=# insert into t values (1);

NOTICE:  currentCommandId: 2

INSERT 0 1

postgres=# insert into t values (1);

NOTICE:  currentCommandId: 3

INSERT 0 1

查询不需要分配command id

postgres=# select 1;

 ?column? 

----------

        1

(1 row)

savepoint 产生子事务

postgres=# savepoint a;

NOTICE:  currentSubTransactionId: 12

SAVEPOINT

postgres=# savepoint a;

NOTICE:  currentSubTransactionId: 13

SAVEPOINT

postgres=# savepoint a;

NOTICE:  currentSubTransactionId: 14

SAVEPOINT

postgres=# savepoint a;

NOTICE:  currentSubTransactionId: 15

SAVEPOINT

rollback to savepoint 产生子事务

postgres=# rollback to savepoint a;

NOTICE:  currentSubTransactionId: 16

ROLLBACK

postgres=# rollback to savepoint a;

NOTICE:  currentSubTransactionId: 17

ROLLBACK

postgres=# rollback to savepoint a;

NOTICE:  currentSubTransactionId: 18

ROLLBACK

postgres=# rollback to savepoint a;

NOTICE:  currentSubTransactionId: 19

ROLLBACK

postgres=# rollback to savepoint a;

NOTICE:  currentSubTransactionId: 20

ROLLBACK

postgres=# rollback to savepoint a;

NOTICE:  currentSubTransactionId: 21

ROLLBACK

postgres=# rollback to savepoint a;

NOTICE:  currentSubTransactionId: 22

ROLLBACK

postgres=# rollback to savepoint a;

NOTICE:  currentSubTransactionId: 23

ROLLBACK

postgres=# rollback to savepoint a;

NOTICE:  currentSubTransactionId: 24

ROLLBACK

postgres=# rollback to savepoint a;

NOTICE:  currentSubTransactionId: 25

ROLLBACK

postgres=# rollback to savepoint a;

NOTICE:  currentSubTransactionId: 26

ROLLBACK

postgres=# rollback to savepoint a;

NOTICE:  currentSubTransactionId: 27

ROLLBACK

postgres=# rollback to savepoint a;

NOTICE:  currentSubTransactionId: 28

ROLLBACK

postgres=# rollback to savepoint a;

NOTICE:  currentSubTransactionId: 29

ROLLBACK

postgres=# rollback to savepoint a;

NOTICE:  currentSubTransactionId: 30

ROLLBACK

postgres=# end;

COMMIT


没有exception的函数不产生子事务:

postgres=# create or replace function f() returns void as $$

declare

begin

end;                          

$$ language plpgsql;

NOTICE:  currentCommandId: 1

CREATE FUNCTION

postgres=# select f();

 f 

---

 

(1 row)

每个exception都需要分配一个子事务:

create or replace function f() returns void as $$

declare

begin


begin

exception when others then

return; 

end;   


begin

exception when others then

return; 

end;   


exception when others then

return;

end;

$$ language plpgsql;

postgres=# select f();

NOTICE:  currentSubTransactionId: 2

CONTEXT:  PL/pgSQL function f() line 3 during statement block entry

NOTICE:  currentSubTransactionId: 3

CONTEXT:  PL/pgSQL function f() line 6 during statement block entry

NOTICE:  currentSubTransactionId: 4

CONTEXT:  PL/pgSQL function f() line 11 during statement block entry

 f 

---

 

(1 row)


溢出的例子:

postgres=# select count(*) from (select f(),f(),f(),f(),f(),f(),f(),f(),f(),f(),f(),f(),f(),f(),f(),f(),f(),f(),f(),f(),f(),f(),f() from generate_series(1,500000000))t;

ERROR:  cannot have more than 2^32-1 subtransactions in a transaction

CONTEXT:  PL/pgSQL function f() line 3 during statement block entry


顺带讲一下函数稳定性,以前写过分享。

stable和volatile在一条SQL中,每条tuple都会被触发(实际上stable当传参一样时,不应该被多次触发,这是PG的一个问题)。

immutable则在任何情况下都只调用一次,和stable区别还有,在使用绑定变量时,immutable会自动转换成常量。

postgres=# alter function f() immutable;

ALTER FUNCTION

仅仅触发一次

postgres=# select f() from generate_series(1,100);

NOTICE:  currentSubTransactionId: 2

CONTEXT:  PL/pgSQL function f() line 3 during statement block entry

NOTICE:  currentSubTransactionId: 3

CONTEXT:  PL/pgSQL function f() line 6 during statement block entry

NOTICE:  currentSubTransactionId: 4

CONTEXT:  PL/pgSQL function f() line 11 during statement block entry

 f 

---

改为stable触发多次

postgres=# alter function f() stable;

ALTER FUNCTION

postgres=# select f() from generate_series(1,100);

NOTICE:  currentSubTransactionId: 2

CONTEXT:  PL/pgSQL function f() line 3 during statement block entry

NOTICE:  currentSubTransactionId: 3

CONTEXT:  PL/pgSQL function f() line 6 during statement block entry

NOTICE:  currentSubTransactionId: 4

CONTEXT:  PL/pgSQL function f() line 11 during statement block entry

NOTICE:  currentSubTransactionId: 5

CONTEXT:  PL/pgSQL function f() line 3 during statement block entry

NOTICE:  currentSubTransactionId: 6

,,,,,,




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值