先来回忆一下事务的基本属性(ACID):
- A,原子性:一个事务中的 *** 作要么全部成功,要么都不成功
- C,一致性:事务执行前后,整个数据系统必须保证数据完整性(符合预设的规则以及可以继续运行)
- I,隔离性:多个事务同时执行,不影响数据系统的一致性。为了兼顾效率与一致性,一般将隔离级别定四档:读未提交、读已提交、可重复度、串行化
- D,持久性:事务一旦提交,对数据的更改永久有效。
下面详细探讨一下四档隔离级别
- 读未提交:事务A在执行中的修改,其他运行中的事务立即可见。可以理解为:读未提交隔离级别下的数据更新生效时机是更新语句执行后。
- 读已提交:事务A在执行中的修改,其他运行中的事务不可见;当事务A提交后,其他运行中的事务立即可见事务A的修改。可以理解为:读已提交隔离级别下的数据更新生效时机是在事务提交后。
- 可重复读:事务A在执行中的修改,其他运行中的事务完全不可见。可以理解为:可重复度隔离级别下,事务在开启时会生成一个当前状态的数据视图(这个视图并不是数据库中CREATE VIEW <视图名> AS
- 串行化:采用加锁的方式,按照事务开启时间,依次执行,无论读写均不会影响其他事务,但比较浪费资源,较少使用。
下面再介绍一下四种隔离级别下的事务问题:
- 脏读:在读未提交下,事务A与事务B同时运行,事务B先将a从1改为了2,事务A读a值时,发现a=2,然后事务B执行时发生异常,事务回滚;事务A读取的a值就是无效数据。
读已提交可以解决脏读问题。 - 不可重复读:在读已提交下,事务A和事务B同时运行,事务A首先读取a值发现a=1,然后事务B修改a=2且立即提交,当事务A再次读取a值时,发现a与上次读取的值不一致,在一次事务执行期间多次读取一条记录得到的结果不一致。
可重复读隔离级别下,能解决不可重复读问题。 - 幻读:同一个事务两次读取同一张表的记录,第二次读取出了第一次没有的数据。举例:当我需要向表T中插入id=1的记录时,先查询一下表T中是否存在id=1的记录,发现没有。执行插入id=1记录时,却报主键冲突异常。这是因为第一次执行的是快照查询,第二次插入的时候也执行了一次查询,不过这次是写查询(写查询永远获取最新的数据,快照查询是读取快照(即上面介绍四档隔离级别第三条可重复读级别提到的视图)创建时的数据)。
强烈推荐下面这篇文章,非常清晰的讲述了什么是幻读
https://zhuanlan.zhihu.com/p/103580034
幻读与不可重复读的一个关键区别就是:幻读涉及到了当前读与快照读
再谈谈数据库是如何实现这四种隔离级别的呢
对于读未提交和串行化,显而易见他们的实现方式:读未提交只需读取当前数据库最新值即可;串行化依次执行事务,不会产生读写冲突。
对于读已提交和可重复读,就涉及到MVCC(多版本并发控制)
mysql每执行一条update语句,都会维护一条回滚数据。类似下图:
多版本并发控制MVCC当某个事务需要使用read-view A时,MySQL会从当前值4开始,使用三条回滚日志让数据回到read-view A。
回滚日志当然不会一直保留,当系统里没有比这条回滚日志更早的read-view时,该条回滚日志及之前的回滚日志都会被清除。
这给我们的启发是:**对于频繁更新数据的表,不要使用长事务。**长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占用存储空间。
除了对回滚段的影响,长事务还占用锁资源,也可能拖垮整个库,后续在深入mysql锁的时候再向大家分享。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)