先推荐两片文章!!
一个MySQL锁,和面试官大战三十回合
关于MySQL的酸与MVCC和面试官小战三十回合
目录
面试官:MySQL InnoDB 的锁 和 MyISAM 的锁有什么区别?
我:MyISAM 只支持表锁,一锁就锁整张表,而 InnoDB 不仅支持表锁,还支持粒度更低的行锁,仅对相关的记录上锁即可,所以对于写入操作来说 InnoDB 的性能更高。
1、首先讲事务
数据库中的事务是指对数据库执行一批操作,这些操作最终要么全部执行成功,要么全部失败;还有关于事务的酸(ACID)也需要了解,这里不赘述。给个链接Mysql事务
2、讲并发产生的问题(脏读、不可重复读、幻读?)
最好结合实例进行理解这里有例子【数据库】快速理解脏读、不可重复读、幻读
- 脏读:两个事务同时在执行,A修改了一个数据但是还没有提交,B读取并使用了A修改后的数据,但是A事务之后又回滚,那么B读到的这个数据就是不合法的,是脏的.这种情况就被称为脏读
- 不可重复读:两个事务在执行,A在一开始读取到一个数据为x,然后B将这个数据更改为y,更改之后A又去读取该数据,结果却发现数据为y.和最开始的x不同.也就是说一个事务在过程中多次读取同一个数据,结果发现前后结果不同.这就被称为不可重复读.
- 幻读: 两个事务在执行,A执行了一个语句比如说 select id from student where id >
1.查询出结果有4条,B这个时候插入了一条数据 **insert into student values(100) **.插入完成之后,A再执行之前相同的语句,却发现一个有5条记录! 平白无故多了一条记录.就像出现了幻觉.这个情况就被称为是幻读.
tip:不可重复读和幻读的区别:不可重复读强调的是对原有的数据进行了修改.而幻读则强调增加或减少数据
————————————————
版权声明:本文为CSDN博主「阿伟のBlog」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_44823898/article/details/115718622
3、隔离机制
隔离级别 | 描述 | 解决问题 | 实现方案 |
---|---|---|---|
读未提交(read uncommitted) | 当前事务可以读取其他事务没有提交的数据 | 什么都没解决 | 什么都没做 |
读已提交(read committed) | 当前事务只能读取其他事务已提交的数据 | 脏读 | MVCC(undo log + read view) |
可重复读(repeatable-read) | 可以保证事务执行期间对一个数据的多次读取结果是相同的 | 脏读,不可重复读 | MVCC(undo log + read view) |
可串行化(serializable) | 最高的隔离级别,事务之间是一个一个的顺序执行,事务不能并发,自然也就没有并发问题. | 什么都解决了 | 加锁 |
4、锁
引用链接
一个MySQL锁,和面试官大战三十回合
关于MySQL的酸与MVCC和面试官小战三十回合
Innodb支持行锁和表锁。InnoDB行锁 有记录锁(Record Locks)、间隙锁(Gap Locks)、Next-Key Locks。
加行锁
记录锁顾名思义就是锁住当前的记录,它是作用到索引上的。我们都知道 innodb 是肯定有索引的,即使没有主键也会创建隐藏的聚簇索引,所以记录锁总是锁定索引记录。
比如,此时一个事务 A 执行 SELECT * FROM yes WHERE name = 'xx' FOR UPDATE;
那么 name = xx 这条记录就被锁定了,其他事务无法插入、删除、修改 name = xx 的记录。
此时事务 A 还未提交,另一个事务 B 要执行 insert into yes (name) values ('xx'),
此时会被阻塞,这个很好理解。
但是,如果另一个事务 C 执行了 insert into yes (name) values ('aa')
,这个语句会被阻塞吗?
看情况。
如果 name 没有索引。前面提到记录锁是加到索引上的,但是 name 没索引啊,那只能去找聚簇索引,但聚簇索引上面只有主键啊,它哪知道各自的 name 是什么,所以咋办?都锁了呗!
因此,如果 name 没有索引,那么事务 C 会被阻塞,如果有索引,则不会被阻塞!
所以这里要注意,没索引的列不要轻易的锁,不要以为有行锁就可以为所欲为,并不是这样滴。
可重复读本质上只是解决不可重复读的问题,但在Innodb的可重复读隔离机制下,同时解决了不可重复读和幻读。查看实现方法InnoDB如何用锁实现事务的隔离
READ COMMITED的实现
该隔离级别解决了脏读的问题,InnoDB使用
一致性非锁定读
的技术来实现的。那么什么是一致性非锁定读呢?一致性非锁定读
一致性的非锁定读是指InnoDB存储引擎通过多版本控制(MVCC)的方式来读取当前执行行时间数据库中的数据。如果读取的行正在执行DELETE或UPDATE操作,这时读取操作不会因此去等待行上锁的释放。相反地,InnoDB存储引擎会去读取行的一个快照数据。
在READ COMMITED隔离级别下,如果要读取的行被上锁(事务B未提交),则读取最新的快照,因此能解决脏读的问题。REPEATABLE READ的实现
如前文所述,一般的情况下REPEATABLE READ解决的是不可重复读的问题,SERIALIZABLE解决的是幻读的问题。但是InnoDB有点厉害了,它在REPEATABLE
READ下就把不可重复读和幻读这两个问题都解决了,也就是说它把SERIALIZABLE活也干了。那么在REPEATABLE READ下是如何实现对这两个问题的解决的呢? 对于不可重复读的问题,解决的方法还是一致性非锁定读。不过在这里它读取快照的策略与READ COMMITED下不一样。在READ COMMITED下是读取最新的快照,而REPEATABLE READ下是读取事务最开始的快照,无论这条记录被其他事务怎么改,在我的事务内我都是读取最开始的那个快照,以此来保证数据的可重复读。
对于幻读的问题,InnoDB使用了一致性锁定读中的Next-Key Lock算法
解决。我们首先介绍一下什么是Next-Key Lock。Next-Key Lock
当InnoDB采用一致性锁定读的时候,就是给记录加锁(X锁即排他锁,S锁即共享锁)。InnoDB存储引擎有3中锁的算法:
- Record Lock:单个行记录上的锁
- Gap Lock:间隙锁,锁定一个范围,但不包括记录本身
- Next-Key Lock:Gap Lock+Record Lock,锁定一个范围,并且锁定记录本身。
在Next-Key Lock锁算法下,查询i>0的时候,(0,+∞+∞)范围会被上锁,这时候事务B要在该范围内插入记录就需要阻塞,就这样幻读问题就解决了。
———————————————— 版权声明:本文为CSDN博主「道友,且慢」的原创文章,遵循CC 4.0
BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qqqq0199181/article/details/80782822
深入了解mysql–gap locks,Next-Key Locks
那么gap锁到底是如何加锁的呢?
假如是for update级别操作,先看看几条总结的何时加锁的规则。
唯一索引
- 精确等值检索,Next-Key Locks就退化为记录锁,不会加gap锁
- 范围检索,会锁住where条件中相应的范围,范围中的记录以及间隙,换言之就是加上记录锁和gap 锁(至于区间是多大稍后讨论)。
- 不走索引检索,是无法使用行级锁的, 此时使用的是表锁
非唯一索引
- 精确等值检索,Next-Key Locks会对间隙加gap锁(至于区间是多大稍后讨论),以及对应检索到的记录加记录锁。
- 范围检索,会锁住where条件中相应的范围,范围中的记录以及间隙,换言之就是加上记录锁和gap 锁(至于区间是多大稍后讨论)。
- 非索引检索,全表间隙gap lock,全表记录record lock
其实很好理解,唯一索引时,索引是不能重复的,对于精确等值检索,不需要担心插入本身。但是非唯一等值索引的等值检索,可能会插入自身,因此要加入间隙锁,让自身的左右都不能再插入相同的值。
对于锁住范围:像上面间隙锁的举例,只能锁定(3,5) 这个区间,而 Next-Key Locks 是一个前开后闭的区间(3,5],这样能防止查询 id=5 的这个幻读。
5、MVCC的实现原理
mysql数据库默认的隔离机制是可重复读。借用一句话,rr和rc两个级别可以共用完全同样的read view逻辑,甚至单从read view来看,rr级别比rc级别更少消耗系统资源,也难怪为啥mysql默认级别是rr。下面具体看
【MySQL笔记】正确的理解MySQL的MVCC及实现原理
关于MySQL的酸与MVCC和面试官小战三十回合
readView 的判断条件
重点理解:
版本链搞清楚了,这时候还需要知道一个概念 readView,这个 readView 就是用来判断哪个版本对当前事务可见的,这里有四个概念:
- creator_trx_id,当前事务ID。一个事务只有当有修改操作的时候才会被分配事务 ID。
- m_ids,生成 readView 时还活跃的事务ID集合,也就是已经启动但是还未提交的事务ID列表。
- min_trx_id,当前活跃ID之中的最小值。
- max_trx_id,生成 readView 时 InnoDB 将分配给下一个事务的 ID 的值(事务 ID 是递增分配的,越后面申请的事务ID越大)。也就是
目前已出现过的事务ID的最大值+1
对于可见版本的判断是从最新版本开始沿着版本链逐渐寻找老的版本,如果遇到符合条件的版本就返回。
判断条件如下:
-
如果当前数据版本的 trx_id == creator_trx_id 说明修改这条数据的事务就是当前事务,所以可见。(注意trx_id 为当前数据版本的ID,creator_trx_id 为当前执行事务的ID,之后就不讨论当前creator_trx_id ,因为该判断是为了证明当前最新版本的修改是否属于当前事务,如果不是,那就读取之前的版本,数据版本都是从最新版本往回读,因此如果最新版本不是当前当前事务执行的,那就往回找最新提交的即可。考虑到可重复读问题,每次都读最新的,会使一个事务中的读取数据不一样,因此在可重复读在第一次生成 readView 之后的所有查询都共用同一个 readView 即可。
-
如果当前数据版本的 trx_id < min_trx_id,说明修改这条数据的事务在当前事务生成 readView 的时候已提交,所以可见。这里解释为什么会小于。如果trx_id < min_trx_id,不就说明执行事务Id已经提交了吗,已经提交后怎么还能继续执行呢?首先要明确trx_id并不是执行事务的ID,而是比较版本的事务Id,所以小于表示该数据版本的事务一定是提交过的,所以可见。
-
如果当前数据版本的 trx_id 在 m_ids 中,说明修改这条数据的事务此时还未提交,所以不可见。
-
如果当前数据版本的 trx_id >= max_trx_id,说明修改这条数据的事务在当前事务生成 readView 的时候还未启动,所以不可见(结合事务ID递增来看)。
理解:trx_id 是当前比较版本的事务ID,并不是当前执行事务的ID就一定是当前版本的事务ID,因为会从最新的版本进行比较,而执行的可能是之前的事务,并且对于普通的select语句,并不分配事务Id,因为不执行更新操作。
下面的5就是当前版本的数据事务ID,而当前事务ID可能是旧事物ID1中的操作,(就算没有提交也会直接修改数据)
牢牢记住数据版本是从新向旧查找,这点很重要!! 整体逻辑整理:
- 就是先判断当前数据版本ID是否为正在执行的事务ID,如果是,那当前版本就对当前事务可见,因为同属于一个事务。但如果不相同,那就往回找最新提交的旧版本。
- 如果数据版本Id小于正在活跃(也就是还未提交)的最小事务ID,说明当前数据版本的事务已经提交,直接可见,进行返回。如果不小于,那下面就判断大于的情况,可能在活跃事务ID列表中,也有可能大于。
- 如果在列表中,则表示当前版本的数据所处的事务还未提交,那肯定就是不可见的,如果当前数据版本Id>=
目前已出现过的事务ID的最大值+1
,说明在当前事务创建readView时,当前版本的数据所属事务还没启动,那肯定也是不可见的。 - 循环整个过程,直到返回True。也即是往回找到最新提交的旧版本。
select 是否用锁
隔离方案可以看出是为了解决上述几种读操作做出的,针对于select。对于像SELECT .. FOR UPDATE
或者LOCK IN SHARE MODE
等这种锁读请求的方式、UPDATE和DELETE请求来说,会加锁,加锁的方式就是上面介绍的加锁过程。
-
面试官:说到插入新记录我问你个问题,如果插入的事务还未提交,现在有另一个事务通过
SELECT ... LOCK IN SHARE MODE
或者SELECT ... FOR UPDATE
打算读取这条记录怎么办?此时生效的是什么锁? -
我:(我丢,面试官想给我挖坑?哼,但是这难不倒我霸中霸!)
插入的事务还未提交,此时普通 select 没问题,有 MVCC 在,所以读不到未提交的版本。而
SELECT ... LOCK IN SHARE MODE
或者SELECT ... FOR UPDATE
是要获取记录 S 锁和 X 锁的,但是此时事务还未提交,因此这两类 select 会阻塞。具体是怎么阻塞的呢?因为有事务ID!通过 MVCC 可以利用事务ID 来进行判断当前记录是否可见,这其实相当于一把隐式锁!知道当前记录不可见,于是这个查询事务会为之前未提交的插入的事务生成一个锁结构,然后查询事务自己也生成锁结构,接着等待插入事务的释放,这样就完成了阻塞!