事务与并发控制

    首先我们先了解下事务的定义,首先感谢各位数据库大家,本文中所有关于概念的解释,均为转载。本文主要目的就是粗浅的谈下事务与并发,不做深入讨论,这个纯属本人能力不行。

Java代码   收藏代码
  1.      事务是恢复和并发控制的基本单位。  
  2.   
  3.   事务具有ACID 属性: 即 Atomic 原子性, Consistent 一致性, Isolated 隔离性, Durable 永久性  
  4.   
  5.   原子性(atomicity )。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。    
  6.   
  7. 一致性(consistency )。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。    
  8.   
  9. 隔离性(isolation )。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。    
  10.   
  11.       持久性(durability )。 持续性也称永久性( permanence ),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。   
     事务是恢复和并发控制的基本单位。

  事务具有ACID 属性: 即 Atomic 原子性, Consistent 一致性, Isolated 隔离性, Durable 永久性

  原子性(atomicity )。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。  

一致性(consistency )。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。  

隔离性(isolation )。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。  

      持久性(durability )。 持续性也称永久性( permanence ),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。 

 

     事务一般可以分为2类:系统事务和用户定义事务

Java代码   收藏代码
  1. 系统事务又称为隐式事务,指某些特定的SQL语句由系统单独作为一个事务处理,例如:create、drop、insert、update和delete语句都是系统事务,相信这个大家都能够理解,没有谁遇到过建表只建了一半的情况吧  
系统事务又称为隐式事务,指某些特定的SQL语句由系统单独作为一个事务处理,例如:create、drop、insert、update和delete语句都是系统事务,相信这个大家都能够理解,没有谁遇到过建表只建了一半的情况吧

 

Java代码   收藏代码
  1. 用 户事务的定义方法:用BEGIN TRANSACTION语句指定一个事务的开始,用COMMIT或ROLLBACK语句表明一个事务的结束。注意必须明 确指定事务的结束,否则系统将把从事务开始到用户关闭连接之间所有的操作都作为一个事务来处理。相信自定义事务大家都用过,begin是开 始,commit是提交,rollback是回滚。  
用户事务的定义方法:用BEGIN TRANSACTION语句指定一个事务的开始,用COMMIT或ROLLBACK语句表明一个事务的结束。注意必须明确指定事务的结束,否则系统将把从事务开始到用户关闭连接之间所有的操作都作为一个事务来处理。相信自定义事务大家都用过,begin是开始,commit是提交,rollback是回滚。

 

      多事务并发时,可能会发生三类数据不一致的情况:数据丢失更新,读“脏”数据,不可重复读

Java代码   收藏代码
  1. 数据更新丢失,简单点说就是有 2 个事务T1和T2都修改字段A的值,A的初始值是 0 ,T1和T2最先读取到A的值是 0 ,它们同时对A加 1 ,结果理论上为 2 ,实际上为 1 会造成写冲突,这是由于两个事务并发的对同一数据写入引起的因此这种情况又称为写写冲突,如下图所示  
数据更新丢失,简单点说就是有2个事务T1和T2都修改字段A的值,A的初始值是0,T1和T2最先读取到A的值是0,它们同时对A加1,结果理论上为2,实际上为1会造成写冲突,这是由于两个事务并发的对同一数据写入引起的因此这种情况又称为写写冲突,如下图所示

Java代码   收藏代码
  1. 两个并发执行的事务T1,T2,T2先读得A的值,T1读得A的值,修改并写入,然后T2读得T1修改后的A的值,T1执行回滚操作,显然T2第二次读到的A的值是一个不存在的值,这是一个“脏”数据。读“脏”数据是由读-写冲突引起的。  
两个并发执行的事务T1,T2,T2先读得A的值,T1读得A的值,修改并写入,然后T2读得T1修改后的A的值,T1执行回滚操作,显然T2第二次读到的A的值是一个不存在的值,这是一个“脏”数据。读“脏”数据是由读-写冲突引起的。

