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

本文涉及的产品
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
云原生数据库 PolarDB 分布式版,标准版 2核8GB
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: PostgreSQL 需要为每个savepoint或者函数的exception section分配子事务号,递增。 即使这个exception没有被触发,也需要一个子事务号。 PushTransaction@src/backend/access/transam/xact.c         /

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

,,,,,,




相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
16天前
|
SQL 关系型数据库 MySQL
轻松入门MySQL:保障数据完整性,MySQL事务在进销存管理系统中的应用(12)
轻松入门MySQL:保障数据完整性,MySQL事务在进销存管理系统中的应用(12)
|
1月前
|
关系型数据库 MySQL 数据库
MySQL事务(简单明了)
MySQL事务(简单明了)
|
1月前
|
SQL 关系型数据库 MySQL
MySQL索引与事务
MySQL索引与事务
|
2月前
|
存储 关系型数据库 MySQL
Mysql高可用|索引|事务 | 调优
Mysql高可用|索引|事务 | 调优
|
1月前
|
关系型数据库 MySQL 数据库
深入探讨MySQL并发事务的问题及解决方案
深入探讨MySQL并发事务的问题及解决方案
66 0
|
2月前
|
NoSQL 关系型数据库 MySQL
MySQL - 深入理解 MySQL 的事务和隔离级别
MySQL - 深入理解 MySQL 的事务和隔离级别
167 0
|
1月前
|
SQL 关系型数据库 MySQL
【MySQL 数据库】4、MySQL 事务学习
【MySQL 数据库】4、MySQL 事务学习
44 0
|
2月前
|
SQL 关系型数据库 MySQL
Mysql事务隔离级别和锁特性
Mysql事务隔离级别和锁特性
|
9天前
|
存储 SQL 关系型数据库
【MySQL实战笔记】03.事务隔离:为什么你改了我还看不见?-02
【4月更文挑战第7天】数据库通过视图实现事务隔离,不同隔离级别如读未提交、读已提交、可重复读和串行化采用不同策略。以可重复读为例,MySQL使用多版本并发控制(MVCC),每个事务有其独立的视图。回滚日志在无更早视图时被删除。长事务可能导致大量存储占用,应避免。事务启动可显式用`begin`或设置`autocommit=0`,但后者可能意外开启长事务。建议使用`autocommit=1`并显式管理事务,若需减少交互,可使用`commit work and chain`。
28 5
|
21天前
|
关系型数据库 MySQL 测试技术
面试-MySQL的四种事务隔离级别
面试-MySQL的四种事务隔离级别
16 0

相关产品

  • 云原生数据库 PolarDB