T-SQL 事务(Transaction)

T-SQL 事务(Transaction)

核心考点:事务的概念与特性、事务的开启/提交/回滚、事务解决的问题(数据一致性)、@@ERROR全局变量的使用、check约束的作用

适配表结构:CardNew(StdudentId, StudentName, CurrentMoney)(StdudentId自增主键,CurrentMoney有余额大于1的约束)

一、案例背景:转账场景的问题(无事务)

转账操作涉及"转出"和"转入"两个更新操作,若不使用事务,其中一个操作失败、另一个操作成功,会导致数据不一致(如钱转出失败但转入成功,出现"无中生有")。

1.1 创建表并插入测试数据

复制代码
-- 创建CardNew表(银行卡表)
create table CardNew 
(
  StdudentId int identity(1000,1) primary key not null, -- 自增主键,起始值1000,步长1
  StudentName varchar(10) not null,                     -- 学生姓名,非空约束
  CurrentMoney money check(CurrentMoney>1)              -- 余额,check约束:余额必须大于1,否则插入/更新报错
);

-- 插入4条测试数据(初始余额分别为1400、1500、1600、1700)
insert into CardNew(StudentName,CurrentMoney) values('赵云',1400);
insert into CardNew(StudentName,CurrentMoney) values('马超',1500);
insert into CardNew(StudentName,CurrentMoney) values('张飞',1600);
insert into CardNew(StudentName,CurrentMoney) values('关羽',1700);

-- 查询表中数据,验证插入结果
select * from CardNew;

关键说明:check(CurrentMoney>1) 约束确保余额始终大于1,若更新后余额≤1,会触发约束报错,操作失败。

1.2 无事务的转账操作(存在数据一致性问题)

复制代码
-- 需求:从赵云(StdudentId=1000)转账1000元给马超(StdudentId=1001)
-- 步骤1:赵云账户扣除1000元(1400-1000=400,符合check约束,操作成功)
update CardNew set CurrentMoney = CurrentMoney - 1000 where StdudentId=1000;

-- 步骤2:马超账户增加1000元(1500+1000=2500,操作成功)
update CardNew set CurrentMoney = CurrentMoney + 1000 where StdudentId=1001;

-- 查询转账后数据(此时数据正常,无问题)
select * from CardNew;

-- 模拟异常场景:从马超(StdudentId=1001)转账1500元给不存在的账户(StdudentId=1004)
-- 步骤1:马超账户扣除1500元(2500-1500=1000,符合约束,操作成功)
update CardNew set CurrentMoney = CurrentMoney - 1500 where StdudentId=1001;

-- 步骤2:向不存在的账户转入1500元(无匹配记录,操作成功但无实际效果)
update CardNew set CurrentMoney = CurrentMoney + 1500 where StdudentId=1004;

-- 查询结果:马超账户少了1500元,但1004账户未增加,数据不一致
select * from CardNew;

问题分析:无事务时,两个更新操作相互独立,其中一个操作异常(如转入不存在的账户、转出后余额违反约束),另一个操作仍会执行,导致数据不一致,违背业务逻辑。

二、事务的核心概念与特性(必背)

事务(Transaction)是一个不可分割的工作单元,包含一系列SQL操作,这些操作要么全部执行成功并提交,要么全部失败并回滚,确保数据的一致性和完整性。

2.1 事务的四大特性(ACID)

  • 原子性(Atomicity):事务中的所有操作要么全部执行,要么全部不执行,不存在部分执行的情况(核心特性,适配转账场景)。

  • 一致性(Consistency):事务执行前后,数据库数据的完整性约束(如check、主键、外键)保持一致(如转账后总金额不变)。

  • 隔离性(Isolation):多个事务同时执行时,彼此之间相互隔离,互不影响(避免并发操作导致的数据混乱)。

  • 持久性(Durability):事务提交后,对数据库的修改是永久的,即使数据库崩溃,数据也不会丢失。

2.2 事务的核心语法(必背)

  • begin transaction:开启事务,标记事务的开始。

  • commit transaction:提交事务,将事务中所有操作的结果永久保存到数据库,事务结束。

  • rollback transaction:回滚事务,撤销事务中所有未提交的操作,恢复到事务开启前的状态,事务结束。

  • @@ERROR:全局变量,返回上一条SQL语句的错误码(0表示无错误,非0表示有错误),用于判断事务中是否出现异常。