Java代码   收藏代码
  1. ① 对于并发执行的两个事务T1,T2,当事务T1读取数据某一数据后,事务T2对该数据执行了更新操作,使得T1无法再次读取与前一次相同的结果,如图 7.4 所示,T1读数据A后,T2修改了数据A,T1再次读数据A,却得到不同的结果。  
  2. ② 事务T1按一定条件读取某些数据记录后,事务T2插入了一些记录,T1再次以相同条件读取记录时得到不同的结果集。  
  3. ③ 事务T1按一定条件读取某些数据记录后,事务T2删除了其中的一些记录,T1再次以相同条件读取记录时得到不同的结果集。  
  4. 后面两种情况又称为“幻像”读。不可重复读也是由读-写冲突引起的。  
① 对于并发执行的两个事务T1,T2,当事务T1读取数据某一数据后,事务T2对该数据执行了更新操作,使得T1无法再次读取与前一次相同的结果,如图7.4所示,T1读数据A后,T2修改了数据A,T1再次读数据A,却得到不同的结果。
② 事务T1按一定条件读取某些数据记录后,事务T2插入了一些记录,T1再次以相同条件读取记录时得到不同的结果集。
③ 事务T1按一定条件读取某些数据记录后,事务T2删除了其中的一些记录,T1再次以相同条件读取记录时得到不同的结果集。
后面两种情况又称为“幻像”读。不可重复读也是由读-写冲突引起的。

      事务并发会造成这么多困扰,那么有没有解决办法呢,也就是我即想兼顾效率,又想稳定的执行,数据库的开发者们给我们留了一种解决方式,那就是封锁机制。

Java代码   收藏代码
  1. 事务T在对某个数据对象(如表、记录等)操作之前,先向DBMS发出请求,申请对该数据对象加锁。当得到锁后,才可对该数据对象进行相应的操作,在事务T释放锁之前,其他事务不能更新此数据对象。  
事务T在对某个数据对象(如表、记录等)操作之前,先向DBMS发出请求,申请对该数据对象加锁。当得到锁后,才可对该数据对象进行相应的操作,在事务T释放锁之前,其他事务不能更新此数据对象。

 封锁类型主要分为:共享锁(S),排它锁(X),更新锁(U)和意向锁(这个个人感觉,没有到高级程序员一级基本用不到)

Java代码   收藏代码
  1. 共 享锁又称为读锁,一个事务T要读取数据对象A首先必须对A加共享锁,然后才能读A,一旦读取完毕,便释放A上的共享锁,除非将事务隔离级别设置为可重复读 或更高级别,或者在事务生存周期内用锁定提示保留共享锁。当一个数据对象上已存在共享锁时,其他事务可以读取数据,但不能修改数据。  
共享锁又称为读锁,一个事务T要读取数据对象A首先必须对A加共享锁,然后才能读A,一旦读取完毕,便释放A上的共享锁,除非将事务隔离级别设置为可重复读或更高级别,或者在事务生存周期内用锁定提示保留共享锁。当一个数据对象上已存在共享锁时,其他事务可以读取数据,但不能修改数据。
 
Java代码   收藏代码
  1. 排他锁又称为独占锁、写锁,一个事务T要更改数据对象A首先必须对A加排他锁,然后才能读或更改A,在T释放A上的排他锁之前,其他任何事务不能读取或更改A。  
排他锁又称为独占锁、写锁,一个事务T要更改数据对象A首先必须对A加排他锁,然后才能读或更改A,在T释放A上的排他锁之前,其他任何事务不能读取或更改A。

 

Java代码   收藏代码
  1. 当一个事务T对数据对象A加更新锁,首先对数据对象做更新锁锁定,这样数据将不能被修改,但可以读取,等到执行数据更新操作时,自动将更新锁转换为独占锁,但当对象上有其他锁存在时,无法对其作更新锁锁定。  
当一个事务T对数据对象A加更新锁,首先对数据对象做更新锁锁定,这样数据将不能被修改,但可以读取,等到执行数据更新操作时,自动将更新锁转换为独占锁,但当对象上有其他锁存在时,无法对其作更新锁锁定。

 

Java代码   收藏代码
  1. 意 向锁表示一个事务为了访问数据库对象层次结构中的某些底层资源(如表中的元组)而加共享锁或排他锁的意向。意向锁可以提高系统性能,因为DBMS仅在表级 检查意向锁就可确定事务是否可以安全地获取该表上的锁,而无须检查表中每个元组的锁来确定事务是否可以锁定整个表。意向锁包括意向共享(IS)、意向排他 (IX)及意向排他共享(SIX)。  
