透过“锁”事看InnoDB对并发的处理?
共 2358字,需浏览 5分钟
·
2021-05-28 10:59
点击上方蓝色字体,选择“标星公众号”
优质文章,第一时间送达
作者 | 机械公敌
来源 | urlify.cn/QneMza
一. 并发场景下的问题
相对于串行处理方式,并发的事务处理可显著提升数据库的事务吞吐量、提高资源利用率。在MySQL实际应用中,根据场景的不同,可以分为以下几类:
读读并发
读写并发
写写并发
在这些场景下,可能会出现更新丢失、脏读、不可重复度、幻读的问题。
更新丢失:当多个事务同时更新某1/n行数据时,最后提交的事务会将之前提交的更新覆盖。
脏读:一个事务正在插入/更新一行数据,在该事务提交之前,这条数据处于“不一致”状态。其他事务读取到这条“脏数据”并据此做进一步处理,就会产生对未提交数据的依赖关系,这种现象称为脏读。
不可重复读:一个事务在查询某条数据的一定时间后再次查询该数据,却发现该条数据已经发生了更新或被删除,这种现象称为不可重复读。
幻读:一个事务以相同的查询条件先后两次查询数据,第二次查询结果出现了第一次查询没有的新数据,这种情况称为幻读。
其中,在各类并发场景下会出现的问题如下:
读读并发场景不会导致数据不一致问题,因此无需特殊处理。
读写并发场景可能会出现脏读、不可重复读、幻读的问题。
写写并发场景可能会出现更新丢失的问题。
二.事务隔离级别
针对上述描述的问题,MySQL采用不同的事务隔离级别分别用于解决上述的部分问题,具体如下:
隔离级别 | 含义 | 读数据一致性 | 更新丢失 | 脏读 | 不可重复读 | 幻读 |
读未提交(Read Uncommitted) | 事务中的修改,即使没有提交,对其他事务依然可见。 | 最低级别,只能保证不读取物理上损坏的数据。 | 否 | 是 | 是 | 是 |
读已提交(Read Committed) | 只有已提交事务所做的修改才对其他事务可见。 | 语句级 | 否 | 否 | 是 | 是 |
可重复读(Repeatable Read ) | 同一事务中多次读取相同的记录结果时一致的。 | 事务级 | 否 | 否 | 否 | 否 |
串行化(Serializable) | 在读取的每一行数据上加锁,强制事务串行执行,不支持并发。 | 事务级 | 否 | 否 | 否 | 否 |
在上述隔离级别中,从上到下并发性能依次降低,安全性依次提高。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.锁
锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据了而言显得尤为重要。
锁可以解决上述描述读写并发场景下的幻读问题、写写并发场景下的更新丢失问题。下面针对锁、锁的类型及其应用场景展开详细描述。
2.1锁的基本原理
http://mysql.taobao.org/monthly/2016/01/01/
https://mp.weixin.qq.com/s/yq5Erdv5Dft3foJEVE2dxA
https://i6448038.github.io/2019/02/23/mysql-lock/
https://www.163.com/dy/article/G0S7FSGG05319WXB.html
duplicate key check检查时需要在二级唯一索引上加gap lock(版本:5.6之前,5.7.26-29之后加record lock)
https://juejin.cn/post/6844903597029720072
https://my.oschina.net/hebaodan/blog/3045246
死锁发生场景:
事务1,2均获取某一行数据的s锁后,均想获取x锁。
事务1,2均获取到某一间隙锁后,希望进行插入操作,即增加插入意向锁。
事务1,2操作同一条数据。由于检索条件不同,事务1,2分别锁定了聚簇索引和二级索引,然后事务1无法操作二级索引(先删除,再插入),事务2无法操作聚簇索引,发生死锁。(参考http://mysql.taobao.org/monthly/2016/01/01/)