三、使用事务解决转账一致性问题(核心案例)

通过事务包裹转账的两个更新操作,结合@@ERROR判断是否出现异常,异常则回滚,无异常则提交,确保两个操作要么都成功,要么都失败。

复制代码
-- 需求:从马超(StdudentId=1001)转账1000元给张飞(StdudentId=1002),使用事务保证数据一致性
declare @errorNum int; -- 定义变量,统计事务中出现的错误个数
set @errorNum = 0;     -- 初始化错误个数为0

begin transaction; -- 开启事务,标记事务开始

begin
   -- 步骤1:转出逻辑(马超账户扣除1000元)
   update CardNew set CurrentMoney = CurrentMoney - 1000 where StdudentId=1001;
   set @errorNum = @errorNum + @@ERROR; -- 记录上一条操作的错误码(0无错,非0有错)

   -- 步骤2:转入逻辑(张飞账户增加1000元)
   update CardNew set CurrentMoney = CurrentMoney + 1000 where StdudentId=1002;
   set @errorNum = @errorNum + @@ERROR; -- 累计错误个数

   -- 判断是否出现错误:错误个数>0,说明至少有一个操作失败
   if(@errorNum > 0)  
   begin
       rollback transaction; -- 回滚事务:撤销所有操作,恢复到事务开启前的状态
       print '转账失败,事务已回滚';
   end
   else
   begin
       commit transaction; -- 提交事务:将所有操作结果永久保存
       print '转账成功,事务已提交';
   end
end

-- 查询转账后数据,验证事务效果
select * from CardNew;

3.1 事务执行效果验证(分两种场景)

场景1:操作无异常(正常转账)

马超账户(1001)扣除1000元后余额为1000(符合check约束),张飞账户(1002)增加1000元后余额为2600,@@ERROR始终为0,@errorNum=0,执行commit transaction,转账成功,数据一致。

场景2:操作有异常(违反check约束)
复制代码
-- 模拟异常:从赵云(1000)转账1399元给马超(1001)(赵云余额1400-1399=1,违反check(CurrentMoney>1)约束)
declare @errorNum int;
set @errorNum = 0;
begin transaction;
begin
   -- 转出逻辑:赵云账户扣除1399元,余额变为1,违反check约束,操作失败,@@ERROR≠0
   update CardNew set CurrentMoney = CurrentMoney - 1399 where StdudentId=1000;
   set @errorNum = @errorNum + @@ERROR;

   -- 转入逻辑:马超账户增加1399元
   update CardNew set CurrentMoney = CurrentMoney + 1399 where StdudentId=1001;
   set @errorNum = @errorNum + @@ERROR;

   if(@errorNum > 0)  
   begin
       rollback transaction; -- 回滚事务,两个操作都撤销
       print '转账失败(违反余额约束),事务已回滚';
   end
   else
       commit transaction;
end

select * from CardNew; -- 查询结果:赵云和马超余额均未变化,数据一致

关键说明:转出操作违反check约束,@@ERROR返回非0值,@errorNum>0,执行回滚,两个更新操作都被撤销,数据恢复到事务开启前的状态,避免了数据不一致。

四、核心考点汇总(必背)

  1. 事务的作用:解决多步SQL操作的数据一致性问题(如转账、批量修改),确保操作要么全成功,要么全失败。

  2. 事务的核心语法:开启(begin transaction)、提交(commit transaction)、回滚(rollback transaction)。

  3. @@ERROR的用法:返回上一条SQL语句的错误码,0表示无错误,非0表示有错误,用于判断事务中是否出现异常。

  4. check约束:用于限制字段的取值范围(如余额>1),违反约束时,对应的SQL操作失败。

  5. 事务的ACID特性:原子性(核心)、一致性、隔离性、持久性,重点掌握原子性和一致性。

