一、事务定义
事务是一个不可分割的数据库操作序列,也是数据库并发操作的基本单位。其执行结果必须使数据库从一种一致性状态变成另一种一致性状态。
二、事务的目的
- 恢复:为数据库操作提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即使在异常状态下仍能保持一致性的方法。
- 隔离:当多个应用程序在并发访问数据库时,可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰。
三、特性
- 原子性(Atomicity):事务所包含一系列数据库操作要么全部执行成功,要么回滚。
- 一致性(Consistency):事务的执行结果必须使数据库从一个一致性状态到另一个一致性状态
- 隔离性(Isolation):并发执行的事务之间不能相互影响
- 持久性(Durability):事务一旦提交,对数据库中数据的改变是永久的
四、隔离级别
ANSI标准定义了4个隔离级别,MySQL的InnoDB都支持 - read uncommitted(读取到未提交数据)
解释:我们将事务隔离级别设置为read uncommitted,即便是事务没有commit,但是我们仍然能读到未提交的数据,这是所有隔离级别中最低的一种。
存在的问题:那就是我们在一个事务中可以随随便便读取到其他事务未提交的数据,这还是比较麻烦的,我们叫脏读。
- read committed(可以读取其他事务提交的数据)
解释:当我们将当前会话的隔离级别设置为read committed的时候,当前会话只能读取到其他事务提交的数据,未提交的数据读不到。
存在问题:那就是我们在会话B同一个事务中,读取到两次不同的结果。这就造成了不可重复读,就是两次读取的结果不同。这种现象叫不可重复读。
- repeatable read(可重读)—MySQL默认的隔离级别
解释:在一个事务开始后,其他事务对数据库的修改在本事务中不可见,直到本事务commit或rollback。
存在的问题:其他事务的insert/delete操作对该事务是可见的,也就是说,该隔离级别并不能避免幻读问题。在一个事务中重复select的结果一样,除非本事务中update数据库。
用户A开启一个事务,第一次查询之后,得到一个结果,此时并没有提交事务
用户B开启一个事务,添加一条记录,然后提交事务。
用户A再次查询,发现和上次查询的结果不一样,多了一条记录(幻读)。
也就是加了行级锁,只会对当前作用的行进行加锁,不会对整个表加锁,因此可以添加、删除,所以会出现幻读现象。 - serializable(串行化)
当我们将当前会话的隔离级别设置为serializable的时候,其他会话对该表的写操作将被挂起。可以看到,这是隔离级别中最严格的,但是这样做势必对性能造成影响。所以在实际的选用上,我们要根据当前具体的情况选用合适的。
并发控制主要采用的技术:乐观并发控制(乐观锁)和悲观并发控制(悲观锁)
五、数据库的乐观锁
- 悲观锁先获取锁,再进行业务操作,即“悲观”的认为所有的操作都会导致并发问题
select status from t_goods where id=1 for update; ——-select … for update - 流程:
在对任意记录进行修改前,先尝试为该记录加排他锁,如果加锁失败,说明记录正在被修改,那么当前查询可能要等待或者抛出异常等;如果加锁成功,那么就可以对记录进行修改,事务完成后就会解锁了。期间如果有其他对该记录做修改或加排他锁的操作,都会得锁释放 - 优缺点:
悲观并发控制实际上是“先取锁再访问”的保守策略,为①数据处理的安全提供了保证。但是②在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会;另外,在只读型事务处理中由于不会产生冲突,也没必要使用锁,这样做只能增加系统负载;还有③会降低了并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数六 、乐观锁
- 特点:先进行业务操作,只在最后实际更新数据时进行检查数据是否被更新过,若未被更新过,则更新成功;否则,失败(回滚)重试。乐观锁在数据库上的实现完全是逻辑的(即不用数据库提供的锁机制),不需要数据库提供特殊的支持。
- 一般的实现乐观锁的方式就是记录数据版本
数据版本:为数据增加的一个版本标识。当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本标识进行更新。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,如果数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,否则认为是过期数据。
- 优缺点:
乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。但如果直接简单这么做,还是有可能会遇到不可预期的结果,例如两个事务都读取了数据库的某一行,经过修改以后写回数据库,这时就遇到了问题。
七、悲观锁与乐观锁的应用场景
一般情况下,读多写少更适合用乐观锁,读少写多更适合用悲观锁。乐观锁在不发生取锁失败的情况下开销比悲观锁小,但是一旦发生失败回滚开销则比较大,因此适合用在取锁失败概率比较小的场景,可以提升系统并发性能。