数据库
首页 > 数据库> > 一文掌握MySQL锁机制(共享锁/排他锁/意向锁/间隙锁/临键锁等)

一文掌握MySQL锁机制(共享锁/排他锁/意向锁/间隙锁/临键锁等)

作者:互联网

文章目录


1、数据库为什么要有锁机制

对于系统来说,数据是最核心的资产,需要提供强一致性和有效性。

当“数据库 + 并发读写访问”场景时,为保持强一致性和有效性,那么数据库中细分的数据区域,就是一个个的临界区。

为具备数据临界区的能力,就引入了数据库的锁机制。

(锁的理念和本质思想都是共性的,数据库锁只是将锁的运用做了场景化的适配优化)


2、锁的分类

MySQL官方文档:https://dev.mysql.com/doc/refman/5.6/en/innodb-locking.html#innodb-intention-locks


在这里插入图片描述

数据库中是以“表”为一个数据体的单位,那么“表锁”是自然存在的一类锁


表锁可分为:

行锁可分为:


核心助读: 《《《《《《 重点!重点!重点!

1、先存在:表锁

2、然后为更好的并发需要,做了细粒度的锁:行锁

3、为了表锁和行锁间更高效的配合,出现了:意向锁

4、为了针对范围型数据操作的有效性,出现了:间隙锁、临键锁

5、为了插入查询并发的优化,出现了:插入意向锁


3、锁详细介绍

3.1、表-共享锁(S)

用途: 表共享锁之间不互斥,读读可以并行。

原理: N/A

操作方式:


3.2、表-排他锁(X)

用途: 表排他锁与任何锁都互斥,写读和谢谢都不可并行。

原理: N/A

操作方式:


3.3、表-自增锁(Auto-Inc Lock)

自增锁是一种特殊的表级锁。

专门针对事务插入AUTO_INCREMENT类型的列。

最简单的情况,如果一个事务正在往表中插入记录,所有其他事务的插入必须等待,以便第一个事务插入的行,是连续的主键值。

MySQL官方解释:
AUTO-INC lock is a special table-level lock taken by transactions inserting into tables with AUTO_INCREMENT columns.

In the simplest case, if one transaction is inserting values into the table, any other transactions must wait to do their own inserts into that table, so that rows inserted by the first transaction receive consecutive primary key values.

举个例子:表中有如下三条数据,id为AUTO_INCREMENT:

1, zhangsan
2, lisi
3, wangwu

事务A先执行,但还未提交: insert into t(name) values(‘lindan’);

事务B后执行: insert into t(name) values(‘lizongwei’);

此时事务B插入操作会阻塞,直到事务A提交


3.4、行 - 记录锁(Record Lock)

(同时支持“共享锁和排他锁”,根据SQL语义区分)

行记录锁是作用在索引记录(Key)上的锁。(B+树上的Key节点)

语义上是“锁定一行数据”,做并发操作的保护。


注意点:

1、记录锁是作用在索引上的锁,如果建表时候没有设置任何一个索引,InnoDB引擎会使用隐式的主键来进行锁定。

2、InnoDB行锁是作用在索引上的锁,只有查询走索引时才会用到。如果SQL查询时没有使用任何一个索引,则会升级为“表锁”。

3、InnoDB行锁是作用在索引上的锁,即使是访问不同行的数据记录,但如果使用的是同一个索引键(KEY),会发生锁冲突

4、如果数据表建有多个索引,可以通过不同的索引锁定不同的行。


常用SQL:

select ... for update;

or

select ... lock in share mode;

思考题:SQL中用 IN(x,y,z)操作,是加行记录锁吗?


3.5、表 - 意向共享锁(IS)/ 意向排他锁(IX)

核心助读:为了表锁和行锁间更高效的配合,出现了:意向锁


为什么?

自从锁粒度细分到了:表锁和行锁,就顺带引入了表锁和行锁的配合关系:

那么,在每次加锁时往往免不了 查询&判断 表上表锁情况和行锁情况。

为优化遍历表下所有索引,查询行锁情况的费时操作,【意向锁】出现了。


意向锁原理:


表共享/排他锁 和 表意向共享/意向排他锁 的兼容关系:

在这里插入图片描述

在这里插入图片描述

常用SQL:

select ... for update;

or

select ... lock in share mode;

3.6、行 - 间隙锁(Gap Lock)

核心助读:为了针对范围型数据操作的有效性,出现了:间隙锁


从幻读讲起:

注:


常用SQL:

select ... 范围 for update;

or

select ... 范围 lock in share mode;

or

update ... 范围;
delete ... 范围;
insert ... 范围;

3.7、行 - 临键锁(Next-Key Lock)

核心助读:为了针对范围型数据操作的有效性,出现了:临键锁

注:临键锁(Next-Key Lock)是InnoDB RR模式下的默认行锁实现。


临键锁:


再注:临键锁(Next-Key Lock)是InnoDB RR模式下的默认行锁实现。


