Quantcast
Channel: PostgreSQL research
Viewing all articles
Browse latest Browse all 253

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

$
0
0
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
,,,,,,

Flag Counter

Viewing all articles
Browse latest Browse all 253

Trending Articles