五、易错踩坑点

  • 开启事务后,未判断错误就直接提交,导致异常场景下数据不一致。

  • 混淆@@ERROR的作用,@@ERROR仅返回上一条SQL的错误码,需每次操作后立即累加,否则会被后续操作覆盖。

  • 事务中包含DDL语句(如create table、drop table),DDL语句会自动提交事务,导致事务失效(事务仅适用于DML语句:insert、update、delete)。

  • 回滚或提交事务后,未结束事务,或重复提交/回滚,导致语法报错。

  • 忽略check约束的影响,在事务中执行违反约束的操作,未处理异常,导致事务回滚但未提示原因。

六、事务语法速记模板(转账场景)

复制代码
-- 事务模板(适用于多步DML操作,确保数据一致性)
declare @errorNum int; -- 统计错误个数
set @errorNum = 0;

begin transaction; -- 开启事务
begin
    -- 操作1:转出/修改/插入
    操作1的SQL语句;
    set @errorNum += @@ERROR; -- 累加错误码

    -- 操作2:转入/修改/删除
    操作2的SQL语句;
    set @errorNum += @@ERROR; -- 累加错误码

    -- 可选:更多操作,依次累加错误码

    -- 判断是否有错误
    if(@errorNum > 0)
    begin
        rollback transaction; -- 回滚事务
        print '操作失败,事务回滚';
    end
    else
    begin
        commit transaction; -- 提交事务
        print '操作成功,事务提交';
    end
end

-- 查询结果,验证事务效果
select * from 表名;

T-SQL 事务拓展:并发、嵌套事务、进阶考点(承接上面转账案例)

一、事务四大特性 ACID 复习(必考简答)

  1. 原子性 A:事务内所有操作不可分割,全成或全败(转账核心)
  2. 一致性 C:事务前后数据库约束、业务规则不变(余额不能小于 1、转账总金额不变)
  3. 隔离性 I:多个事务同时运行,互相隔离,互不干扰(解决并发脏读、不可重复读、幻读)
  4. 持久性 D:commit 提交后数据永久保存,断电、宕机不丢失

二、并发事务三大问题(选择 / 简答高频)

多个人同时操作同一张银行卡(并发转账)会出现三类数据错乱:

1. 脏读

一个事务读到另一个事务未提交、中途回滚的数据。 举例: 事务 A 扣钱 1000(未 commit),事务 B 读取 A 余额;A 随后 rollback 撤销扣款。B 读到了不存在的扣款数据,就是脏读。

2. 不可重复读

同一事务内,两次读取同一行数据,中间被其他事务 update 修改并提交,两次结果不一样。 举例: 事务 B 第一次查赵云余额 1400;事务 A 转账扣掉 1000 并提交;事务 B 再次查询,余额变成 400,同一事务两次读取结果不同。

3. 幻读

同一事务内,两次相同条件查询,中间其他事务 insert/delete 提交,两次返回行数不一样。 举例: 事务 B 查询余额 > 1000 的人,查出 4 人;事务 A 新增一张银行卡并提交;事务 B 再查,变成 5 人,像多出来一行 "幻影数据"。

三、SQL Server 5 种隔离级别(解决并发问题)

隔离级别从低到高,锁越来越严格,并发性能越来越差:

  1. Read Uncommitted 读未提交:允许脏读、不可重复读、幻读
  2. Read Committed(系统默认级别):禁止脏读,允许不可重复读、幻读
  3. Repeatable Read:禁止脏读、不可重复读,允许幻读
  4. Serializable 可序列化:三类问题全部禁止,并发最低
  5. Snapshot 快照:行版本隔离,不加共享锁,无阻塞

修改当前会话隔离级别语法

sql

复制代码
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

四、嵌套事务(重点易错点)

核心结论:

SQL Server 没有真正的嵌套事务,内层 begin transaction 只是计数器累加,只有最外层 commit 才会真正写入数据库;任意一层 rollback 直接回滚全部事务。

示例演示

sql

复制代码
declare @err int=0
begin transaction --外层 计数=1
    update CardNew set CurrentMoney=CurrentMoney-1000 where StdudentId=1000
    set @err+=@@ERROR

    begin transaction --内层嵌套 计数=2
        update CardNew set CurrentMoney=CurrentMoney+1000 where StdudentId=1001
        set @err+=@@ERROR

        if(@err>0)
            rollback transaction; --只要一次回滚,全部撤销
        else
            commit transaction; --内层commit 计数=1,数据不落地