意向锁表示一个事务为了访问数据库对象层次结构中的某些底层资源(如表中的元组)而加共享锁或排他锁的意向。意向锁可以提高系统性能,因为DBMS仅在表级检查意向锁就可确定事务是否可以安全地获取该表上的锁,而无须检查表中每个元组的锁来确定事务是否可以锁定整个表。意向锁包括意向共享(IS)、意向排他(IX)及意向排他共享(SIX)。

 锁类型及其作用

锁之间的相容性

封锁粒度

Java代码   收藏代码
  1. 被 锁定的对象的数据量称为封锁粒度。封锁对象可以是逻辑单元,也可以是物理单元。以关系数据库为例,封锁对象可以是行、列、索引项、页、扩展盘区、表和数据 库等。封锁粒度不同,系统的开销将不同,并且锁定粒度与数据库访问并发度是矛盾的,锁定粒度大,系统开销小但并发度会降低,且对DBMS来说内部管理更简 单;锁定粒度小,系统开销大,但可提高并发度。选择封锁粒度时必须同时考虑开销和并发度两个因素,进行权衡,以求得最优的效果。一般原则为:  
  2. ●    需要处理大量元组的用户事务,以关系为封锁单元。  
  3. ●    需要处理多个关系的大量元组的用户事务,以数据库为封锁单元。  
  4. ●    只处理少量元组的用户事务,以元组为封锁单元。  
被锁定的对象的数据量称为封锁粒度。封锁对象可以是逻辑单元,也可以是物理单元。以关系数据库为例,封锁对象可以是行、列、索引项、页、扩展盘区、表和数据库等。封锁粒度不同,系统的开销将不同,并且锁定粒度与数据库访问并发度是矛盾的,锁定粒度大,系统开销小但并发度会降低,且对DBMS来说内部管理更简单;锁定粒度小,系统开销大,但可提高并发度。选择封锁粒度时必须同时考虑开销和并发度两个因素,进行权衡,以求得最优的效果。一般原则为:
●    需要处理大量元组的用户事务,以关系为封锁单元。
●    需要处理多个关系的大量元组的用户事务,以数据库为封锁单元。
●    只处理少量元组的用户事务,以元组为封锁单元。

 下面我们再来介绍另一个可以解决并发时出现问题的办法,那就是设定事务的隔离级别,它定义了事务并发执行时,事务之间的隔离程度,在标准的SQL中定义了4个级别

Java代码   收藏代码
  1. 未授权读取(Read Uncommitted):允许读“脏”数据,但不允许更新丢失。如果一个事务已经开始写数据,则允许其他事务读此数据,但不允许同时进行写操作。  
未授权读取(Read Uncommitted):允许读“脏”数据,但不允许更新丢失。如果一个事务已经开始写数据,则允许其他事务读此数据,但不允许同时进行写操作。

 

Java代码   收藏代码
  1. 授 权读取(Read Committed):读取数据的事务允许其他并行事务访问该数据,但是未提交的写事务将禁止其他事务同时访问该数据。这是大多数主流 数据库的默认事务隔离等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了读“脏”数据。该级别适用于大多数系统。  
授权读取(Read Committed):读取数据的事务允许其他并行事务访问该数据,但是未提交的写事务将禁止其他事务同时访问该数据。这是大多数主流数据库的默认事务隔离等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了读“脏”数据。该级别适用于大多数系统。

 

Java代码   收藏代码
  1. 可 重复读(Repeatable Read):禁止不可重复读和读“脏”数据,但有时可能出现“幻像”数据,读取数据的事务将会禁止写事务(但允许读事 务),写事务则禁止任何其他事务,这保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了读“脏”数据和“不可重复读”的情 况。  
可重复读(Repeatable Read):禁止不可重复读和读“脏”数据,但有时可能出现“幻像”数据,读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务,这保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了读“脏”数据和“不可重复读”的情况。

 

Java代码   收藏代码
  1. 序列化(Serializable):提供严格的事务隔离,它要求事务序列化执行,即事务只能一个接着一个地执行。  
