SQL Server 事务与索引学习总结
一、事务(Transaction)
1.1 事务概念
事务是数据库操作的基本单位,确保一组操作要么全部成功执行,要么全部失败回滚。事务具有ACID特性:
- 原子性(Atomicity):事务中的操作是不可分割的原子单元
- 一致性(Consistency):事务执行前后数据完整性保持一致
- 隔离性(Isolation):多个事务并发执行时互不干扰
- 持久性(Durability):事务提交后数据永久保存
1.2 使用@@ERROR处理事务
通过检查@@ERROR全局变量来判断SQL执行是否出错:
sql
declare @errorNum int -- 声明错误计数器
set @errorNum = 0
begin transaction -- 开始事务
begin
-- 转出方扣款
update CardNew set CurrentMoney = CurrentMoney - 1000 where StdudentId=1001
set @errorNum = @errorNum + @@ERROR -- 记录当前错误号
-- 转入方加款
update CardNew set CurrentMoney = CurrentMoney + 1000 where StdudentId=1002
set @errorNum = @errorNum + @@ERROR -- 记录当前错误号
if(@errorNum > 0)
begin
rollback transaction -- 回滚事务
end
else
commit transaction -- 提交事务
end
select * from CardNew
关键知识点:
@@ERROR:返回上一条SQL语句的错误号,0表示成功begin transaction:开始事务commit transaction:提交事务(永久保存)rollback transaction:回滚事务(撤销所有操作)
1.4 存储过程中的事务
将事务逻辑封装到存储过程中,提高复用性:
sql
if exists(select * from sysobjects where name = 'Test1')
drop proc Test1
go
create proc Test1
@inAccount int, -- 转入账户
@outAccout int, -- 转出账户
@jine int -- 转账金额
as
begin
declare @errorNum int -- 错误计数器
set @errorNum = 0
begin transaction -- 开始事务
begin
-- 转出方扣款
update CardNew set CurrentMoney = CurrentMoney - @jine where StdudentId=@outAccout
set @errorNum = @errorNum + @@ERROR -- 记录错误
-- 转入方加款
update CardNew set CurrentMoney = CurrentMoney + @jine where StdudentId=@inAccount
set @errorNum = @errorNum + @@ERROR -- 记录错误
if(@errorNum > 0)
rollback transaction -- 回滚事务
else
commit transaction -- 提交事务
end
end
go
-- 调用存储过程进行转账
exec Test1 1000, 1003, 1000
select * from CardNew
1.5 使用TRY...CATCH处理事务
TRY...CATCH 是SQL Server 2005+引入的异常处理机制,比@@ERROR更简洁、更强大:
sql
-- 使用TRY...CATCH实现事务
begin try
begin transaction
update t_cardnew set current_money = current_money - 1000 where stu_id = 1000;
update t_cardnew set current_money = current_money + 1000 where stu_id = 1002;
commit transaction
end try
begin catch
if @@TRANCOUNT > 0
rollback transaction
end catch
go
TRY...CATCH工作流程:
BEGIN TRY块中的代码按顺序执行- 如果TRY块中发生错误,程序立即跳转到
BEGIN CATCH块 - CATCH块中可以进行错误处理和事务回滚
- CATCH块执行完毕后,继续执行CATCH块之后的代码
1.6 存储过程中的TRY...CATCH事务
将TRY...CATCH事务逻辑封装到存储过程中:
sql
if exists(select * from sys.objects where name = 'usp_test1')
drop procedure usp_test1
go
create procedure usp_test1
@InAccount int, -- 转入账户
@OutAccount int, -- 转出账户
@Amount int -- 转账金额
as
begin
begin try
begin transaction
update t_cardnew set current_money = current_money - @Amount where stu_id = @OutAccount;
update t_cardnew set current_money = current_money + @Amount where stu_id = @InAccount;
commit transaction
print '转账成功'
end try
begin catch
if @@TRANCOUNT > 0
rollback transaction
print '转账失败'
print error_message() -- 输出错误信息
end catch
end
go
-- 调用存储过程进行转账
exec usp_test1 1000, 1002, 100
-- 查询转账结果
select * from t_cardnew;
关键知识点:
ERROR_MESSAGE():返回当前错误的详细描述信息TRY...CATCH模式更简洁,无需手动维护错误计数器- 错误发生时自动跳转到CATCH块,避免错误遗漏
二、索引(Index)
2.1 索引概述
索引是数据库中用于加速数据检索的数据结构,类似于书籍的目录。合理使用索引可以显著提高查询性能。
2.2 聚集索引 vs 非聚集索引
| 特性 | 聚集索引(Clustered) | 非聚集索引(Non-clustered) |
|---|---|---|
| 数据存储 | 数据按索引顺序物理存储 | 数据存储在单独位置,索引存储指向数据位置的指针 |
| 数量限制 | 每个表只能有一个 | 最多249个 |
| 唯一性 | 必须唯一(主键默认创建) | 可以不唯一 |
| 适用场景 | 主键、经常排序的列 | 经常查询的非主键列 |
2.3 创建非聚集索引
sql
-- 检查索引是否存在,如果存在则删除
if exists(select name from sysindexes where name = 'IX_Student_StudentName')
drop index CardNew.IX_Student_StudentName
go
-- 创建非聚集索引
create nonclustered index IX_Student_StudentName
on CardNew(CurrentMoney)
with fillfactor = 30 -- 填充因子:索引页填充30%,预留70%空间供后续插入
go
关键知识点:
fillfactor:填充因子,指定索引页的填充百分比,范围1-100- 填充因子越小,预留空间越多,减少页分裂,但占用更多存储空间
- 填充因子越大,空间利用率越高,但插入/更新时可能导致页分裂
2.4 使用索引提示
强制查询使用指定的索引:
sql
-- 使用索引提示强制使用指定索引
select * from CardNew
with (index=IX_CardNew_StudentName)
where StudentName like '张%'
-- 查询时强制使用指定索引
select * from CardNew
with (index=IX_Student_StudentName)
三、存储过程与分页
3.1 分页存储过程
使用OFFSET...FETCH NEXT实现分页查询:
sql
if exists(select * from sysobjects where name = 'usp_test3')
drop procedure usp_test3
go
create procedure usp_test3
@pagesize int, -- 每页大小
@currentpage int, -- 当前页码
@where varchar(max), -- 查询条件
@totalPage int output -- 输出参数:总页数
as
begin
declare @sql1 nvarchar(max) -- 查询数据的SQL
declare @sql2 nvarchar(max) -- 查询总数的SQL
declare @total int
declare @offset int
-- 计算偏移量
set @offset = (@currentpage - 1) * @pagesize
-- 初始化SQL语句(N前缀表示Unicode字符串)
set @sql1 = N'select * from StudentInfo where 1=1 '
set @sql2 = N'select @count = count(*) from StudentInfo where 1=1 '
-- 如果有查询条件,拼接条件
if(len(trim(@where)) <> 0)
begin
set @sql1 += @where
set @sql2 += @where
end
-- 拼接分页语句
set @sql1 += N'order by Score offset ' + CAST(@offset as nvarchar)
+ N' rows fetch next ' + CAST(@pagesize as nvarchar) + N' rows only';
-- 执行查询数据的SQL
exec(@sql1)
-- 使用sp_executesql执行带输出参数的SQL
exec sp_executesql @sql2, N'@count int output', @total output
-- 计算总页数
if(@total % @pagesize = 0)
set @totalPage = @total / @pagesize
else
set @totalPage = @total / @pagesize + 1
end
go
-- 测试分页存储过程
declare @f1 int
exec usp_test3 3, 2, '', @f1 output
print @f1 -- 输出总页数
关键知识点:
N'字符串':N前缀表示Unicode字符串(UTF-16)OFFSET...FETCH NEXT:SQL Server 2012+支持的分页语法sp_executesql:执行动态SQL,支持参数化查询,更安全exec(@sql):执行动态SQL,但不支持参数化
3.2 增删改查存储过程
新增(Insert):
sql
if exists(select * from sysobjects where name = 'p_stundentInfo_add')
drop procedure p_stundentInfo_add
go
create procedure p_stundentInfo_add
@studentName varchar(10),
@score int
as
begin
insert into StudentInfo(StudentName, Score) values(@studentName, @score)
end
go
exec p_stundentInfo_add '张三', 100
更新(Update):
sql
if exists(select * from sys.procedures where name = 'p_studentinfo_update')
drop proc p_studentinfo_update
go
create proc p_studentinfo_update
@StudentId int,
@StudentName varchar(50),
@Score int
as
begin
set nocount on; -- 关闭影响行数提示
update StudentInfo
set StudentName = @StudentName, Score = @Score
where StudentId = @StudentId;
end
go
exec p_studentinfo_update 1002, '李四', 95
删除(Delete):
sql
if exists(select * from sys.procedures where name = 'p_studentinfo_delete')
drop proc p_studentinfo_delete
go
create proc p_studentinfo_delete
@StudentId int
as
begin
set nocount on;
delete from StudentInfo where StudentId = @StudentId;
end
go
exec p_studentinfo_delete 1002
四、知识点总结
5.1 事务核心要点
- 事务四要素:原子性、一致性、隔离性、持久性
- 事务控制语句 :
BEGIN TRAN→COMMIT TRAN/ROLLBACK TRAN - 错误检测 :
@@ERROR(单条SQL错误)、TRY...CATCH(捕获异常) - 事务状态 :
@@TRANCOUNT(活动事务数) - 错误抛出 :
RAISERROR('消息', 16, 1)
5.2 索引核心要点
- 聚集索引:唯一、数据按索引顺序存储、每个表一个
- 非聚集索引:最多249个、索引和数据分开存储
- 填充因子 :
fillfactor控制索引页填充比例 - 索引提示 :
WITH (INDEX=索引名)强制使用指定索引
5.3 存储过程核心要点
- 分页实现 :
OFFSET...FETCH NEXT语法 - 动态SQL :
exec(@sql)和sp_executesql(推荐) - 输出参数 :
@参数名 类型 output - Unicode字符串 :动态SQL使用
N'字符串'前缀