什么是事务?
事务是对数据库的一系列操作,有明确的开始和结束标志,且要被完全执行,要么全部成功,要么全部失败。
为什么要使用事务?
为了保证我们业务的完整性,因为我们的业务就是一个序列操作,具备这种要么全部成功,要么全部失败的特性。
事务的特征
事务具备如下四大特征【ACID】
- 原子性(Atomicity)
事务最终状态只有两种,全部执行成功和全部不执行,事务中任何一项操作失败,都会导致整个事务失败。一旦失败所有操作都会被取消【回滚】,就像事务从来没有执行过一样。 - 一致性(Consistency)
事务执行前后,数据的完整性保持一致。 - 隔离性(Isolation)
多个事务并发执行时,多个事务之间互不干扰。即一个事务内部的操作及使用的数据,对其他并发事务是隔离的。 - 持久性(Durability)
事务结束后,对数据库所作的更改被永久保存下来了,即使发生系统故障或宕机,只要数据库能够恢复到被访问,那么一定能够被恢复到事务完成时的状态。
隔离级别
当数据库上有多个事务同时执行的时候,就可能出现脏读,不可重复读和幻读的问题,因此就有了“隔离级别”的概念。
首先要知道,隔离得越严实,效率就越低。因此很多时候,要在这二者之间寻求一个平衡点。SQL标准的事务隔离级别包括如下几种。
- 读未提交(read uncommitted)
一个事务还没提交,此时它所做的变更能被别的事务看到。此隔离级别会引发 脏读 的问题 - 读提交(read committed)
一个事务提交之后,它做的变更才会被其他事务看到。此隔离级别会引发 不可重复读 的问题。 - 可重复读(repeatable read)
一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。此隔离级别下会引发 幻读 的问题。 - 串行化(serializable)
对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后序访问的事务必须等前一个事务执行完成,才能继续执行。
Oracle数据库的默认隔离级别是 “读提交” 而 MySQL 的默认隔离级别是 “可重复读”,因此对于一些从Oracle迁移到MySQL的应用,为保证数据库隔离级别一致,一定要记得修改隔离级别为“读提交”。
脏读,不可重复读和幻读都是什么?
- 脏读
脏读就是拿了别的事务还没提交的数据在此基础上做了别的业务处理。 - 不可重复读
在一个事务中,以相同的查询条件检索同一条数据,且当前事务没对此数据做任何修改,结果两次检索出的数据却不一致,说明有别的事务修改了此数据。 - 幻读
在一个事务中,以某个条件检索出了一批数据,假设检索出了五条数据集,批量修改此数据集,预期是返回修改五条记录。结果发现修改了六条,说明有别的事务插入了满足此检索条件的数据集。
这里对幻读的描述只是其中的一个场景,实际上不同事物 update,insert,delete 到同一批数据,都会出现幻读。原因在于“这些操作都是当前读的”。
事务隔离的实现
在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。在 MySQL 中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。
同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)
- “可重复读”隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。
- “读提交”隔离级别下,这个视图是在每个 SQL 语句开始执行的时候创建的。这里需要注意的是。
- “读未提交”隔离级别下直接返回记录上的最新值,没有视图概念。
- “串行化”隔离级别下直接用加锁的方式来避免并行访问。
福利来啦
事务这一块的东西非常非常重要,楼主在大四出来实习的时候,基本去哪面试都有被问过,有幸读过一篇阅读量上万的事务总结,非常的受用,可是随着学习的积累,楼主发现那篇文章描述的知识点有一些错误之处【说明了学习时找对学习资源的重要性】,这个错误楼主不止在那篇文章中看到过,在别的地方也见到过,所以在这里做个记录,希望能帮到有缘人。
- 情景再现
部分博客对幻读的描述是这样的:幻读发生在当两个完全相同的查询执行时,第二次查询所返回的结果集跟第一个查询不相同。
经楼主查证,其实这种幻读【BUG】,已经被 MVCC 机制修复了。只是比较老的书都已经发版了也没法改,新发行的书中基本都有提到这一点。为了增加说服力,以图做镇,以示清白!