一、隔离性与隔离级别
隔离性:一个事务正在操作的数据应该锁起来,阻塞其他事务修改。
隔离级别:描述事务隔离性的程度。隔离级别越高,隔离性就越好,性能就越差。
二、并发事务的类型
并发事务即多个事务同时执行,而在事务间执行操作的方面可以分为三种
- 读-读(一个事务在执行select,另一个事务也在执行select)
- 读-写(一个事务在执行select,另一个事务执行增删改操作)
- 写-写(一个事务在执行增删改,另一个事务也在执行增删改)
当然在多个事务执行的过程中,既可能出现读-写又可能出现写-写。
在上面读-读类型不会产生问题,其他两个会产生问题。
1.读-写事务、隔离级别、MVCC
读-写事务可能产生的问题
如果同时操作共享数据,可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,为了解决这些问题,就有了“隔离级别”的概念。
4个隔离级别的描述及问题
读未提交:读取另一个事务未提交的数据,出现脏读、不可重复读、幻读
读已提交:读取另一个事务已提交的数据,出现不可重复读、幻读
可重复读:保证在同一个事务中多次读取统一数据结果是一样的,出现幻读
可串行化:强制事务串行执行,多个事务互不打扰,不会出现并发一致性问题
前面说过隔离级别越高,隔离性就越好,并发性能越差。比如可串行化,多个事务间串行执行,已经不存在并发了,但是多个事务间可能不存在同时操作共享资源的情况,造成性能的浪费。因此从实际应用角度考虑,MySQL官方推荐的默认隔离级别是可重复读。
如何实现可重复读呢?——MVCC
MVCC即多版本控制协议,InnoDB实现了MVCC作版本控制,也就是实现了读已提交和可重复读的隔离级别。
实现的核心思想是防止不该被事务看到的数据(例如还没提交的事务修改的数据)被看到。在InnoDB中,主要是通过使用readview的技术来实现判断。查询出来的每一行记录,都会用readview来判断一下当前这行是否可以被当前事务看到,如果可以,则输出,否则就利用undolog来构建历史版本,再进行判断,知道记录构建到最老的版本或者可见性条件满足。
上面这段话是对MVCC最本质的理解,在下文会描述具体从代码级别的实现。
2.写-写事务与锁
写写事务出现的问题举例如下,一个事务修改了另一个事务修改的数据,并且另一个事务还没有提交,这就导致了数据更新丢失的问题。
怎么去解决呢?就是给共享数据加锁,即在一个事务修改数据前要先获得锁,进而操作数据,等事务提交后释放锁下一个事务才能操作。关于锁的内容后面也会介绍。
三、总结
对于数据库的并发事务类型下一共三种情况,分别是读-读、读-写、写-写。把上面阐述的总结成下表。
并发事务类型 | 场景释义 | 出现问题 | 问题详述 | 解决办法 | 解决办法释义 |
读-读 | 两个事务都在读相同行数据 | 不出现问题 | |||
读-写 | 一个事务在读,一个事务在写相同行数据 | 事务隔离性问题 | 脏读、不可重复读、幻读 | MVCC | 判断事务对一行数据是否可见 |
写-写 | 两个事务都在写相同行数据 | 更新丢失数据问题 | 第一类更新丢失 | ||
第二类更新丢失 | 读写锁 | 对共享资源数据上锁,InnoDB采用的是悲观锁 |
总结上表中描述的,InnoDB通过用MVCC解决了读的问题,实现了可重复读的隔离级别。读写锁解决了写的问题。MVCC+读写锁的方式实现了并发事务的隔离性。