.NET+PostgreSQL实践与避坑指南(推荐)(3)

这又是一个有点棘手的事情,首先是这个中文翻译得很不好,这是一条数据库抛出来的出错信息,它的英文是“Prepared transactions are disabled”,其正确的中文翻译我觉得应该是:预处理事务已被禁用。唉,所以我说为什么要英文版,如果提示中文,想在网上找答案都会多些障碍。

对事务的使用,这里有个简单的例子:

using (NpgsqlConnection conn = new NpgsqlConnection(connectionStr)) { conn.Open(); using (TransactionScope ts = new TransactionScope()) { conn.EnlistTransaction(Transaction.Current); //SQLs... } ts.Complete(); } }

什么叫“预处理事务”?其实很简单,就是“事务包事务”,就是可以分步提交的事务,比如我先开启了一个事务A,在这个事务中我又开启了一个事务B,B提交,A再提交。PG对于预处理事务是默认关闭的,当然了,你可以打开它,编辑配置文件postgresql.conf,把max_prepared_transactions改为100(默认是0,0表示禁用),重启PG服务即可。

但你确定你真的用得到预处理事务吗?我看下来我们是用不到的,但为什么出现这个问题?——还是我们程序写得有问题,即便你从单个方法上看不出来事务包事务。以下两种场景可能会出现“预处理事务”:

1,我创建了一个方法A访问数据库,这个方法可能会被其它方法调用,所以它有个DbConnection类型的参数,表示调用者负责打开数据库连接传递过来,而A里面开启了事务,而调用者并不知情,也开启了事务,形成预处理事务

2,这种情况更隐晦些,数据库连接字符串,如:Host=192.168.1.101; Username=postgres; Password=123456; Database=testdb; Enlist=true,在后面有个叫Enlist的参数为true,这表示这个连接在打开的时候,会自动Enlist到当前执行上下文的Transaction中去,如果当前执行上下文中打开了事务(从代码上看包含在了using(TransactionScope)中),那这个数据库连接就自动Enlist上去了,再考虑这样的场景:A方法会自己打开数据库连接去查询点什么东西,B方法也会访问数据库,且B方法会使用事务,事务中调用了A方法,A方法打开数据库连接的时候发现当前执行上下文中存在Transaction,于是自动Enlist上去了,不经意间形成了预处理事务,且还是“分布式”的(A和B打开的可能是不同的数据库连接),这种情况应该并不是你所需要的

那我们应该怎么做?下面是我的做法:

1,max_prepared_transactions还是设置为0,关掉,因为我们真用不到,如果用得到,那就是我们代码写错了,所以一旦出现“禁用已准备好的事务”这个异常,就回去检查代码

2,把Enlist=true在数据库连接字符串中去掉,这么一来,每次使用事务都需要显式地调用 conn.EnlistTransaction(Transaction.Current),虽然对了一行代码,但语义更明确,也不用考虑到底是TransactionScope包DbConnection或反过来DbConnection包TransactionScope

3,规范化我们的数据库访问代码,明确哪些是需要事务哪些是不需要的,在各个方法的注释上注明

40001:由于多个事务间的读/写依赖而无法串行访问

它对应的英文是:Cound not serialize access due to read/write dependencies among transactions,这个应该怎么理解呢?其实了解数据库事务隔离级别的人对这个应该不会陌生。.NET的TransactionScope默认使用的是事务隔离级别中的最高级别——Serializable(可序列化)。这个级别最大程度上确保了数据的一致性,但代价也挺高,一来速度较慢,二来很容易出现“事务间读/写依赖”,就是这个错误了,举个简单的例子:

A、B两个事务,同时访问test表中id为50的一条记录,A读出这条记录,接着B更新了这条记录并提交,根据可序列化的隔离级别的规则,A并不知道B更新了记录,A在B提交后尝试修改这条记录,这时候数据库就会让A事务失败,并抛出这个异常,因为让A修改成功的话,就会导致B之前的修改不经意间丢失了,可序列化隔离级别并不允许这种情况的发生。

所以,这是个“正常的错误”,按常规的业务逻辑来说,应该很少会出现,如果真的出现,且频繁出现,那需要考虑下是不是业务逻辑设计得不太合理,看看能不能从设计上避免这个问题,如果业务逻辑一定如此,那可以用下面的方法尝试一下:

1,将这种并行事务用客户端代码排个队,弄个线程安全队列,逐个执行,这样速度会慢点,但确保了每个事务都能成功

2,捕捉这个异常,然后自动重试,其实这也是数据库推荐的正统的做法

3,降低事务隔离级别,这个可能会出现问题,也可能不出现,这完全取决于你的业务,关于事务隔离级别,这是个蛮大的话题,我考虑适当时候再写一篇文章

4,对于极少出现的频次来说,可以不处理,仅仅需要捕捉这个异常类型,然后提示用户重试即可,很多网站貌似都这么干的

总结

有时间的话我会另外开一篇文章来写写PG的一些常规用法,如热备冷备还原维护等,但不太能保证什么时候能写出来。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/1caecd5b89913419567f8b8ffc3439ae.html