带你了解mysql中锁的概念

为什么需要锁?

凡是遇到新知识,必问为什么?没别的,就是喜欢深入(当然是因为需要造航母啊)~

数据库锁出现的原因是为了处理并发问题,因为数据库是一个多用户共享的资源,当出现并发的时候,就会导致出现各种各样奇怪的问题,就像程序代码一样,出现多线程并发的时候,如果不做特殊控制的话,就会出现意外的事情,比如“脏“数据、修改丢失等问题。所以数据库并发需要使用事务来控制,事务并发问题需要数据库锁来控制,所以数据库锁是跟并发控制和事务联系在一起的。

锁的分类

在Mysql中,可以按照范围来区分锁:

  • 全局锁
  • 表级锁
  • 行锁

全局锁

顾名思义,全局锁就是给全局上的一把锁,能够对整个数据库造成影响的锁。

flush tables with read lock

只要执行了上述命令,整个数据库就变为可读状态了,其他线程的所有操作都会被堵塞。

我这里用一个数据进行验证一下:

mysql> select * from t_user;
+----+-----------+------+
| id | name      | age  |
+----+-----------+------+
|  1 | 张三      |   18 |
|  3 | 王五      |   17 |
|  4 | Tom       |   18 |
+----+-----------+------+
3 rows in set (0.01 sec)

mysql> flush tables with read lock;
Query OK, 0 rows affected (0.00 sec)

这时,开启一个新的线程,并且修改表中数据会发生堵塞吗?

mysql> update go_db.t_user set age = 66 where name = 'Tom';
....

虽然可以读取数据,但是只要执行增删改这些操作(修改表等操作也会被堵塞)就会发生堵塞。

unlock tables

执行该条命令即可解除全局锁,如果会话断开全局锁也是会自动解除的。

那么这样一个导致全局都只能进行读取,而不能进行增删改等操作的锁有什么用呢?

全局锁的应用

全局锁主要用于数据库全库逻辑的备份,在备份期间肯定不能有对数据库造成影响的语句发生,唯一可以不会造成影响的就是读操作了。

全局锁的缺点

显而易见,全局加锁用于备份,而备份数据这个时间可不短。那么在备份的时间里,所有的语句都没法执行,甚至全部处于堵塞状态。这就先不考虑堵塞结束后数据库会不会崩溃,需要先考虑的问题是只能读的情况下业务岂不是全部没法执行了吗。那么全局锁必须要业务停下来才能备份的话,岂不又是一个大问题?

如何避免全局锁的缺点呢?

还记得事务的隔离性中有一个级别可以单独有一个数据空间进行事务处理吗?没错就是可重复读

如果数据库的存储引擎支持事务,并且支持事务的可重复读这个隔离级别。那么他就可以在备份数据库之前先开启Read View。然后在Read View下进行一系列操作。

在可重复读的环境下,事务之外的任何操作都不会影响到该事务内的数据变化。在备份期间可以一直使用这个空间。

表级锁

在Mysql中被称为表级锁的有这么几种:

  • 表锁
  • 元数据锁(MDL)
  • 意向锁
  • AUTO-INC锁
表锁

表锁锁定有两种方式,一种是读锁,一种是写锁。

//表级别的共享锁,也就是读锁;
lock tables t_student read;

//表级别的独占锁,也就是写锁;
lock tables t_stuent write;

两种锁的解锁的方式和全局锁一样。

当使用读锁的时候,整个数据表都只能进行查询操作。一旦有写操作就会被堵塞,等待读锁的释放。

当使用写锁的时候,整个数据表无论读写都会被堵塞,等待写锁的资源释放。

如果没有主动释放锁,当会话结束之后,锁也会被释放掉。

元数据锁

**元数据锁(MDL)**是一个隐式的锁。意思就是不需要主动开启,它会自动启用。

那么什么情况下元数据锁会主动启动呢?

  • 当你对一张表进行CRUD的时候,就会对这个表开启MDL写锁,其余读操作均被阻塞。
  • 当你对一张表进行结构变更的时候,就会对这个表开启MDL读锁,其余读写操作全被堵塞。

