设为首页 收藏本站
查看: 1266|回复: 0

[经验分享] SQL Server创建事务——锁

[复制链接]

尚未签到

发表于 2017-12-13 18:18:35 | 显示全部楼层 |阅读模式
  学习地址:http://www.cnblogs.com/knowledgesea/p/3714417.html
  事务定义:
  事务是作为单个逻辑单元执行的一系列操作,它是一个不可分割的工作逻辑单元。它包含了一组数据库操作命令,这组命令要么全部执行,要么全部不执行。
  举个例子,我们经常用到的 ATM 存取款机,比如转账的时候,是先减去转出账户的金额,然后再在指定转入账户的金额加上转出的金额。如果刚好这个时候转出的操作已经执行完成,但是由于系统的故障,导致转入的操作失败了。那么怎么办?这就需要用到事务了,只要事务里面有一条命令未成功执行,那么数据就会回滚到事务开始之前的状态。
  事务特性:
  1、原子性(Atomicity):事务是一个完整的操作, 事务中所有操作命令必须作为一个整体提交或回滚。如果事务中任何操作命令失败,则整个事务将因失败而回滚。
  2、一致性(Consistency):当事务完成时,数据都处于一致状态。
  3、隔离性(Isolation): 对数据进行修改的所有并发事务是彼此隔离的,它不以任何方式依赖或影响其他事务。
  4、持久性(Durability):事务提交之后,数据是永久性的,不可再回滚。
  事务操作:
  1、begin transaction:开始事务。
  2、commit transaction:提交事务。
  3、rollback transaction:回滚事务。
  4、save transaction:事务保存点。即事务回滚时,可以指定回滚到保存点,而不进行全部回滚。
  事务分类:
  1、显式事务:用 begin transaction 明确指定事务的开始,由 commit transaction 提交事务、rollback transaction 回滚事务到事务结束。
  2、隐式事务:通过设置 set implicit_transactions on 语句,将隐式事务模式设置为打开。当以隐式事务模式操作时,不必使用 begin transaction 开启事务,当一个事务结束后,这个模式会自动启用下一个事务,只需使用 commit transaction 提交事务或 Rollback Transaction 回滚事务即可。
  3、自动提交事务: 这是 SQL Server 的默认模式,它将每条单独的 T-SQL 语句视为一个事务。如果成功执行,则自动提交。如果错误,则自动回滚。
  先看一下数据:
DSC0000.png

DSC0001.png

  

begin tran  
declare @errorNo int   
  
set @errorNo=0
  
begin try
  
     update Student set C_S_Id='2' where S_StuNo='003'
  
     set @errorNo=@errorNo+@@ERROR
  
     select 'S_StuNo=003 已经修改啦'
  

  
     update Student set C_S_Id='3' where S_StuNo='002'
  
     set @errorNo=@errorNo+@@ERROR            
  
     select 'S_StuNo=002 已经修改啦'
  

  
     if(@errorNo>0)
  
     begin
  
         
  
         RAISERROR(233333,16,3)
  
     end
  

  
end try
  
begin catch
  
     select ERROR_NUMBER() errorNumber,        
  
            ERROR_SEVERITY() errorSeverity,   
  
            ERROR_STATE() errorState,        
  
            ERROR_PROCEDURE() errorProcedure,   
  
            ERROR_LINE() errorLine,        
  
            ERROR_MESSAGE() errorMessage        
  

  
     if(@@trancount>0)   
  
     begin
  
         rollback tran;        
  
     end
  
end catch
  

  
if(@@trancount>0)
  
begin
  
     commit tran;        
  
end
  

  
select * from Student
  

DSC0002.png

  这里由于外键约束的原因,所以第二条 update 语句导致失败,以上结果可以看出第一条数据肯定是执行过了,但是在 catch 语句里面回滚了,所以数据还是原来的状态。
  现在只需要把第二条 update 语句 C_S_Id 列的值改为 5 即可。
  

begin tran  
declare @errorNo int   
  
set @errorNo=0
  