--必须外层再commit,事务才真正提交写入硬盘
commit transaction;

易错坑

  1. 内层 commit 只是事务计数器 - 1,不会永久保存数据
  2. 任意一层执行 rollback,整个大事务全部回滚,不分内外层

五、事务进阶:TRY-CATCH 异常捕获(比 @@ERROR 更推荐)

原来只用@@ERROR有缺陷:每一句执行后必须立刻累加,否则会被下一句覆盖。 使用begin try...begin catch自动捕获所有异常,不用手动累加错误码,考试、开发优先写这种。

转账事务完整版(TRY-CATCH 标准写法)

sql

复制代码
begin transaction
begin try
    --转出
    update CardNew set CurrentMoney = CurrentMoney - 1000 where StdudentId=1000
    --转入
    update CardNew set CurrentMoney = CurrentMoney + 1000 where StdudentId=1001

    --无任何异常,提交
    commit transaction
    print '转账成功'
end try
begin catch
    --任意一句报错,直接回滚全部操作
    rollback transaction
    print '转账失败,已回滚'
    --打印错误信息
    print '错误编号:'+cast(ERROR_NUMBER() as varchar)
    print '错误描述:'+ERROR_MESSAGE()
end catch

select * from CardNew

内置错误函数(catch 块专用)

  • ERROR_NUMBER ():错误编号
  • ERROR_MESSAGE ():完整错误文字
  • ERROR_SEVERITY ():错误严重等级

六、事务禁用场景(不能用事务)

DDL 语句执行时会自动提交当前事务,导致事务失效,不要放在事务中: create table /drop table /alter table /create proc /drop view 等

错误示例(事务直接失效):

sql

复制代码
begin transaction
create table test(id int) --DDL自动提交事务
update CardNew set CurrentMoney=1000 where StdudentId=1000
rollback --此时事务早已提交,回滚无效

七、高频简答题背诵总结

  1. 事务原子性是什么? 答:事务是不可分割单元,内部 DML 操作要么全部提交生效,要么全部回滚撤销,不会出现一半成功一半失败的数据不一致情况。

  2. 不加事务转账会出现什么问题? 答:转出扣款失败(余额违反约束报错),但转入加钱语句正常执行,总金额对不上,数据错乱。

  3. 脏读、不可重复读、幻读三者区别?

  • 脏读:读到未提交的修改
  • 不可重复读:同一事务两次读同一行,中间被别人 update 提交
  • 幻读:同一事务两次范围查询,中间被别人 insert/delete 提交
  1. @@ERROR 和 TRY-CATCH 哪个更好? TRY-CATCH,自动捕获所有异常,无需每一行手动记录错误码,不会丢失错误信息。

  2. 嵌套事务为什么内层 commit 没用? SQL Server 事务靠计数器控制,内层 commit 只减少计数,只有最外层 commit 才会持久化数据;任意 rollback 直接全部回滚。

相关推荐
风中芦苇啊15 小时前
Java EasyExcel 导入通用工具类:自定义注解映射字段 + 反射机制
java·开发语言
家有娇妻张兔兔15 小时前
Java 对接 PLC 主流型号最合适的方案:Apache PLC4X 实战指南
java·开发语言·plc·modbus·数据缓存·西门子s7·apache plc4x
摇滚侠16 小时前
Codebuddy 官网 Codebuddy IntelliJ IDEA 插件 阅读笔记 2
java·笔记·intellij-idea
snow@li16 小时前
MyBatis:动态 SQL 全景梳理
数据库·sql·mybatis
gugucoding17 小时前
24. 【Java】枚举:更安全的常量
java·开发语言
360智汇云17 小时前
KV-Probe:通用 KV 数据库测试套件
数据库
Kyrie_kk17 小时前
Java--BigInteger 超大整数计算
java·算法
x8617 小时前
我与 IT 这三十年:2015,大数据平台的重与轻
数据库·it史
开发者联盟league17 小时前
Java 通过 JNA 调用 C++ DLL:关键流程总结
java·c++·jna