当一个执行操作结束时或者会话结束时,MDL锁就会被释放。

当有一个事务执行时,会在事务提交的时候进行释放。也就是说只要事务还没有提交,MDL已经开启了,那么他就一直不会进行释放。因此也出现了一个隐患问题。

在事务提交的时候才会释放的MDL锁带来的隐患

假如A线程开启了事务,并且进行了查询操作触发了MDL读锁

B线程进行了查询操作并且没有堵塞,因为两方互不影响,MDL读锁不会阻塞读锁。

这时C线程对表结构进行了更改,发现A线程事务还没有提交,触发MDL写锁,进行堵塞。

自此之后的CRUD线程来一个就会被堵塞一个,直到A线程提交事务或者线程过载数据库系统崩溃

为什么单个线程触发写锁之后,其他线程也没法进行读操作?

因为MDL锁是一个队列锁。所有申请MDL锁的线程会排成一个队列等待执行。而这个队列中写的优先级要高于读。一旦出现了MDL写锁,那么之后所有的CRUD操作全部都被堵塞了。

所有为了性能和安全的考虑,在进行对数据库表结构进行修改的时候,需要先查看是否有长事务在进行中。如果该事务必须执行,则只能等待执行。如果该事务的执行可以在修改结构之后,那么可以选择先kill掉这个长事务的线程。

意向锁

意向锁其实”人如其名“,就是一个有想法,但准备行动的锁。值得注意的是:意向锁是一个表级锁

在一个数据表中,如果你想要对表里的某些记录加上 [共享锁][独占锁]的时候,你需要先去判断,这个表中是否已经加过了共享锁或者独占锁(排他锁)了。因此你需要遍历表中的所有记录,查询后发现没有这些锁的痕迹,你才能放心的加你想加的锁。

这里有一个很重要的点:就是只有获取表中的行锁时,才会需要先申请意向锁。 如果是执行 ALTER TABLE 等需要锁定整个表的语句,是不需要申请意向锁的,可以直接去申请表级 独占锁。

而在你加锁之前,还需要做一件事。来表明你需要加锁这个意向。这就是意向锁

  • 当你准备加共享锁的时候,你需要先加意向共享锁

  • 当你准备加独占锁的时候,你需要先加意向独占锁

就因为有个想法就要去加一把锁吗?并不是,意向锁可大有用处。

还记得你在加锁之前需要先判断这个表中是否已经被加过锁这个操作吗?遍历记录是很浪费时间的,你只需要判断这个表是否有意向锁就行了。因为在加锁之前都会先加一个意向锁

因此,意向锁的目的是为了快速判断表里是否有记录被加锁

值得一提的是:

意向共享锁和意向独占锁是表级锁,不会和行级的共享锁和独占锁发生冲突,而且意向锁之间也不会发生冲突,只会和共享表锁(lock tables … read)和独占表锁(lock tables … write)发生冲突。

表锁和行锁是满足读读共享、读写互斥、写写互斥的。

AUTO-INC锁

大家都知道每张表中会有一个自增主键。而无论多么高频率的并行插入数据,这个自增主键永远保持 自增连续

能够无错误的连续且自增,正是因为AUTO-INC锁的作用。

在一个事务进行插入数据的时候,他会先去获取到AUTO-INC锁。等插入完毕之后再释放掉这个锁。注意,这个锁是在插入数据之后便释放的,即使在事务中也是如此。

但是在高频率的插入下,仍然会产生效率的问题。因此,在Mysql5.1.22版本之后,Innodb引擎出现了一个新的轻量级的锁

Innodb存储引擎提供了一个innodb_autoinc_lock_mode的系统变量。通过这个变量可以选择,什么时候使用AUTO-INC锁,什么时候使用轻量级锁

  • innodb_autoinc_lock_mode = 0时,就采用AUTO-INC锁,语句执行结束之后释放锁。
  • innodb_autoinc_lock_mode = 2时,就采用轻量级锁,申请自增主键后就释放锁,并不需要等语句执行后才释放。
  • innodb_autoinc_lock_mode = 1时,会分情况讨论:
    • 普通 insert语句,自增锁在申请之后就马上释放;
    • 类似 insert … select 这样的批量插入数据的语句,自增锁还是要等语句结束后才被释放;