begin try
  
     update Student set C_S_Id='2' where S_StuNo='003'
  
     set @errorNo=@errorNo+@@ERROR
  
     select 'S_StuNo=003 已经修改啦'
  

  
     update Student set C_S_Id='5' where S_StuNo='002'
  
     set @errorNo=@errorNo+@@ERROR            
  
     select 'S_StuNo=002 已经修改啦'
  

  
     if(@errorNo>0)
  
     begin
  
         
  
         RAISERROR(233333,16,3)
  
     end
  

  
end try
  
begin catch
  
     select ERROR_NUMBER() errorNumber,        
  
            ERROR_SEVERITY() errorSeverity,   
  
            ERROR_STATE() errorState,        
  
            ERROR_PROCEDURE() errorProcedure,   
  
            ERROR_LINE() errorLine,        
  
            ERROR_MESSAGE() errorMessage        
  

  
     if(@@trancount>0)   
  
     begin
  
         rollback tran;        
  
     end
  
end catch
  

  
if(@@trancount>0)
  
begin
  
     commit tran;        
  
end
  

  
select * from Student
  

DSC0003.png

  关于 RAISERROR 自定义抛出异常可以看这里:http://www.cnblogs.com/Brambling/p/6687068.html
  设置 xact_abort:
  设置 xact_abort on/off , 指定是否回滚当前事务,为 on 时如果当前 sql 出错,回滚整个事务,为 off 时如果 sql 出错回滚当前 sql 语句,其它语句照常运行读写数据库。
  xact_abort 只对运行时出现的错误有用。
  

set xact_abort off  

  
begin tran        
  
declare @errorNo int   
  
set @errorNo=0
  
begin try
  
     update Student set C_S_Id='2' where S_StuNo='003'
  
     set @errorNo=@errorNo+@@ERROR
  
     select 'S_StuNo=003 已经修改啦'
  

  
     update Student set C_S_Id='3' where S_StuNo='002'
  
     set @errorNo=@errorNo+@@ERROR            
  
     select 'S_StuNo=002 已经修改啦'
  

  
     if(@errorNo>0)
  
     begin
  
         
  
         RAISERROR(233333,16,3)
  
     end
  

  
end try
  
begin catch
  
     select ERROR_NUMBER() errorNumber,        
  
            ERROR_SEVERITY() errorSeverity,   
  
            ERROR_STATE() errorState,        
  
            ERROR_PROCEDURE() errorProcedure,   
  
            ERROR_LINE() errorLine,        
  
            ERROR_MESSAGE() errorMessage        
  

  
     if(@@trancount>0)   
  
     begin
  
         rollback tran;        
  
     end
  
end catch
  

  
select * from Student
  

DSC0004.png

  xact_abort 设置为 off 时,虽然也出现了异常,但是可以看出第一天数据还是修改了,并没有回滚。因为它只是回滚出错的 sql 语句,并不全部回滚。
  

set xact_abort on  

  
begin tran        
  
declare @errorNo int   
  
set @errorNo=0
  
begin try
  
     update Student set C_S_Id='2' where S_StuNo='003'
  
     set @errorNo=@errorNo+@@ERROR
  
     select 'S_StuNo=003 已经修改啦'
  

  
     update Student set C_S_Id='3' where S_StuNo='002'
  
     set @errorNo=@errorNo+@@ERROR            
  
     select 'S_StuNo=002 已经修改啦'
  

  
     if(@errorNo>0)
  
     begin
  
         
  
         RAISERROR(233333,16,3)
  
     end
  

  
end try
  
begin catch
  
     select ERROR_NUMBER() errorNumber,        
  
            ERROR_SEVERITY() errorSeverity,   
  
            ERROR_STATE() errorState,        
  
            ERROR_PROCEDURE() errorProcedure,   
  
            ERROR_LINE() errorLine,        
  
            ERROR_MESSAGE() errorMessage        
  

  
     if(@@trancount>0)   
  
     begin
  
         rollback tran;        
  
     end
  
end catch
  

  
select * from Student
  

DSC0005.png

  xact_abort 设置为 on 时,出现了异常,回滚整个事务。
  事务死锁:
  打开两个查询窗口,把下面的语句,分别放入2个查询窗口,在5秒内运行2个事务模块。
  

