关于数据库事务的嵌套

SQLServer事务嵌套处理

 

一、基础概念

SQL Server 数据库引擎将忽略内部事务的提交。根据最外部事务结束时采取的操作,将提交或者回滚内部事务。如果提交外部事务,也将提交内部嵌套事务。如果回滚外部事务,也将回滚所有内部事务,不管是否单独提交过内部事务。

嵌套事务的程序执行过程应该是:随着事务的执行,嵌套层数由一递增,之后逐渐递减到第一层。整个事务是否提交取决于最后一层是运行Commit(提交)还是Rollback(回滚)。

1、对 COMMIT TRANSACTION的每个调用都应用于最后执行的 BEGIN TRANSACTION。如果嵌套 BEGIN TRANSACTION 语句,那么 COMMIT 语句只应用于最后一个嵌套的事务,也就是在最内部的事务。即使嵌套事务内部的 COMMIT TRANSACTION transaction_name 语句引用外部事务的事务名称,该提交也只应用于最内部的事务。

2ROLLBACK TRANSACTION 语句的 transaction_name 参数引用一组命名嵌套事务的内部事务是非法的,transaction_name 只能引用最外部事务的事务名称。如果在一组嵌套事务的任意级别执行使用外部事务名称的 ROLLBACK TRANSACTION transaction_name 语句,那么所有嵌套事务都将回滚。如果在一组嵌套事务的任意级别执行没有 transaction_name 参数的ROLLBACK TRANSACTION 语句,那么所有嵌套事务都将回滚,包括最外部事务。(此段意思表示:只允许嵌套层第一层时使用ROLLBACK回滚)

3@@TRANCOUNT 函数记录当前事务的嵌套级别。每个 BEGIN TRANSACTION 语句使 @@TRANCOUNT 增加 1。每个 COMMIT TRANSACTION语句使 @@TRANCOUNT 减去 1

A、没有事务名称的ROLLBACK TRANSACTION 语句将回滚所有嵌套事务,并使 @@TRANCOUNT 减小到 0

B、使用一组嵌套事务中最外部事务的事务名称的 ROLLBACK TRANSACTION 将回滚所有嵌套事务,并使 @@TRANCOUNT 减小到 0

C、在无法确定是否已经在事务中时,可以用 SELECT @@TRANCOUNT 确定 @@TRANCOUNT 是等于 1 还是大于 1。如果 @@TRANCOUNT 等于 0,则表明不在事务中。

注:以上内容摘自-- http://msdn.microsoft.com/zh-cn/library/ms189336.aspx

 

二、示例说明:

1、创建一个表格,用于插入数据的实验

create table TranTestTable(

T datetime

)

2、没有使用事务,程序运行会出错。但第一条语句还是插入了一条记录,第二条没有插入记录。

alter PROC TranTest2(@errorTag int output)

AS

BEGIN

       insert into TranTestTable values(getdate())

       insert into TranTestTable values('....')

END

3、见下面代码,存在两层事务的嵌套使用,TranTest1嵌套调用TranTest2,根据“一”中的1、2可以得出以下代码。调用TranTest1运行正常,没有插入任何记录。

alter PROC TranTest1(@errorTag int output)

AS

BEGIN

    set @errorTag = 0

    Begin transaction

    declare @errorTag2  int

    exec TranTest2 @errorTag2 output ––调用TranTest2存储过程

    if @errorTag2<0

    BEGIN

       set @errorTag = -1

       ROLLBACK TRANSACTION

       RETURN

    END

    IF @@TRANCOUNT > 0

       COMMIT TRANSACTION

END

----------- TranTest2代码------------

alter PROC TranTest2(@errorTag int output)

AS

BEGIN

    set @errorTag = 0

    Begin transaction

       insert into TranTestTable values(getdate())

       insert into TranTestTable values('....')

    if @@error <> 0

    BEGIN

       set @errorTag = -1

       COMMIT TRANSACTION

       RETURN

    END

    IF @@TRANCOUNT > 0

       COMMIT TRANSACTION

END

4、但TranTest2是一个独立的存储过程,也可能被直接嗲用(外层不嵌套事务),TranTest2被TranTest1调用和直接被调用的区别在于事务嵌套级别不同。直接被调用(不存在嵌套调用)时,此时TranTest2中就是事务的第一层,碰到异常情况应该运行ROLLBACK TRANSACTION使得整个事务回滚;被TranTest1调用(存在嵌套调用)时,TranTest2的层级(@@TRANCOUNT的值)为2,此时出现异常情况应该运行COMMIT TRANSACTION,将嵌套层数减1,并将事务交给上一层处理,最后这个事务是否提交取决于第一层最后是提交还是回滚;

    为了使得TranTest2存储过程具有通用性,可以根据嵌套层数决定是调用COMMIT还是ROLLBACK。如果是大于1,说明上面还有事务包着,此时事务应该交给上一层处理。如果是1,则需要回滚整个事务,因为没有其他地方可以将事务回滚了。

