(八)事务

一、定义

事务是一个不可分割的数据库操作序列,也是数据库并发操作的基本单位。其执行结果必须使数据库从一种一致性状态变成另一种一致性状态。

二、事务的特征ACID

  • 原子性(Atomicity):事务所包含一系列数据库操作要么全部执行成功,要么回滚。
  • 一致性(Consistency):事务的执行结果必须使数据库从一个一致性状态到另一个一致性状态
  • 隔离性(Isolation):并发执行的事务之间不能相互影响
  • 持久性(Durability):事务一旦提交,对数据库中数据的改变是永久的

三、事务并发带来的问题

  • 脏读(无效数据的读出):一个事务读取了另一个事务未提交的数据
  • 是指在数据库访问中,事务T1将某一个值修改(并没有提交),T2读取该值,此后T1因为某种原因撤销对该值的修改,这就导致了T2所读取的数据是无效的。

  • 不可重复读:(重点在修改)
  • 事务T1读到了一条数据,事务T2把这条数据改变了,然后事务T1再读时候,发现数据不匹配

  • 幻读:(重点在新增或删除)
  • 两次读出来的记录数不一样。

    事务T1根据条件检索到N条数据,然后事务T2新增了M条符合T1筛选条件的数据,导致事务T1再次搜索发现有N+M条数据,产生幻读

四、事务隔离级别

  • Read uncommitted:最低级别它允许一个事务读取另一个事务还没提交的数据,这样会提高性能,但是会导致脏读等问题
  • Read commit:正在读的事务,必然是其他事务已经提交的事务(不会再读到其他事务的中间值)
  • 会有不可重复读问题

  • Repeatable read(默认):在一个事务开始后,其他事务对数据库的修改在本事务中不可见,直到本事务commit或rollback。但是,其他事务的insert/delete操作对该事务是可见的,也就是说,该隔离级别并不能避免幻读问题。在一个事务中重复select的结果一样,除非本事务中update数据库。
  • Serializable:其他会话会对该表的写操作挂起

五、数据库的乐观锁和悲观锁

并发控制主要采用的技术:乐观并发控制(乐观锁)和悲观并发控制(悲观锁)

乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。那么我们如何实现乐观锁呢,一般来说有以下2种方式:

  1.使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据,用下图说明:

或者:

  • 取出记录时,获取当前version
  • 更新时,带上这个version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败

如果更新操作顺序执行,则数据的版本(version)依次递增,不会产生冲突。但是如果发生有不同的业务操作对同一版本的数据进行修改,那么,先提交的操作(图中B)会把数据version更新为2,当A在B之后提交更新时发现数据的version已经被修改了,那么A的更新操作会失败。

2.乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。

 

 悲观锁:

先获取锁,再进行业务操作,即“悲观”的认为所有的操作都会导致并发问题

select status from t_goods where id=1 for update;  -------select ... for update

在数据库中,悲观锁的流程如下:

(注意:所有操作都要加锁。若是修改,则要先获得锁;若是查询,也要先获得锁,以免别人正在修改)

  • 在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)
  • 查询时,如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常
  • 如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了
  • 其间如果有其他对该记录做修改或加排他锁的操作,都会等待我们解锁或直接抛出异常

悲观锁有两种,读锁和写锁

读锁、写锁实例参考:https://blog.csdn.net/heronos/article/details/77896050

读锁:也叫共享锁,可以多事务加共享锁,但是只能读,也杜绝了自己和别人修改

写锁:也叫排他锁,不共享数据对象上锁权,锁只能一个人用,其他事务不能修改、读取

 

悲观锁优点:

  • 悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。
  • 悲观锁是基于DB层面实现,对业务代码无入侵,使用方便

悲观锁缺点:

  • 在效率方面,处理加锁的机制会让数据库产生额外的开销,另外,在只读型事务处理中由于不会产生冲突,也没必要使用锁,这样做只能增加系统负载;
  • 会降低了并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数

 

悲观锁与乐观锁的应用场景

   一般情况下,读多写少更适合用乐观锁,读少写多更适合用悲观锁。乐观锁在不发生取锁失败的情况下开销比悲观锁小,但是一旦发生失败回滚开销则比较大,因此适合用在取锁失败概率比较小的场景,可以提升系统并发性能。

posted @ 2019-07-13 22:04  测试开发分享站  阅读(146)  评论(0编辑  收藏  举报