一. 并发场景下的问题
相对于串行处理方式,并发的事务处理可显著提升数据库的事务吞吐量、提高资源利用率。在MySQL实际应用中,根据场景的不同,可以分为以下几类:
- 读读并发
- 读写并发
- 写写并发
在这些场景下,可能会出现更新丢失、脏读、不可重复度、幻读的问题。
- 更新丢失:当多个事务同时更新某1/n行数据时,最后提交的事务会将之前提交的更新覆盖。
- 脏读:一个事务正在插入/更新一行数据,在该事务提交之前,这条数据处于“不一致”状态。其他事务读取到这条“脏数据”并据此做进一步处理,就会产生对未提交数据的依赖关系,这种现象称为脏读。
- 不可重复读:一个事务在查询某条数据的一定时间后再次查询该数据,却发现该条数据已经发生了更新或被删除,这种现象称为不可重复读。
- 幻读:一个事务以相同的查询条件先后两次查询数据,第二次查询结果出现了第一次查询没有的新数据,这种情况称为幻读。
其中,在各类并发场景下会出现的问题如下:
- 读读并发场景不会导致数据不一致问题,因此无需特殊处理。
- 读写并发场景可能会出现脏读、不可重复读、幻读的问题。
- 写写并发场景可能会出现更新丢失的问题。
二.事务隔离级别
针对上述描述的问题,MySQL采用不同的事务隔离级别分别用于解决上述的部分问题,具体如下:
在上述隔离级别中,从上到下并发性能依次降低,安全性依次提高。InnoDB存储引擎下默认的事务隔离级别是RR,可通过如下SQL查询事务隔离级别。
select @@global.tx_isolation;
三.事务隔离级别的实现
InnoDB对事务隔离级别的实现,基本可分为如下两种或两种的组合:
1.多版本并发控制(MVCC,Multiversion Currency Control)
MVCC可用来解决读写并发场景下的脏读、不可重复读问题。具体在不同隔离级别下的应用如下:
- RC:可解决脏读问题。
- RR:可解决脏读、不可重复读问题。
此处做简单补充:
相较于RC,RR之所以能够解决不可重复读问题,原因在于RR隔离级别下读取Read-View是事务级别的,RC是语句级别的。即:RR只在事务开始时读取一次Read-View,因此一个事务中的多次查询依赖同一个Read-View,能实现可重复读。RC级别下每次查询均读取 最新的Read-View,无法保证同一事务中先后读取到的Read-View是相同的,这也是先后读取到的数据有差异的原因。
2.锁
锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据了而言显得尤为重要。
锁可以解决上述描述读写并发场景下的幻读问题、写写并发场景下的更新丢失问题。下面针对锁、锁的类型及其应用场景展开详细描述。
死锁发生场景:
- 事务1,2均获取某一行数据的s锁后,均想获取x锁。
- 事务1,2均获取到某一间隙锁后,希望进行插入操作,即增加插入意向锁。
- 事务1,2操作同一条数据。由于检索条件不同,事务1,2分别锁定了聚簇索引和二级索引,然后事务1无法操作二级索引(先删除,再插入),事务2无法操作聚簇索引,发生死锁。