加锁情况与死锁原因分析
为方便大家复现,完整表结构和数据如下:
+c1+ int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (+c1+),
) ENGINE=InnoDB
死锁日志如下:
INSERT INTENTION LOCK
但是插入意向锁是客观存在的,我们可以在官方手册中查到,不可忽略:
Prior to inserting the row, a type of gap lock called an insert intention gap lock is set. This lock signals the intent to insert in such a way that multiple transactions inserting into the same index gap need not wait for each other if they are not inserting at the same position within the gap.
当插入一条记录时,会去检查当前插入位置的下一条记录上是否存在锁对象,如果下一条记录上存在锁对象,就需要判断该锁对象是否锁住了 gap.如果 gap 被锁住了,则插入意向锁与之冲突,进入等待状态(插入意向锁之间并不互斥).最后提醒一下大家这把锁的属性:
① 它不会阻塞其他任何锁;
在学习 MySQL 过程中,一般只有在它被阻塞的时候才能观察到,所以这也是它常常被忽略的原因吧...
GAP LOCK
在此例中,另外一个重要的点就是 gap lock,通常情况下我们说到 gap lock 都只会联想到 REPEATABLE-READ 隔离级别利用其解决幻读.但实际上在 READ-COMMITTED 隔离级别,也会存在 gap lock ,只发生在:唯一约束检查到有唯一冲突的时候,会加 S Next-key Lock,即对记录以及与和上一条记录之间的间隙加共享锁.
通过下面这个例子就能验证:
有个困惑很久的疑问:出现唯一冲突需要加 S Next-Key Lock 是事实,但是加锁的意义是什么?还是说是通过 S Next-Key Lock 来实现的唯一约束检查,但是这样意味着在插入没有遇到唯一冲突的时候,这个锁会立刻释放,这不符合二阶段锁原则.这点希望能与大家一起讨论得到好的解释.
如果是在 REPEATABLE-READ,除以上所说的唯一约束冲突外,gap lock 的存在是这样的:
对于 gap lock,相信 DBA 们的心情是一样一样的,所以我的建议是:
① 在绝大部分的业务场景下,都可以把 MySQL 的隔离界别设置为 READ-COMMITTED;
锁冲突矩阵
前面我们说的 GAP LOCK 其实是锁的属性,另外我们知道 InnoDB 常规锁模式有:S 和 X,即共享锁和排他锁.锁模式和锁属性是可以随意组合的,组合之后的冲突矩阵如下,这对我们分析死锁很有帮助:
mysql 为并发事务同时对一条记录进行读写时,提出了两种解决方案:
①.)使用 mvcc 的方法,实现多事务的并发读写,但是这种读只是"快照读",一般读的是历史版本数据,还有一种是"当前读",一般加锁实现"当前读",或者 insert、update、delete 也是当前读.
快照读:就是select
当前读:特殊的读操作,插入/更新/删除操作,属于当前读,处理的都是当前的数据,需要加锁.
mysql 在 RR 级别怎么处理幻读的呢?一般来说,RR 级别通过 mvcc 机制,保证读到低于后面事务的数据.但是 select for update 不会触发 mvcc,它是当前读.如果后面事务插入数据并提交,那么在 RR 级别就会读到插入的数据.所以,mysql 使用 行锁 + gap 锁(简称 next-key 锁)来防止当前读的时候插入.
Gap Lock在InnoDB的唯一作用就是防止其他事务的插入操作,以此防止幻读的发生.
Innodb自动使用间隙锁的条件:
背景
数据库的锁是在多线程高并发的情况下用来保证数据稳定性和一致性的一种机制.MySQL 根据底层存储引擎的不同,锁的支持粒度和实现机制也不同.MyISAM 只支持表锁,InnoDB 支持行锁和表锁.目前 MySQL 默认的存储引擎是 InnoDB,这里主要介绍 InnoDB 的锁.
使用 InnoDB 的两大优点:一是支持事务;二是支持行锁.
在高并发的情况下事务的并发处理会带来几个问题
由于高并发事务带来这几个问题,所以就产生了事务的隔离级别
举个例子
今天给大家分享了一下 MySQL 的 InnoDB 的事务以及锁的一些知识,通过自己的实际上手实践对这块更加熟悉了,希望大家在看的时候也可以动手试试,这样更能体会,理解的更深刻.