SQL 事务(Transaction)完整版学习笔记(含转账案例+存储过程+异常捕获)

一、事务核心概念(必考简答题)

1. 事务定义

事务(TRANSACTION)是作为单个逻辑工作单元执行的一系列操作。多个操作作为一个整体向系统提交,要么全部执行成功,要么全部不执行(回滚),事务是不可分割的工作逻辑单元。

2. 事务四大特性(ACID 必背)

  • 原子性(Atomicity):事务整体不可拆分,所有操作同时成功或同时失败回滚,无部分执行情况。

  • 一致性(Consistency):事务执行前后,数据库数据始终保持合法、一致的状态,不会出现数据错乱。

  • 隔离性(Isolation):多个并发事务相互独立、互不干扰,彼此隔离运行。

  • 永久性(Durability):事务提交成功后,对数据库的修改永久生效,不会丢失。

3. 事务三大分类

(1)自动提交事务(默认模式)

SQL Server 默认机制,每条独立T-SQL语句都是一个单独事务,执行成功自动提交,失败自动回滚。

(2)显式事务(重点、最常用)

手动通过 begin transaction 开启事务,手动控制 commit 提交、rollback 回滚,适用于多语句联动业务(转账、增减库存等)。

(3)隐性事务

开启开关 SET IMPLICIT_TRANSACTIONS ON 后,后续所有T-SQL语句自动开启事务,需手动提交/回滚,无需手动写开启语句。


二、无事务的业务BUG(反面案例)

1. 创建银行卡表+测试数据

sql 复制代码
-- 创建银行卡资金表(带余额校验:余额必须大于1,规范字段类型)
create table CardNew
(
StudentId int identity(1000,1) primary key not null,
StudentName varchar(10) not null,
CurrentMoney money check(CurrentMoney > 1)
)

-- 初始化测试数据
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)

-- 【BUG场景】无事务转账(典型数据错乱问题)
-- 1001刘备余额1500,转出1000后剩余500(符合check约束,可执行)
update CardNew set CurrentMoney=CurrentMoney-1000 where StudentId=1001
-- 1002关羽转入1000
update CardNew set CurrentMoney=CurrentMoney+1000 where StudentId=1002

-- 查看数据
select * from CardNew

-- 【手动制造报错场景】重新初始化数据,演示BUG
truncate table CardNew
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)

-- 错误场景:1001转出1500,余额=0,违反 CurrentMoney>1 约束
update CardNew set CurrentMoney=CurrentMoney-1500 where StudentId=1001
-- 本条正常执行,导致:转出失败、转入成功,数据不一致
update CardNew set CurrentMoney=CurrentMoney+1500 where StudentId=1002

select * from CardNew

BUG原因:默认自动提交事务,两条update语句相互独立,前一句报错回滚,后一句正常提交,导致数据不一致。


三、基础显式事务(@@ERROR 错误捕获)

通过系统全局变量 @@ERROR 累计错误数,判断是否回滚/提交事务,解决转账数据错乱问题。

sql 复制代码
-- 重置测试数据
truncate table CardNew
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)

declare @errorNum int -- 定义错误计数器
set @errorNum=0

begin transaction -- 手动开启显式事务
	-- 转出操作(余额清零,触发约束报错)
	update CardNew set CurrentMoney=CurrentMoney-1500 where StudentId=1001
	set @errorNum=@errorNum+@@ERROR -- 累加SQL错误码

	-- 转入操作
	update CardNew set CurrentMoney=CurrentMoney+1500 where StudentId=1002
	set @errorNum=@errorNum+@@ERROR

-- 事务提交/回滚判断
if(@errorNum > 0)
begin
	rollback transaction -- 任意步骤报错,整体回滚
	print '事务执行失败,已全部回滚'
end
else
begin
	commit transaction -- 无错误,正常提交
	print '事务执行成功,数据已更新'
end

select * from CardNew

四、基础转账事务存储过程(可直接调用)

封装通用转账逻辑,支持自定义入账账号、出账账号、转账金额,适配所有转账场景。

sql 复制代码
-- 存在则删除存储过程
if exists(select * from sysobjects where name='Test1')
	drop proc Test1
go

-- 基础通用转账事务存储过程
create proc Test1
 @inAccount int,	-- 入账账号
 @outAccount int,	-- 出账账号
 @jine int			-- 转账金额
as
declare @errorNum int 
set @errorNum=0

begin transaction -- 开启事务
	-- 出账账号扣款
	update CardNew set CurrentMoney=CurrentMoney-@jine where StudentId=@outAccount
	set @errorNum=@errorNum+@@ERROR

	-- 入账账号加款
	update CardNew set CurrentMoney=CurrentMoney+@jine where StudentId=@inAccount
	set @errorNum=@errorNum+@@ERROR

	-- 事务逻辑判断
	if(@errorNum > 0)
	begin
		rollback transaction
		print '转账失败,事务回滚'
	end
	else
	begin
		commit transaction
		print '转账成功,事务提交'
	end
go

-- 测试1:正常转账(成功)
truncate table CardNew
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)

exec Test1 1003,1000,300
select * from CardNew

-- 测试2:异常转账(触发约束,失败回滚)
exec Test1 1003,1000,1200
select * from CardNew

五、高级异常捕获:TRY-CATCH 语法

1. 基础TRY-CATCH异常捕获

用于捕获代码运行异常,避免程序中断,可手动抛出错误、终止执行。

