SQLServer:内部事务与外部事务的统一

 在我们日常数据库开发过程中,涉及到数据增删改,无一例外都需要使用事务来控制其一致性。

而对于业务逻辑比较复杂的ERP/MES/WMS等系统来说,一致性尤其重要。

此时我们考虑一个问题,存储过程A开启了事务,此时B由于业务需要调用存储过程A,但是B又有自己的业务,也需要开启事务。此时我们就需要考虑事务的一致。

而受SQLServer的事务机制影响,在第一次commit之后,事务计数就会重新,等到我们commit第二个事务的时候就会报错:EXECUTE 后的事务计数指示BEGIN COMMIT 语句的数目不匹配。上一计数 = 1,当前计数 = 0。

除了嵌套这一种比较麻烦的方式之外,还有另外一种更简单的方式,下面我们模拟一下

ProcA:

begin try

begin tran tran_ProcA

  --ProcA的业务

  exec ProcB

commit tran tran_ProcB

end try

begin catch

  rallback tran tran_ProcA

end catch

 

ProcB:

declare @tranCount int

set @tranCount = @@TRANCOUNT

begin try

if @tranCount = 0

  begin tran tran_ProcB;

  --ProcA的业务

  if @tranCount = 0

    commit tran tran_ProcB;

end try

begin catch

  if @tranCount = 0 

    rollback tran tran_ProcB;

end catch

 

为了区分A和B,所以单独对ProcB增加了对事务的判断,原理很简单,就是我再执行ProcB的时候,判断事务有没有开启,当单独调用ProcB的时候,@@TRANCOUNT = 0,所以就会开启事务。

但是如果在ProcA中调用ProcB的时候,事务已经开启的,所以ProcB就不会再开启事务了,如果有另外的存储过程再调用ProcA,那么也需要在ProcA中增加对@@TRANCOUNT的判断,主要要先赋值,且再进入事务之前就建立变更接收@@TRANCOUNT的值,因为后面@@TRANCOUNT会随着事务的进行发生变化。

 

当然,这种方法也是有弊端的,因为对于复杂的业务来说,我们在对数据提交前会做很多的校验,例如我装箱之前要判断是否有库存,是否已经被装箱,箱号是否重复等等信息,再加上拣货/反拣等等的状态控制。其实我们会对很多表去关联查询来检验这个数据是否合法,显然 这些都不适合放在事务内去操作,放在事务外我们使用with(nolock)关联查询,可以极大避免事务提交慢而锁住一堆业务表,最终导致业务瘫痪。所以我们也要遵循一个原则,事务内只处理增删改,无关的表最好连查询都不要放进去。

现在写了不少MES业务的存储过程之后再回想这些,其实我们都没必要弄这么复杂。存储过程里调存储过程本身就不是一个好的选择,还是要推荐根据具体业务分析出合理的优化方式

posted @ 2022-01-08 20:09  千帆皆是梦  阅读(378)  评论(0编辑  收藏  举报