理解postgreSQL中的prepared transactions和处理孤儿(orphans)事务
2020-12-09 14:11 abce 阅读(2965) 评论(0) 编辑 收藏 举报
关于事务
在数据库系统中,事务是一种处理通常包含多个语句的块中的全部或零个语句的方法。在提交整个块之前,该块中语句的结果对其他事务不可见。 如果事务失败或回滚,则对数据库完全没有影响。
事务依附于会话。但是,当要执行与会话独立的事务时(也有其他好处)。这就是“prepared transactions”的来源。
prepared transactions
prepared transaction是独立于会话、抗崩溃、状态维护的事务。事务的状态存储在磁盘上,这使得数据库服务器即使在从崩溃中重新启动后也可以恢复事务。在对prepared transaction执行回滚或提交操作之前,将一直维护该事务。
PostgreSQL文档声明,在一个已存在的事务块中,可以使用prepare transaction ’transaction_id‘命令创建一个prepared transaction。它进一步声明该过程为两阶段提交准备了一个事务。
此外,建议应用程序或交互式会话不要使用prepared transaction。理想情况下,外部事务管理器应该跨同构或异构数据库资源执行原子的全局事务。
在postgreSQL中,缺省的max_prepared_transaction=0;即关闭了prepared transaction。如果你想使用prepared transaction,建议将max_prepared_transaction设置成max_connections的值。在同步的流复制standby库上,最好将其设置的比max_connections大一点,以免standby不能接收查询。
1 2 3 4 | # select * from pg_prepared_xacts; transaction | gid | prepared | owner | database -------------+-----+----------+-------+---------- (0 rows ) |
1.transaction:事务id
3.prepared:prepared日期,创建事务时带有时区的时间戳
4.owner:创建该prepared transaction的事务
5.database:数据库名
创建prepared transaction
知道什么是prepared transaction之后,现在来看看如何创建一个prepared transaction。创建一个该事务通常需要四个步骤:
1.begin(或start transaction)
2.执行需要的操作
3.prepare transaction
4.commit(或rollback prepared)
prepare transaction、commit prepared、或rollback prepared后面加上一个gid,可以唯一标识prepared transaction。
例如下面的代码块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | postgres=# begin ; BEGIN postgres=# create table abce(id int ); CREATE TABLE postgres=# insert into abce values (1); INSERT 0 1 postgres=# prepare transaction 'abce_insert' ; PREPARE TRANSACTION postgres=# select * from pg_prepared_xacts; transaction | gid | prepared | owner | database -------------+-------------+-------------------------------+----------+---------- 16362 | abce_insert | 2020-12-09 11:41:45.742375+08 | postgres | postgres (1 row) postgres=# commit prepared 'abce_insert' ; COMMIT PREPARED postgres=# select * from pg_prepared_xacts; transaction | gid | prepared | owner | database -------------+-----+----------+-------+---------- (0 rows ) postgres=# |
当一个含有一个或多个活跃的prepared transactions的postgresql停止了或者奔溃了,会为每个活跃的prepared transaction创建一个文件,在目录pg_twophase中。
比如,我们有个prepared transaction:
1 2 3 4 5 6 7 | postgres=# select * from pg_prepared_xacts; transaction | gid | prepared | owner | database -------------+--------------+-------------------------------+----------+---------- 16363 | abce_insert2 | 2020-12-09 11:46:01.983483+08 | postgres | postgres (1 row) postgres=# |
所以我没有提交事务就停止了postgresql server。postgresql就会创建一个名为00003FEB的文件,对应于prepared transaction的事务id。
1 2 3 | $ ls -l ../data/pg_twophase/ total 4 -rw ------- 1 postgres postgres 220 Dec 9 11:47 00003FEB |
00003FEB等价于16363。在postgresql被重启后,在启动日志会报如下信息:
1 2 3 | 2020-12-09 11:51:28.112 CST [963] LOG: database system was shut down at 2020-12-09 11:47:39 CST 2020-12-09 11:51:28.113 CST [963] LOG: recovering prepared transaction 16363 from shared memory 2020-12-09 11:51:28.132 CST [960] LOG: database system is ready to accept connections |
如果你不希望恢复一个prepared transaction,可以简单地删除pg_twophase文件夹下的相应文件。
prepared transaction可能遇到哪些错误?
如果客户端消失了,则prepared transaction可以未完成(既不提交也不回滚)。发生这种情况的原因多种多样,包括客户机崩溃,或者服务器崩溃导致客户机连接被终止而无法重新连接。你实际上是依靠事务管理器来确保没有孤立的prepared transaction。
除了崩溃之外,还有另一种原因可以使prepared transaction未完成。如果一个用于恢复的备份包含了事务的prepared阶段,但是没有包含关闭事务的阶段,仍然会生成孤儿事务。
或者,DBA创建了一个prepared transaction,却忘记了关闭它。
所以,如果一个prepared transaction没有完成,又会有什么大不了的呢?
真正的问题
真正的问题是,孤儿prepared transaction继续持有可能包含锁的关键系统资源,或者使事务ID保持活动状态,该事务ID可能会阻止vacuum清除只对该孤儿事务可见、对其它事务不可见的死的元组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | postgres=# select * from pg_prepared_xacts; transaction | gid | prepared | owner | database -------------+--------------+-------------------------------+----------+---------- 16363 | abce_insert2 | 2020-12-09 11:46:01.983483+08 | postgres | postgres (1 row) postgres=# alter table abce add column b int ; ^CCancel request sent ERROR: canceling statement due to user request postgres=# select c.oid,c.relname,l.locktype,l.relation,l.mode postgres-# from pg_class c postgres-# inner join pg_locks l on c.oid=l.relation postgres-# where c.relname= 'abce' ; oid | relname | locktype | relation | mode --------+---------+----------+----------+------------------ 370883 | abce | relation | 370883 | RowExclusiveLock (1 row) postgres=# |
对vacuum的阻塞可能会更严重,在极端情况下,会导致数据库关闭,因为孤儿prepared事务会阻止事务id的wrap around。
虽然一般的预期是prepared事务在几秒钟内完成,但是情况并不总是这样。一个prepared事务可能持续几分钟、几小时甚至几天。
1 2 3 4 5 6 | postgres=# BEGIN ; BEGIN postgres=# INSERT INTO abce VALUES (3); INSERT 0 1 postgres=# PREPARE TRANSACTION 'abce_insert 1m' ; PREPARE TRANSACTION |
或者下面的事务:
1 2 3 4 5 6 | postgres=# BEGIN ; BEGIN postgres=# INSERT INTO abce VALUES (4); INSERT 0 1 postgres=# PREPARE TRANSACTION 'abce_insert 1d' ; PREPARE TRANSACTION |
在这些事务名称中,最后一部分定义事务的时间。任何超出时间的事务可以通过sql查询轻易地找出来:
1 2 3 4 5 6 7 8 9 | postgres=# select gid,prepared,regexp_replace(gid, '.* ' , '' ) AS age from pg_prepared_xacts WHERE prepared + CAST (regexp_replace(gid, '.* ' , '' ) AS INTERVAL) < NOW(); gid | prepared | age ----------------+-------------------------------+----- abce_insert 1m | 2020-12-09 13:39:01.383091+08 | 1m (1 row) postgres=# |
这里就很清晰地显示了一个不应该再有效的事务。因此,使用一个外部代理或者cron任务可以轻易找出这些事务,或者通知管理员、或者回滚事务。
在我看来,这是一种简单而容易的方式,可以确保即使事务管理器失败或DBA意外地留下了一个事务,也可以在你的环境中管理孤儿事务。
结论
Prepared transactions显然是一个非常重要的功能,但是需要使用回退通知程序或清理程序仔细设置环境,以轻松确保这些事务不会不必要地占用关键资源,并且系统保持良好状态。
PostgreSQL社区中仍在讨论如何处理孤儿prepared事务。它是否成为postgresql核心的一部分尚待观察。同时,我们需要使用外部工具来管理这些事务,或者设法解决这个问题。
原文地址:https://www.highgo.ca/2020/01/28/understanding-prepared-transactions-and-handling-the-orphans/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
2016-12-09 Fatal Error: TXK Install Service oracle.apps.fnd.txk.config.ProcessStateException: OUI process failed : Exit=255 See log for details
2016-12-09 here was insufficient free space available after evicting expired cache entries - consider increasing the maximum size of the cache
2015-12-09 ASM磁盘组兼容性设置
2015-12-09 mongodb-创建索引
2015-12-09 MongoDB - Cursors
2015-12-09 asmdisk opened & asmdisk cached