什么是事务?什么是锁?

答:事务是指一个工作单元,它包含了一组数据操作命令,并且所有的命令作为一个整体一起向系统提交或撤消请求操作,即这组命令要么都执行,要么都不执行。
锁是在多用户环境中对数据的访问的限制。SqlServer自动锁定特定记录、字段或文件,防止用户访问,以维护数据安全或防止并发数据操作问题,锁可以保证事务的完整性和并发性。

事务

事务的概念:
数据库中的事务是数据库并发控制的基本单位,一条或者一组语句要么全部成功,对数据库中的某些数据成功修改; 要么全部不成功,数据库中的数据还原到这些语句执行。

事务必须具备以下四个属性,简称ACID 属性:
原子性(Atomicity):事务是一个完整的操作。事务的各步操作是不可分的(原子的);要么都执行,要么都不执行
一致性(Consistency):当事务完成时,数据必须处于一致状态
隔离性(Isolation):对数据进行修改的所有并发事务是彼此隔离的,这表明事务必须是独立的,它不应以任何方式依赖于或影响其他事务
永久性(Durability):事务完成后,它对数据库的修改被永久保持,事务日志能够保持事务的永久性

事务被分为3类常见的事务:
自动提交事务:是SQL Server默认的一种事务模式,每条Sql语句都被看成一个事务进行处理,你应该没有见过,一条Update 修改2个字段的语句,只修该了1个字段而另外一个字段没有修改。。
显式事务:T-sql标明,由Begin Transaction开启事务开始,由Commit Transaction 提交事务、Rollback Transaction 回滚事务结束。
隐式事务:使用Set IMPLICIT_TRANSACTIONS ON 将将隐式事务模式打开,不用Begin Transaction开启事务,当一个事务结束,这个模式会自动启用下一个事务,只用Commit Transaction 提交事务、Rollback Transaction 回滚事务即可。

事务中常用的语句:
Begin Transaction:标记事务开始。
Commit Transaction:事务已经成功执行,数据已经处理妥当。
Rollback Transaction:数据处理过程中出错,回滚到没有处理之前的数据状态,或回滚到事务内部的保存点。
Save Transaction:事务内部设置的保存点,就是事务可以不全部回滚,只回滚到这里,保证事务内部不出错的前提下。

同时增加三个城市示例:

begin tran tran_R_City --开始事务
declare @tran_error int;
set @tran_error=0;

begin try
    insert into R_City values('新疆','xinjiang',1,0,'2019-03-21',1,'XJ')
    insert into R_City values('深圳','shenzhen',1,0,'2019-03-21',1,'SZ')
    insert into R_City values('广西','guangxi',1,0,'2019-03-21',1,'GX')
end try

begin catch
set @tran_error=@tran_error+1;--加分号或不加都能正常执行
end catch

if(@tran_error>0)
begin 
    rollback tran ;--执行出错,回滚事务(不指定事务名称)
    print @tran_error;
end
else
begin 
   commit tran ;--没有异常,提交事务(不指定事务名称)
   print @tran_error;
End

结果:

什么是事务?什么是锁?

银行卡转账示例:

create table bank
(
customer varchar(20), --客户姓名
currentMoney money --当前余额
)
--银行规定:每个卡上必须要有元钱
alter table bank
add constraint CK_BANK_CURRENTMONEY check(currentMoney>=1); --模拟两个用户
insert into bank values('张三',1000);
insert into bank values('李四',1);
select * from bank; --李四找张三借元钱
--(1)将张三的余额减少
update bank set currentMoney =currentMoney-1000 where customer='张三';
select @@error; --返回如果不为,表示上面的SQL语句执行有问题。
--(2)将李四的余额要增加
update bank set currentMoney =currentMoney + 1000 where customer='李四';
select @@error; --返回如果不为,表示上面的SQL语句执行没有问题。
--事务:将需要做一件事情的所有步骤当做一个整体执行,要么所有步骤都执行,要么一个步骤都不执行。
--显示事务(常用):
--(1)开始事务:
begin transaction;
--(2)提交事务:
commit transaction;
--(3)回滚事务:
rollback transaction; --隐性事务:
SET IMPLICIT_TRANSACTIONS ON
SET IMPLICIT_TRANSACTIONS OFF
delete from bank where customer='李四';
select * from bank;
delete from bank; --自动提交事务:这是SQL Server 的默认模式,它将每条单独的T-SQL 语句视为一个事务,如果成功执行,则自动提交;如果错误,则自动回滚
--转账出现了问题,如果解决?要使用事务
--决定什么时候提交事务?什么时候回滚事务?需要根据@@error的值来决定。
---@@error 只记录上一条SQL语句的执行状态如果成功,返回,如果不成功,返回非的值
--第一步:开启事务
begin transaction;
declare @myerror int --声明一个变量,用来记录所有SQL语句执行完成后的状态值
set @myerror=0 --默认为表示无错误
--(1)将张三的余额减少
update bank set currentMoney =currentMoney-1000 where customer='张三';
set @myerror=@myerror+@@error;
--(2)将李四的余额要增加
update bank set currentMoney =currentMoney + 1000 where customer='李四';
set @myerror=@myerror+@@error;
if(@myerror<>0)
begin
print '对不起,转账未成功!' --第二步:回滚事务
rollback transaction;
end;
else
begin
print '恭喜您,转账成功' --提交事务
commit transaction ;
end;
go
print '转账后的状态为:' go
select * from bank;