begin tran  
   update Student set C_S_Id='2' where S_StuNo='002'
  

  
   waitfor delay '0:0:5'
  

  
   update Student set C_S_Id='5' where S_StuNo='003'
  
commit tran
  

  
select * from Student
  

  

begin tran  
   update Student set C_S_Id='5' where S_StuNo='003'
  

  
   waitfor delay '0:0:5'
  

  
   update Student set C_S_Id='2' where S_StuNo='002'
  
commit tran
  

  
select * from Student
  

DSC0006.png

DSC0007.png

  因为事务在执行过程中会将事务中用到的表和数据进行锁定,直到事务结束(提交或回滚),才会释放。
  在很多用户都同时使用事务访问同一个数据资源的情况下,就会造成以下几种数据错误:
  1、更新丢失:多个用户同时对一个数据资源进行更新,必定会产生被覆盖的数据,造成数据读写异常。
  2、不可重复读:如果一个用户在一个事务中多次读取一条数据,而另外一个用户则同时更新啦这条数据,造成第一个用户多次读取数据不一致。
  3、脏读:第一个事务读取第二个事务正在更新的数据表,如果第二个事务还没有更新完成,那么第一个事务读取的数据将是一半为更新过的,一半还没更新过的数据。
  4、幻读:第一个事务读取一个结果集后,第二个事务,对这个结果集进行增删改操作,然而第一个事务中再次对这个结果集进行查询时,数据发现丢失或新增。
  然而锁定,就是为解决这些问题的,它的存在使得一个事务对它自己的数据块进行操作的时候,而另外一个事务则不能插足这些数据块。这就是所谓的锁定。
  锁兼容性具体参见:http://msdn.microsoft.com/zh-cn/library/ms186396.aspx
  锁粒度和层次结构参见:http://msdn.microsoft.com/zh-cn/library/ms189849(v=sql.105).aspx
  什么是死锁,为什么会产生死锁。见上面的例子。
  例子是这样的:
  第一个事务(称为A):先更新表 Student S_StuNo='003' 这条数据 --->>停顿5秒---->>更新表 Student S_StuNo='002' 这条数据
  第二个事务(称为B):先更新表 Student S_StuNo='002' 这条数据--->>停顿5秒---->>更新表 Student S_StuNo='003' 这条数据
  先执行事务A----5秒之内---执行事务B,出现死锁现象。
  过程是这样子的:


  • A更新表 Student S_StuNo='003' 这条数据,请求排他锁,成功。
  • B更新表 Student S_StuNo='002' 这条数据,请求排他锁,成功。
  • 5秒过后
  • A更新表 Student S_StuNo='002' 这条数据,请求排它锁,由于B占用着表 Student S_StuNo='002' 这条数据,等待。
  • B更新表 Student S_StuNo='003' 这条数据,请求排它锁,由于A占用着表 Student S_StuNo='003' 这条数据,等待。
  这样相互等待对方释放资源,造成资源读写拥挤堵塞的情况,就被称为死锁现象,也叫做阻塞。而为什么会产生,上例就列举出来啦。
  然而数据库并没有出现无限等待的情况,是因为数据库搜索引擎会定期检测这种状况,一旦发现有情况,立马选择一个事务作为牺牲品。牺牲的事务,将会回滚数据。
  但是我们可以指定具体哪个事务作为牺牲品:
  语法:
  