举例:

 `create table test(
  id int,
  v1 int,
  v2 int,
  primary key(id),
  key `idx_v1`(`v1`)
 )Engine=InnoDB DEFAULT CHARSET=UTF8;
+----+------+------+
| id | v1  | v2  |
+----+------+------+
| 1 |  1 |  0 |
| 2 |  3 |  1 |
| 3 |  4 |  2 |
| 5 |  5 |  3 |
| 7 |  7 |  4 |
| 10 |  9 |  5 |
(-∞,1)
(1,3)
(3,4)
(4,5)
(5,7)
(7,9)
(9, +∞)
事务A事务B
BEGIN;BEGIN;
UPDATE test SET v2 = 8 WHERE v1 = 7;
//do something othersINSERT INTO test VALUES(6,6,5); // 被阻塞
COMMIT;//do something others
COMMIT;

为什么事务B的insert操作会被阻塞呢?


疑问:操作单条记录,为什么要锁上下游?

(防止相同KEY的插入,破坏事务间的隔离性)

注:


3.8、行 - 插入意向锁(Insert Intention Lock)

核心助读:插入意向锁是间隙锁的一种。保障间隙锁的隔离性,又提高并发插入的能力。


以上的锁,基本都是对已存在的数据做select、update、delete。那么,新插入的数据需不需要锁呢?

要的!


插入意向锁:


不加插入意向锁,会有什么问题?


3.9、总结-要点

1、基础是“表锁和行锁”下的读写锁,其他名称的锁都是为了解决/优化特定场景而诞生的,记住每个锁解决的场景。

2、行锁是作用在索引KEY上的。若SQL查询未走索引,则会升级为表锁。


在这里插入图片描述


4、对应SQL场景

以MySQL默认的InnoDB以及RR隔离级别为条件


select ...


select ... lock in share mode


select ... for update


update ... [等值 / 范围] / delete ... [等值 / 范围]


insert ...


5、锁与事务的关系

5.1、加锁


5.2、释放锁


6、死锁和死锁检测处理

6.1、死锁

死锁的发生:多个事务,每个事务都已持有部分锁,然后进一步去获取对方事务持有的锁,互相等待。


例子:

start transaction;
update stock_price set close = 45.50 where stock_id = 4 and date = '2017-4-26';
update stock_price set close = 19.80 where stock_id = 3 and date = '2017-4-27';
commit;
start transaction;
update stock_price set high = 20.10 where stock_id = 3 and date = '2017-4-27';
update stock_price set high = 47.20 where stock_id = 4 and date = '2017-4-26';
commit;

如果凑巧,两个事务均执行了第一条update语句,同时锁定了该资源。当尝试执行第二条update语句的时候,去发现资源已经被锁定,两个事务都等待对方释放锁,则陷入死循环,形成死锁。

6.2、死锁检测和处理

MySQL官方文档:https://dev.mysql.com/doc/refman/5.6/en/innodb-deadlock-example.html


MySQL支持了死锁检测和处理机制。

当出现死锁后,有两种策略:

1、直接进入等待,直到超时。这个超时时间可以通过参数 innodb_lock_wait_timeout 来设置

2、发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑


如何选择策略

在 InnoDB 中,innodb_lock_wait_timeout 的默认值是 50s,意味着如果采用第一个策略,当出现死锁以后,第一个被锁住的线程要过 50s 才会超时退出,然后其他线程才有可能继续执行。对于在线服务来说,这个等待时间往往是无法接受的。但是,我们又不可能直接把这个时间设置成一个很小的值,比如 1s。这样当出现死锁的时候,确实很快就可以解开,但如果不是死锁,而是简单的锁等待呢?所以,超时时间设置太短的话,会出现很多误伤。

所以,正常情况下我们还是要采用第二种策略,即:主动死锁检测,而且 innodb_deadlock_detect 的默认值本身就是 on。主动死锁检测在发生死锁的时候,是能够快速发现并进行处理的,但是它也是有额外负担。


故,即使出现死锁,待mysql回滚,业务上做失败处理或重试即可。


6.3、如何减少死锁

(其实,我们日常都在做)

1、业务研发上,一般以乐观锁为主。

2、SQL尽量用索引,因为走索引才能用上行锁,更细粒度的锁,减少锁冲突的概率。

3、减少大事务,用于减少持有锁的时间,进而降低冲突。


6、类比Java的锁场景

其实,MySQL数据的9种锁并不复杂,试着分层、对比Java的锁场景理解下:


以Java ConcurrentHashMap为例。

为了做到线程安全,ConcurrentHashMap要做好多线程并发访问的控制。

第一层:粗粒度


第二层:细粒度

第三层:粗细结合

第四层:场景化定制


就这么简单!

标签:事务,插入,update,临键,索引,死锁,意向锁,MySQL,select
来源: https://blog.csdn.net/weixin_43318367/article/details/113444316