使用set xact_abort
设置 xact_abort on/off , 指定是否回滚当前事务,为on时如果当前sql出错,回滚整个事务,为off时如果sql出错回滚当前sql语句,其它语句照常运行读写数据库。
 需要注意的时:xact_abort只对运行时出现的错误有用,如果sql语句存在编译时错误,那么他就失灵啦。

delete tran_R_City --清空数据
set xact_abort off
begin tran 
    --语句正确
   insert into R_City values('新疆','xinjiang',1,0,'2019-03-21',1,'XJ')
   --IsShow为int类型,出错,如果1..那个数据换成'132dsaf' xact_abort将失效
    insert into R_City values('深圳','shenzhen',1,0,'2019-03-21',1,'SZ')
   --语句正确
   insert into R_City values('广西','guangxi',1,0,'2019-03-21',1,'GX')
commit tran
select * from tran_R_City


在多用户都用事务同时访问同一个数据资源的情况下,就会造成以下几种数据错误。

更新丢失:多个用户同时对一个数据资源进行更新,必定会产生被覆盖的数据,造成数据读写异常。

不可重复读:如果一个用户在一个事务中多次读取一条数据,而另外一个用户则同时更新啦这条数据,造成第一个用户多次读取数据不一致。

脏读:第一个事务读取第二个事务正在更新的数据表,如果第二个事务还没有更新完成,那么第一个事务读取的数据将是一半为更新过的,一半还没更新过的数据,这样的数据毫无意义。

幻读:第一个事务读取一个结果集后,第二个事务,对这个结果集经行增删操作,然而第一个事务中再次对这个结果集进行查询时,数据发现丢失或新增。

锁定,就是为解决这些问题所生的,他的存在使得一个事务对他自己的数据块进行操作的时候,而另外一个事务则不能插足这些数据块。这就是所谓的锁定。

锁定从数据库系统的角度大致可以分为6种:

共享锁(S):还可以叫他读锁。可以并发读取数据,但不能修改数据。也就是说当数据资源上存在共享锁的时候,所有的事务都不能对这个资源进行修改,直到数据读取完成,共享锁释放。

排它锁(X):还可以叫他独占锁、写锁。就是如果你对数据资源进行增删改操作时,不允许其它任何事务操作这块资源,直到排它锁被释放,防止同时对同一资源进行多重操作。

更新锁(U):防止出现死锁的锁模式,两个事务对一个数据资源进行先读取在修改的情况下,使用共享锁和排它锁有时会出现死锁现象,而使用更新锁则可以避免死锁的出现。资源的更新锁一次只能分配给一个事务,如果需要对资源进行修改,更新锁会变成排他锁,否则变为共享锁。

意向锁:SQL Server需要在层次结构中的底层资源上(如行,列)获取共享锁,排它锁,更新锁。例如表级放置了意向共享锁,就表示事务要对表的页或行上使用共享锁。在表的某一行上上放置意向锁,可以防止其它事务获取其它不兼容的的锁。意向锁可以提高性能,因为数据引擎不需要检测资源的每一列每一行,就能判断是否可以获取到该资源的兼容锁。意向锁包括三种类型:意向共享锁(IS),意向排他锁(IX),意向排他共享锁(SIX)。

架构锁:防止修改表结构时,并发访问的锁。

大容量更新锁:允许多个线程将大容量数据并发的插入到同一个表中,在加载的同时,不允许其它进程访问该表。

然而数据库并没有出现无限等待的情况,是因为数据库搜索引擎会定期检测这种状况,一旦发现有情况,立马选择一个事务作为牺牲品。牺牲的事务,将会回滚数据。有点像两个人在过独木桥,两个无脑的人都走在啦独木桥中间,如果不落水,必定要有一个人给退回来。这种相互等待的过程,是一种耗时耗资源的现象,所以能避则避。

哪个人会被退回来,作为牺牲品,这个我们是可以控制的。控制语法:

set deadlock_priority  <级别>

死锁处理的优先级别为 low<normal<high,不指定的情况下默认为normal,牺牲品为随机。如果指定,牺牲品为级别低的。
还可以使用数字来处理标识级别:-10到-5为low,-5为normal,-5到10为high。

减少死锁的发生,提高数据库性能

死锁耗时耗资源,然而在大型数据库中,高并发带来的死锁是不可避免的,所以我们只能让其变的更少。
1.按照同一顺序访问数据库资源,上述例子就不会发生死锁啦
2.保持是事务的简短,尽量不要让一个事务处理过于复杂的读写操作。事务过于复杂,占用资源会增多,处理时间增长,容易与其它事务冲突,提升死锁概率。
3.尽量不要在事务中要求用户响应,比如修改新增数据之后在完成整个事务的提交,这样延长事务占用资源的时间,也会提升死锁概率。
4.尽量减少数据库的并发量
5.尽可能使用分区表,分区视图,把数据放置在不同的磁盘和文件组中,分散访问保存在不同分区的数据,减少因为表中放置锁而造成的其它事务长时间等待。
6.避免占用时间很长并且关系表复杂的数据操作。
7.使用较低的隔离级别,使用较低的隔离级别比使用较高的隔离级别持有共享锁的时间更短。这样就减少了锁争用。

参考资料:

浅谈SQL SERVER中事务的ACID
SQL Server中的事务与锁
T-SQL查询进阶—理解SQL Server中的锁

相关推荐