请勿滥用 2PC, 忘记提交prepared transaction对PostgreSQL造成的危害.

我在数据库中开启了一个2PC事务,但是不去管他,会发生什么呢?有什么危害?

Postgres2015全国用户大会将于11月20至21日在北京丽亭华苑酒店召开。本次大会嘉宾阵容强大,国内顶级PostgreSQL数据库专家将悉数到场,并特邀欧洲、俄罗斯、日本、美国等国家和地区的数据库方面专家助阵:

  • Postgres-XC项目的发起人铃木市一(SUZUKI Koichi)
  • Postgres-XL的项目发起人Mason Sharp
  • pgpool的作者石井达夫(Tatsuo Ishii)
  • PG-Strom的作者海外浩平(Kaigai Kohei)
  • Greenplum研发总监姚延栋
  • 周正中(德哥), PostgreSQL中国用户会创始人之一
  • 汪洋,平安科技数据库技术部经理
  • ……
 
  • 2015年度PG大象会报名地址:http://postgres2015.eventdove.com/
  • PostgreSQL中国社区: http://postgres.cn/
  • PostgreSQL专业1群: 3336901(已满)
  • PostgreSQL专业2群: 100910388
  • PostgreSQL专业3群: 150657323
postgres=# begin;
BEGIN
postgres=# insert into t6 values (1);
INSERT 25622 1
postgres=# prepare transaction 'a';
PREPARE TRANSACTION
postgres=# select * from txid_current_snapshot();
     txid_current_snapshot     
-------------------------------
 639903995:639904018:639903995
(1 row)
postgres=# select * from pg_prepared_xacts ;
 transaction | gid |           prepared            |  owner   | database 
-------------+-----+-------------------------------+----------+----------
   639903995 | a   | 2015-09-24 10:03:53.900569+08 | postgres | postgres
(1 row)

危害1,  膨胀
因为vacuum 在回收垃圾时,判断dead tuple可以回收的前提是,dead tuple是在最早未提交事务之前产生的。
所以,在这个事务之后,产生的DEAD TUPLE都无法被回收,即使VACUUM FULL也无法回收。
源码分析请参考

危害2, 年龄
年龄同样会受到威胁,最多只能降低到最早未提交的事务。
例如:

postgres=# select age(datfrozenxid),datfrozenxid,datname from pg_database where datname=current_database();
 age | datfrozenxid | datname  
-----+--------------+----------
  36 |    639903995 | postgres
(1 row)
无法降低年龄了
postgres=# vacuum freeze;
VACUUM
postgres=# select age(datfrozenxid),datfrozenxid,datname from pg_database where datname=current_database();
 age | datfrozenxid | datname  
-----+--------------+----------
  36 |    639903995 | postgres
(1 row)
随着事务流逝,年龄越来越大
postgres=# insert into t6 values (1);
INSERT 25655 1
postgres=# insert into t6 values (1);
INSERT 25656 1
postgres=# insert into t6 values (1);
INSERT 25657 1
postgres=# insert into t6 values (1);
INSERT 25658 1
postgres=# insert into t6 values (1);
INSERT 25659 1
postgres=# select age(datfrozenxid),datfrozenxid,datname from pg_database where datname=current_database();
 age | datfrozenxid | datname  
-----+--------------+----------
  41 |    639903995 | postgres
(1 row)
postgres=# vacuum freeze;
VACUUM
postgres=# select age(datfrozenxid),datfrozenxid,datname from pg_database where datname=current_database();
 age | datfrozenxid | datname  
-----+--------------+----------
  41 |    639903995 | postgres
(1 row)


危害3, 持锁,DDL当然也是下不去的。
包括vacuum full, alter table, ....

最后,还需要提醒, 基于流复制的备库,2PC事务会复制过去,激活后就可以看到。
所以2PC是非常坚强的,停库后起来还在,切换到备库也还在。
监控必须建立起来,对于长时间不提交的prepared transaction,及时告警。

[参考]
error error.kind: org.postgresql.util.PS0LException message: An I/0 error occurred while sending to the backend. stack: org.postgresql.util.PsQLException: An I/0 error occurred while sending to the backend at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:335) at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:441) at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:365) at org.postgresql.jdbc.PgPreparedstatement.executeWithFlags$original$d7UrnXE](PgPrepared at org.postgresql.jdbc.PgPreparedStatement.executeWithflags$original$d7UrnXE]$accessor$8 at org.postgresql.jdbc.PgPreparedStatement$auxiliary$sRWuPpuQ.call(Unknown Source) at org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstMethodsInter.inte at org.postgresql.jdbc.PgPreparedstatement.executeWithFlags(PgPreparedstatement.java) at org.postgresql.jdbc.PgPreparedStatement.execute(PgPreparedStatement.java:132) at com.alibaba,druid.filter,FilterChainImpl.preparedStatement execute(filterChainImpl.ia at com.alibaba.druid.filter,FilterEventAdapter.preparedStatement execute(filterEventAda! at com.alibaba.druid.filter.FilterChainImpl.preparedStatement execute(FilterChainImpl.ja at com.alibaba.druid.wall.WallFilter.preparedStatement execute(WallFilter.java:639) at com,alibaba,druid,filter,filterChainImpl,preparedStatement execute(FilterChainImpl.ja at com.alibaba.druid.filter,filterEventAdapter,preparedStatement execute(FilterEventAdar at com.alibaba.druid.filter.filterChainImpl.preparedstatement execute(FilterChainImpl.ja at com.alibaba.druid.proxy.jdbc.PreparedStatementProxyImpl,execute(PreparedStatementProx at com.alibaba.druid.pool.DruidPooledPreparedStatement.execute(DruidPooledPreparedStatem at sun.reflect.GeneratedMethodAccessor181.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl,invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.apache.ibatis.logging.jdbc.PreparedstatementLogger
最新发布
03-08
### 解决方案 当遇到 `org.postgresql.util.PSQLException: An I/O error occurred while sending to the backend` 错误时,这通常意味着客户端尝试发送的数据超出了协议允许的最大值或存在其他网络传输问题。具体到当前情况,错误信息表明试图发送超出范围的整数作为2字节值[^1]。 #### 数据量过大引发的问题 对于大批量数据插入操作(如3000+条记录),一次性提交可能导致请求体过大使JDBC驱动程序无法处理。PostgreSQL JDBC驱动默认情况下会将整个批量插入作为一个事务来处理,如果单次插入的数据量太大,则可能触发上述异常[^2]。 #### 调整批量插入策略 为了有效解决问题并提高性能,建议采用分批插入的方式: ```java int batchSize = 100; // 设置每批次大小 try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) { try { for (YourDataObject item : largeDataSet) { mapper.insert(item); if (++count % batchSize == 0) { // 当达到设定的batchSize时执行flush session.flushStatements(); session.clearCache(); // 清除缓存以释放内存资源 } } session.commit(); // 提交剩余未完成的操作 } catch (Exception e) { session.rollback(); throw e; } } ``` 通过这种方式可以减少每次向服务器发送的数据总量,从而降低发生I/O错误的风险。 #### 参数调整优化 另外还可以考虑适当增加一些配置项来适应更大的工作负载需求: - **max_prepared_transactions**: 如果应用程序频繁使用预编译语句,可适当增大此参数。 - **work_mem**: 增加用于内部排序和哈希表的工作内存量有助于提升查询效率。 - **statement_timeout**: 对长时间运行的任务设置合理的超时时长防止其占用过多资源。 这些改动应在充分测试之后谨慎实施,并确保不会影响系统的稳定性和安全性[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值