序列化(Serializable):提供严格的事务隔离,它要求事务序列化执行,即事务只能一个接着一个地执行。

      事务的隔离级别越高,就越能够保证数据的一致性,但是有利就有弊,级别越高相应的影响性能就约大,目前像我是从事银行开发,一般我们考虑性能和安全性的平 衡点我们会将隔离级别设置为Read Committed,它能避免读取“脏”数据,而且几乎对性能没有影响。

       既然用到了锁机制,那么必不可少的就会出现死锁的情况,解决死锁

Java代码   收藏代码
  1. 如果两个或多个事务每个都持有另一事务所需资源上的锁,没有这些资源,每个事务都无法继续完成其工作,这种情况称为死锁。  
如果两个或多个事务每个都持有另一事务所需资源上的锁,没有这些资源,每个事务都无法继续完成其工作,这种情况称为死锁。

       目前对于死锁没有什么太好的办法,无非2种,一是预防,二是出现死锁后解锁

       那么如何预防死锁呢

Java代码   收藏代码
  1. 1 )一次封锁法  
  2. 要 求每个事务必须一次将所有要访问的数据对象全部加锁,否则就不能继续执行。该方法可有效地预防死锁,但存在的问题是:一次就将以后要访问的全部数据对象加 锁,扩大了封锁的范围,从而降低了系统的并发度。另外,数据库中的数据是不断变化的,很难事先准确地确定每个事务所要封锁的数据对象。  
  3. 2 )顺序封锁法  
  4. 预 先对数据对象规定一个封锁顺序,所有事务都按这个顺序实行封锁。该方法可以有效地预防死锁,但存在的问题是: 数据库系统中可封锁的数据对象极多,并且随 着数据的插入、删除等操作而不断变化,要维护这些数据对象的封锁顺序非常困难,成本很高。此外,事务的封锁请求是随着事务的执行动态决定的,很难事先确定 每个事务要封锁哪些对象,这样,就很难按规定的顺序封锁对象。  
(1)一次封锁法
要求每个事务必须一次将所有要访问的数据对象全部加锁,否则就不能继续执行。该方法可有效地预防死锁,但存在的问题是:一次就将以后要访问的全部数据对象加锁,扩大了封锁的范围,从而降低了系统的并发度。另外,数据库中的数据是不断变化的,很难事先准确地确定每个事务所要封锁的数据对象。
(2)顺序封锁法
预先对数据对象规定一个封锁顺序,所有事务都按这个顺序实行封锁。该方法可以有效地预防死锁,但存在的问题是: 数据库系统中可封锁的数据对象极多,并且随着数据的插入、删除等操作而不断变化,要维护这些数据对象的封锁顺序非常困难,成本很高。此外,事务的封锁请求是随着事务的执行动态决定的,很难事先确定每个事务要封锁哪些对象,这样,就很难按规定的顺序封锁对象。

       上述2种方法其实都不适用与并发,所以大多数情况还是通过解锁,来解决问题。

        这就有涉及两个问题,如何判断死锁和如何解决死锁

Java代码   收藏代码
  1. 1 )超时法  
  2. 如果一个事务的等待时间超过了规定的时限,就认为发生了死锁。这种方法实现简单,但存在两个问题:一是可能误判死锁,如果事务是由于其他原因而使等待时间长,系统会认为是发生了死锁;二是时限的设置问题,若时限设置得太长,可能导致死锁发生后不能及时发现。  
  3. 2 )等待图法  
  4. 等待图法是动态地根据并发事务之间的资源等待关系构造一个有向图,并发控制子系统周期性地检测该有向图是否出现环路,若有,则说明出现了死锁。  
(1)超时法
如果一个事务的等待时间超过了规定的时限,就认为发生了死锁。这种方法实现简单,但存在两个问题:一是可能误判死锁,如果事务是由于其他原因而使等待时间长,系统会认为是发生了死锁;二是时限的设置问题,若时限设置得太长,可能导致死锁发生后不能及时发现。
(2)等待图法
等待图法是动态地根据并发事务之间的资源等待关系构造一个有向图,并发控制子系统周期性地检测该有向图是否出现环路,若有,则说明出现了死锁。
 
Java代码   收藏代码

DBMS的并发控制子系统一旦检测到系统存在死锁,就要设法解除,通常采用的方法是选择一个处理死锁代价最小的事务,将其撤销,释放该事务持有的所有的锁,使其他事务能继续运行

转载自http://512zw.iteye.com/blog/1017309

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值