sql 复制代码
-- 基础 TRY-CATCH 异常捕获语法
begin try
	-- 强制类型转换错误(模拟业务异常)
	declare @i int
	set @i = CAST('abc' as int)
	print @i
end try
begin catch
	-- 抛出自定义错误信息
	raiserror('字符串无法转换为数字,数据类型错误',16,1)
	print '代码执行异常,已捕获错误'
end catch

2. TRY-CATCH 结合事务

sql 复制代码
-- TRY-CATCH + 事务 标准组合用法
begin tran
begin try
	delete from StudentInfo where StudentId=1000
	-- 主动抛出异常,测试回滚效果
	raiserror('无此学生编号,删除失败',16,1)
	commit tran -- 无异常则提交事务
	print '执行成功,事务已提交'
end try
begin catch
	print '代码执行出错,事务已回滚'
	if(@@TRANCOUNT > 0)
		rollback tran -- 存在未提交事务则回滚
end catch

核心逻辑:TRY包裹正常业务代码,CATCH捕获所有异常,只要出错立刻回滚事务,保证数据一致。


六、终极完整版转账存储过程(双重校验+业务判断+双异常捕获)

整合业务逻辑校验 + @@ERROR错误捕获 + TRY-CATCH异常捕获,企业级标准写法,无漏洞、安全性最高。

sql 复制代码
-- 终极完整版:业务校验+事务+双重异常捕获 转账存储过程
if exists ( select * from sysobjects where name='test01')
drop proc test01
go

create proc test01
@inAccount int,		-- 入账账号
@outAccount int,	-- 出账账号
@jine int			-- 转账金额
as
	declare @errorNum int
	set @errorNum=0

	-- ========== 第一层:前置业务合法性校验(拦截逻辑错误) ==========
	-- 1. 转账金额不能为负数
	if (@jine <= 0)
	begin
		raiserror('转账金额不合法,必须大于0',16,1)
		return
	end

	-- 2. 校验转出账号是否存在
	if not exists (select 1 from CardNew where StudentId=@outAccount)
	 begin
		raiserror('转出账户不存在',16,1)
		return
	 end

	 -- 3. 校验转入账号是否存在
	if not exists (select 1 from CardNew where StudentId=@inAccount)
	 begin
		raiserror('转入账户不存在',16,1)
		return
	 end

	-- 4. 校验转出账户余额是否充足
	declare @yue money
	select @yue = CurrentMoney from CardNew where StudentId=@outAccount
	if(@yue < @jine)
	begin
		raiserror('账户余额不足,无法转账',16,1)
		return
	end

	-- ========== 第二层:事务 + 双重错误捕获(拦截系统SQL错误) ==========
	begin tran -- 开启事务
	begin try
		-- 转出扣款
		update CardNew set CurrentMoney=CurrentMoney-@jine where StudentId=@outAccount
		set @errorNum=@errorNum+@@ERROR

		-- 转入加款
		update CardNew set CurrentMoney=CurrentMoney+@jine where StudentId=@inAccount
		set @errorNum=@errorNum+@@ERROR

		-- 捕获轻微SQL执行错误
		if(@errorNum > 0)
		begin
			raiserror('SQL语句执行出错,事务回滚',16,1)
			rollback transaction
		end
		else
		begin
			commit transaction
			print '转账业务执行成功'
		end
	end try
	begin catch
		-- 捕获严重异常,防止事务悬挂
		if(@@TRANCOUNT > 0)
		begin
			rollback tran
			print '程序异常,事务强制回滚'
		end
		return
	end catch
go

-- 测试调用
truncate table CardNew
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)

-- 正常转账
exec test01 1001,1000,200
select * from CardNew

-- 异常测试(余额不足)
exec test01 1002,1000,2000
select * from CardNew

七、核心考点总结(考前必背)

  1. 事务作用:保证多步关联操作数据一致性,防止部分执行、数据错乱(转账核心场景)。

  2. ACID四大特性:原子性、一致性、隔离性、永久性(简答题必考)。

  3. 事务三语句:begin transaction开启、commit提交、rollback回滚。

  4. 错误捕获区别:@@ERROR捕获单条SQL错误;TRY-CATCH捕获所有运行异常(严重错误)。

  5. 双层校验逻辑:先业务人工校验(金额、账号、余额),再系统事务校验,零漏洞。

  6. @@TRANCOUNT:判断当前是否存在未提交的事务,避免重复回滚报错。

相关推荐
风曦Kisaki3 小时前
# Kubernetes(K8s)笔记Day12 :K8s 七层代理(Ingress 和 Ingress Controller)
linux·笔记·云原生·容器·kubernetes
Bernice橘子3 小时前
职场英语学习计划Day034
学习
雪之下雪乃的代码日记4 小时前
Python快速入门(Java开发者版)
java·开发语言·笔记·python
磊灬泽4 小时前
【从零学习 MQTT】发布/订阅、主题、QoS 与报文解析
学习
zzh___zzh4 小时前
Java Stream API 常用方法笔记
java·笔记
0566464 小时前
Python数据结构——循环队列
python·学习
zzm6285 小时前
安全嵌套子博弈求解与非完美信息博弈——NIPS 2017 最佳论文阅读笔记
论文阅读·笔记·nips·博弈论
天国梦5 小时前
2026高中生英语词汇学习APP测评:天学网等4款产品实测对比
学习
砚凝霜5 小时前
软考网络工程师|第 6 章 网络安全基础、攻击、等保完整备考笔记
网络·笔记·web安全