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