行级锁

在Mysql中,是有锁的颗粒度这个概念的。所谓颗粒度就是指锁的级别,范围越大的锁,颗粒度越大,性能也就越低。

因此行级锁就是颗粒度最小的一类锁。

注:Innodb存储引擎是支持行级锁的,而Myisam存储引擎并不支持。

行级锁的类型主要有三类:

  • Record Lock,记录锁,也就是仅仅把一条记录锁上;
  • Gap Lock,间隙锁,锁定一个范围,但是不包含记录本身;
  • Next-Key Lock:Record Lock + Gap Lock 的组合,锁定一个范围,并且锁定记录本身。

在了解行级锁之前,需要先了解两个概念:共享锁(S)独占锁或者叫做排他锁(X)。

下面分别是两种锁的执行语句:

//对读取的记录加共享锁
select ... lock in share mode;

//对读取的记录加独占锁
select ... for update;

共享锁(S锁)满足读读共享,读写互斥。独占锁(X锁)满足写写互斥、读写互斥。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5z3zl9KS-1680535311435)(MySQL.assets/x锁和s锁.png)]

Record Lock

Record Lock 称为记录锁,锁住的是一条记录。而且记录锁是有 S 锁和 X 锁之分的:

记录锁是对索引记录的锁定,换句话说就是,记录锁只会锁定索引。

  • 当一个事务对一条记录加了 S 型记录锁后,其他事务也可以继续对该记录加 S 型记录锁(S 型与 S 锁兼容),但是不可以对该记录加 X 型记录锁(S 型与 X 锁不兼容)
  • 当一个事务对一条记录加了 X 型记录锁后,其他事务既不可以对该记录加 S 型记录锁(S 型与 X 锁不兼容),也不可以对该记录加 X 型记录锁(X 型与 X 锁不兼容)

当开启一个事务之后,并且对id=5的数据添加上记录锁之后。这样其他事务就无法对这条数据进行修改了。当事务提交之后该锁才会被释放掉。

Gap Lock

Gap Lock称为间隙锁,只存在于可重复读隔离级别,目的是为了解决可重复读隔离级别下幻读的现象。

先来说一下什么是幻读:

幻读就是一个线程读取了一类数据的值之后,另一个线程增添了该类数据的值。这时总的数据量发生了变化,等到第一个线程再次区读取该类数据的值之后发现,相比之前的查询结果竟然多出来了一条数据。

因此,幻读主要注重于行的变化。

再来说说为什么间隙锁能够解决幻读:

间隙锁可以将索引id范围进行锁定,比如锁定(3,7)之间。那么这段数据之间不能再插入一条新的数据(4,5,6)。而幻读注重行的变化,你在范围内的变化给他锁定了,那他不就无法变化了吗。

间隙锁虽然存在 X 型间隙锁和 S 型间隙锁,但是并没有什么区别,间隙锁之间是兼容的,即两个事务可以同时持有包含共同间隙范围的间隙锁,并不存在互斥关系,因为间隙锁的目的是防止插入幻影记录而提出的

Next-Key Lock

Next-Key Lock称为临键锁。是记录锁和间隙锁的合体形态。他会锁定这个范围,并且锁定记录本身不能被改变。

假设,表中有一个范围 id 为(3,5] 的 next-key lock,那么其他事务即不能插入 id = 4 记录,也不能修改 id = 5 这条记录。

所以,next-key lock 即能保护该记录,又能阻止其他事务将新纪录插入到被保护记录前面的间隙中。

虽然相同范围的间隙锁是多个事务相互兼容的,但对于记录锁,我们是要考虑 X 型与 S 型关系,X 型的记录锁与 X 型的记录锁是冲突的。

参考资料

MySQL InnoDB中的锁-自增锁(AUTO-INC Locks)-阿里云开发者社区 (aliyun.com)

MySQL 有哪些锁? | 小林coding (xiaolincoding.com)

快速解“锁”MySQL,拿下这7把钥匙,便能撬倒面试官 - 知乎 (zhihu.com)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

捶捶自己

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值