以下仅为自己整理后的理解,如有不当之处还请指正。
出现背景
在程序中有多线程并发,事务亦如此。为解决事务并发时不同事务间的数据混乱,以及应对不同的场景需要,出现了事务隔离级别。不同的隔离级别对应不同的事务并发下的安全情况,同时也有着不同的性能和问题,需要我们在实际使用时,在利与弊间权衡适合的方式。
事务特性:ACID
- | 特性 | 英文原文 | 实现方式 | 简介 |
---|---|---|---|---|
A | 原子性 | Atomicity | undolog | 事务的最小单元,不可再分 |
C | 一致性 | Consistency | binlog、redolog | 当前事务中的多条语句同时成功或失败 |
I | 隔离性 | Isolation | 读写锁+MVCC | 两事务之间具有隔离,在一定程度上互不干扰 |
D | 持久性 | Durability | ACI的共同努力 | 数据必须最终持久化到硬盘等媒介,事务才算成功完成 |
其中原子性、一致性、持久性是数据库存储的最基本要素,我们在实际应用中,主要是对隔离性进行选择,在性能与可靠性之间权衡。
概述
隔离级别 | 英文 | 简写 | 简述 |
---|---|---|---|
读未提交 | Read uncommitted | RU | 能看到其它事务的变更 |
读已提交 | Read committed | RC | 其它事务提交后,自己才能看到 |
可重读 | Repeatable read | RR | 在整个事务生命周期内,即使数据被别的事务修改并提交了,看到的数据都是启动时的样子 |
串行1化 | Serializable | S | 同一时刻只允许一个事务执行。读会加读锁,写会加写锁,当读写锁冲突时必须等待先创建的事务执行完成。 |
在数据操作过程中利用数据库的锁机制或MVCC,获取更高的隔离级别。但随着隔离级别的提升,并发能力随之下降,所以在选择隔离级别时需要权衡取舍。
疑问:
- 串行化时,读写锁、写写锁冲突都是单一并发,读读锁冲突呢?是否可以并发?
一个耗时较长的事务,在最后才可能执行写入,那么在这中间的读操作会锁住吗?- 可重读解决了前后不一致问题,可实际中应该只需要关注最新数据吧,哪些时候会要求前后数据要一致,哪怕读到的是过期数据?
下面是隔离级别及对应的问题一览表:
图例:Y表示有此问题,N表示无此问题
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 | Y | Y | Y |
读已提交 | N | Y | Y |
可重读 | N | N | Y |
串行化 | N | N | N |
可以看到每提升一个级别,问题就少了一些,但对应的性能与并发也受影响下降了一些,在开发中需要权衡使用。
读未提交 RU
其它事务的修改还未提交,就可读取到其结果。相当于隔离级别的裸奔
优点是不需要处理事物隔离,效率高,并发也高。
缺点是有 脏读 等问题。
读已提交 RC
其它事物的修改,必须是已提交成功状态,才能在当前事务中读取到。因此也是对读未提交下脏读问题的解决。
这也是多数数据库软件的默认隔离级别,比如Oracle。
但该级别下会出现当前事务前后两次读取中间,另一事务修改并提交,导致当前事务两次读取结果不一致的问题,称之为 不可重复读 ,解决方法是升级到可重读。
可重读 RR
当前事务前后两次或多次读取之间,即使其它事务修改并提交了,当前事务读取到前后结果仍然是一致的(最早一次的结果)。
需要注意的是,该结果仅是某条已存在数据的修改,如果是另一事务插入了数据,当前事务的count等聚合函数查询仍会出现前后结果不一致问题。
MySQL默认隔离级别即为可重读。而实现这种效果的,主要是MVCC的快照读。
虽然它使得前后读取的数据一样,但当另一事务插入数据并提交后,count等聚合类查询仍会前后不一致,称之为 幻读。
解决方法是升级到串行化,以加上排它锁使其它事务不能操作对应数据,但也会造成性能的严重下降。
串行化
顾名思义,事务开启后会上读写锁,该时间段内只有自己能读写锁定范围内的数据。
脏读、不可重复读、幻读
说完了隔离级别,看到里面讲到了三个问题,这里再单独拿出来说明一下
脏读
当前事务读到了另一个事务中的修改结果,但另一个事务还未提交。在读未提交级别下会出现该问题。
不可重复读
假设当前id=1的数据值为100,两个线程都开启事务
操作序号 | 事务A | 事务B |
---|---|---|
1 | 获取数据:100 | |
2 | 修改为60 | |
3 | 获取数据:100 | |
4 | 提交事务 | |
5 | 获取结果60 |
事务B在整个事务期间,受事务A影响,步骤5、7两次获取数据,结果却不一样。
这里有个疑问(在概述章节开头表格下,也有提到):
理论来说,我们应该关注最新值是什么,而对于历史数据并不关心。所以即使前后两次结果不一样,取最后的最新值即可,为什么还要用第一次取到的那个过期值呢???欢迎留言讨论
幻读
幻读针对的是新数据的插入, 脏读、不可重复读,都是针对现有数据的修改。
幻读是指事务A先count一下数量为100,事务B插入了20条数据,此时事务A再次查询得到120。
但对于期间一些值改变,不再符合统计条件,并不会导致前后count结果不一致。
引用参考
扩展
在整理时涉及的一些相关知识点,做一个简要记录,方便阅览与回忆
MVCC
多版本并发控制(Multi-Version Concurrency Control),由数据库软件实现事务内存,用来解决多事务并发时读写冲突,线程不用争抢读写锁,避免因加锁导致的性能下降。
此外,还需要了解两个概念:当前读、快照读
当前读
是一种悲观锁的操作,他会对当前的读取操作加锁,以保证读取到的数据都是最新的。
主要有以下集中加锁操作
- select lock in share mode(共享锁)
- select for update(排他锁)
- update(排他锁)
- insert(排他锁)
- delete(排他锁)
快照读
MVCC所提到的读是快照读,也就是普通的select语句不加锁。
mysql单表设计中,除了我们设计的字段,还会自动加两个隐藏字段 trx_id 和 roll_pointer。
- trx_id:事物id。没进行一次事物操作,就会自增1。
- roll_pointer:回滚指针,用于找到上一个版本的数据,结合undolog回滚。
串行化的行读音:xíng ↩︎