alter PROC TranTest2(@errorTag int output)

AS

BEGIN

    set @errorTag = 0

    Begin transaction

       insert into TranTestTable values(getdate())

       insert into TranTestTable values('....')

    if @@error <> 0

    BEGIN

       set @errorTag = -1

       IF @@TRANCOUNT > 1

           COMMIT TRANSACTION

       ELSE

           ROLLBACK TRANSACTION

       RETURN

    END

    IF @@TRANCOUNT > 0

       COMMIT TRANSACTION

END

5、最后将TranTest1像TranTest2一样改成具有通用性,因为TranTest1也不知道会被谁调用。

alter PROC TranTest1(@errorTag int output)

AS

BEGIN

    set @errorTag = 0

    Begin transaction

    declare @errorTag2  int

    exec TranTest2 @errorTag2 output

    if @errorTag2<0

    BEGIN

       set @errorTag = -1

       IF @@TRANCOUNT > 1

           COMMIT TRANSACTION

       ELSE

           ROLLBACK TRANSACTION

       RETURN

    END

    IF @@TRANCOUNT > 0

       COMMIT TRANSACTION

END

 

三、总结整理

 

在存储过程中启动事务:

遵守一个原则,启动一个事务时@@TRANCOUNT的值和结束该层事务时的@@TRANCOUNT的值是相等的。在上一层的事务中是提交还是回滚,是根据内层返回的回滚提交标志决定的。

1、在事务的正常完成结束点。事务正常结束时@@TRANCOUNT减去1

       IF @@TRANCOUNT > 0

              COMMIT TRANSACTION

A、如果外层没有嵌套事务,则@@TRANCOUNT=1正常提交;

B、如果外层嵌套了事务,则@@TRANCOUNT>1,运行COMMIT使得@@TRANCOUNT-1,并且返回正确系统运行正常信息,事务的真正提交交给外层事务。

2、在事务中途要退出事务,并且返回错误标志(回滚标志)

       IF @@TRANCOUNT > 1

              COMMIT TRANSACTION

       ELSE

              ROLLBACK TRANSACTION

       RETURN

A、如果外层嵌套了事务,则满足IF @@TRANCOUNT > 1条件,运行COMMIT使得@@TRANCOUNT-1,并且返回错误信息,真正的回滚交给最上层事务(由上层根据这个错误信息决定回滚)。

B、如果外层没有嵌套事务,则@@TRANCOUNT=1,满足else,直接回滚;

### Android Room 数据库事务嵌套的使用方法 在 Android Room 中,事务是一组作为单个逻辑单元执行数据库操作。事务嵌套指的是在一个事务中包含另一个事务。可以使用 `@Transaction` 注解来标记一个方法,使其在事务执行。 以下是一个简单的示例,展示了如何进行事务嵌套: ```kotlin import androidx.room.Database import androidx.room.Entity import androidx.room.PrimaryKey import androidx.room.Dao import androidx.room.Insert import androidx.room.Transaction import androidx.room.RoomDatabase import android.content.Context import androidx.room.Room // 定义实体类 @Entity data class User( @PrimaryKey val id: Int, val name: String ) // 定义 DAO 接口 @Dao interface UserDao { @Insert fun insertUser(user: User) @Transaction fun outerTransaction() { // 外部事务 insertUser(User(1, "John")) innerTransaction() } @Transaction fun innerTransaction() { // 内部事务 insertUser(User(2, "Jane")) } } // 定义数据库类 @Database(entities = [User::class], version = 1) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao } // 使用示例 fun main() { val context: Context = TODO() // 需要替换为实际的 Context val database = Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, "app_database" ).build() val userDao = database.userDao() userDao.outerTransaction() } ``` 在上述示例中,`outerTransaction` 方法标记为事务,在该方法中调用了 `innerTransaction` 方法,而 `innerTransaction` 方法也标记为事务,从而实现了事务嵌套。 ### 注意事项 - **原子性**:Room 中的事务嵌套遵循原子性原则。如果内部事务或外部事务中的任何一个操作失败,整个嵌套事务都会回滚,确保数据的一致性。 - **性能影响**:事务嵌套可能会对性能产生一定的影响,因为每个事务都需要额外的开销来管理事务的开始、提交或回滚。因此,应尽量避免不必要的事务嵌套。 - **异常处理**:在嵌套事务中,需要正确处理异常。如果内部事务抛出异常,外部事务可以选择捕获异常并进行相应的处理,或者让异常继续向上抛出,导致整个嵌套事务回滚。 - **线程安全**:确保在正确的线程上执行事务。Room 默认不允许在主线程上执行数据库操作,因此需要在后台线程上执行事务
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值