set deadlock_priority  <级别>  

  死锁处理的优先级别为 low < normal < high,不指定的情况下默认为normal,牺牲品为随机。如果指定,牺牲品为级别低的。
  还可以使用数字来处理标识级别:-10 到 -5 为 low,-5 为 normal,-5 到 10 为 high。
  死锁耗时耗资源,然而在大型数据库中,高并发带来的死锁是不可避免的,尽管死锁不能完全避免,但遵守特定的编码惯例可以将发生死锁的机会降至最低。将死锁减至最少可以增加事务的吞吐量并减少系统开销,因为只有很少的事务:


  •   回滚,撤消事务执行的所有工作。

  •   由于死锁时回滚而由应用程序重新提交。

  下列方法有助于将死锁减至最少:
  1、按同一顺序访问数据库对象资源。
  2、避免事务中的用户交互,即事务中等待用户输入、提交等操作。
  3、保持事务简短并处于一个批处理中,在同一数据库中并发执行多个需要长时间运行的事务时通常会发生死锁。事务的运行时间越长,它持有排他锁或更新锁的时间也就越长,从而会阻塞其他活动并可能导致死锁。
  4、使用较低的隔离级别,确定事务是否能在较低的隔离级别上运行。实现已提交读允许事务读取另一个事务已读取(未修改)的数据,而不必等待第一个事务完成。使用较低的隔离级别(例如已提交读)比使用较高的隔离级别(例如可序列化)持有共享锁的时间更短。这样就减少了锁争用。
  5、尽可能使用分区表,分区视图,把数据放置在不同的磁盘和文件组中,分散访问保存在不同分区的数据,减少因为表中放置锁而造成的其它事务长时间等待。
  可参考:http://msdn.microsoft.com/zh-cn/library/ms191242(v=sql.105).aspx
  查看锁和事务活动情况:
  

  
select * from sys.dm_tran_locks
  

  
dbcc opentran
  

  可参考:http://msdn.microsoft.com/zh-cn/library/ms190345.aspx
  事务隔离级别:
  事物隔离级别,分为5种。就是并发事务对同一资源的读取深度层次。
  1、read uncommitted:这个隔离级别最低,可以读取到一个事务正在处理的数据,但事务还未提交,这种级别的读取叫做脏读。
  2、read committed:这个级别是默认选项,不能脏读,不能读取事务正在处理没有提交的数据,但能修改。
  3、repeatable read:不能读取事务正在处理的数据,也不能修改事务处理数据前的数据。
  4、snapshot:指定事务在开始的时候,就获得了已经提交数据的快照,因此当前事务只能看到事务开始之前对数据所做的修改。
  5、serializable:最高事务隔离级别,只能看到事务处理之前的数据。
  语法:
  

  
set tran isolation level <级别>
  

  read uncommitted 隔离级别的例子:
  

begin tran  
   set deadlock_priority low        
  

  
   update Student set C_S_Id='2' where S_StuNo='002'
  

  
   waitfor delay '0:0:5'        
  
rollback tran
  

  
select * from Student
  

  5秒内在另外一个查询窗口执行下面语句:
  

set tran isolation level read uncommitted  

  
   select * from Student        
  

  
   waitfor delay '0:0:5'        
  

  
select * from Student        
  

DSC0008.png

  read committed 隔离级别的例子:
  

begin tran  
   set deadlock_priority low        
  

  
   update Student set C_S_Id='2' where S_StuNo='002'        
  

  
   waitfor delay '0:0:5'        
  
rollback tran
  

  
select * from Student
  

  

set tran isolation level read committed  

  
   select * from Student        
  

  
   update Student set C_S_Id='5' where S_StuNo='002'        
  

  
   waitfor delay '0:0:5'        
  

  
select * from Student        
  

DSC0009.png

DSC00010.png

  设置锁超时时间:
  发生死锁的时候,数据库引擎会自动检测死锁,解决问题,然而这样子是很被动,只能在发生死锁后,等待处理。
  然而我们也可以主动出击,设置锁超时时间,一旦资源被锁定阻塞,超过设置的锁定时间,阻塞语句自动取消,释放资源,报1222错误。
  任何事情都具有两面性,调优的同时,也有他的不足之处,那就是一旦超过时间,语句取消,释放资源,但是当前报错事务,不会回滚,会造成数据错误,你需要在程序中捕获1222错误,用程序处理当前事务的逻辑,使数据正确。
  

  
select @@lock_timeout
  

  

  
set lock_timeout 0   
  

  之前从没接触过事务,今天跟着学习了一下,哇!!!事务还挺好理解的,不过事务并发死锁这类问题就需要实际经验了。所以我理解也不是太深刻,不过大家可以看下面这篇文章,我就是跟着学习的,个人觉得写的很不错。
  学习地址:
  http://www.cnblogs.com/knowledgesea/p/3714417.html

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-423758-1-1.html 上篇帖子: SQL Server 一些使用小技巧 下篇帖子: [转]SQL Server编程:SMO介绍
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表