写点什么

一文看懂 Mysql 锁

  • 2022 年 9 月 01 日
    北京
  • 本文字数:3572 字

    阅读完需:约 12 分钟

一文看懂Mysql锁

Mysql 锁

既然说到了 Mysql 锁,那么什么是 Mysql 锁呢?本质上来说,锁是一种协调多个进程或者多个线程对某一资源的访问的机制,Mysql 通过锁实现了事务的隔离级别;通俗来说就是当应用访问某一资源时,会对当前资源加锁,防止其他请求来访问该资源,导致产生问题,待当前应用对这一资源访问结束后再释放锁供其他请求获取该资源。

Mysql 锁的分类

Mysql 锁主要从以下几个方面来分类

从性能上

Mysql 锁可以分为悲观锁和乐观锁:

悲观锁:就是说不相信在整个数据处理过程中其他应用不会修改数据库中的数据,一旦获取资源就立刻加锁,在整个数据处理过程中都将相应数据锁定,其他应用无法修改数据。读数据需要加锁,此时不能对这些数据进行修改操作;修改数据也需要加锁,此时不能对这些数据进行读取操作。

乐观锁:就是说为数据增加一个版本标识,查询数据时会将版本号标识一起读出来,在更新数据时,令版本号加 1,提交数据时与数据库记录版本对比,如果提交版本号大于数据库中版本号则修改,否则不能修改。

从操作类型上

Mysql 锁分为读锁和写锁:

读锁:也叫做共享锁,同一份数据可以加多个读锁。

写锁:也叫做排他锁,如果当前数据的写锁未释放,则不能再加写锁或读锁。

从锁的粒度上

Mysql 锁分为表锁、行锁和页面锁:

表锁:就是整个表加锁,开销小,加锁快,一般不会死锁,锁粒度比较大,发生冲突概率性高。

行锁:就是在数据行上加锁,开销比较大,加锁慢,可能会死锁,锁粒度最小,发生冲突概率小,并发高。

页面锁:就是页面级别加锁,开销介于表锁和行锁之间,可能会死锁,锁粒度介于表锁和行锁之间。

从锁的粒度上还有两种锁:间隙锁和临键锁,这两种锁遇到的不到,这里就不再详细阐述了,有兴趣的小伙伴可以自行搜索相关文章介绍,下面再说一下 Mysql 死锁产生条件。

死锁

既然上面说到了可能会死锁,那么死锁是怎么产生的呢?什么情况下会出现死锁?如何避免死锁呢?

死锁的四个条件

产生死锁需要四个必要条件:互斥条件不可剥夺条件请求保持条件循环等待条件

互斥条件:就是说某个资源只能被以及各进程或者线程占用,其他进程或线程请求该资源必须等待。

不可剥夺条件:就是说某个资源在该进程或线程使用完之前,不能强行被剥夺,只能由其自行释放。

请求保持条件:有一个进程或者线程已经获得了一个资源,现在要请求其他资源,但请求的其他资源被其他进程占用,此时请求被阻塞,并且不会释放自己的资源。

循环等待条件:有 A、B、C 三个进程或者线程同时占用自己的资源同时又想获取别的资源

图中 A 占用自己资源同时请求 B 的资源,B 占用自己资源同时请求 C 资源,C 占用自己资源同时请求 A 资源,这样就行程了循环等待。

死锁的验证

开启客户端 A、B 连接 Mysql 数据库,只需要通过其中一个客户端 A 创建数据库,数据表即可

create database mytest;
复制代码

查看数据库创建成功与否

show databases;
复制代码

执行结果如图

选中当前数据库,创建数据表 sys_user 并插入数据

use mytest;
CREATE TABLE `sys_user` ( `user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID', `dept_id` bigint(20) DEFAULT NULL COMMENT '部门ID', `login_name` varchar(30) NOT NULL COMMENT '登录账号', `user_name` varchar(30) DEFAULT '' COMMENT '用户昵称', `user_type` varchar(2) DEFAULT '00' COMMENT '用户类型(00系统用户 01注册用户)', `email` varchar(50) DEFAULT '' COMMENT '用户邮箱', `phonenumber` varchar(11) DEFAULT '' COMMENT '手机号码', `sex` char(1) DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)', `avatar` varchar(100) DEFAULT '' COMMENT '头像路径', `password` varchar(50) DEFAULT '' COMMENT '密码', `salt` varchar(20) DEFAULT '' COMMENT '盐加密', `status` char(1) DEFAULT '0' COMMENT '帐号状态(0正常 1停用)', `del_flag` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', `login_ip` varchar(50) DEFAULT '' COMMENT '最后登陆IP', `login_date` datetime DEFAULT NULL COMMENT '最后登陆时间', `create_by` varchar(64) DEFAULT '' COMMENT '创建者', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_by` varchar(64) DEFAULT '' COMMENT '更新者', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `remark` varchar(500) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`user_id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户信息表';
INSERT INTO `sys_user` VALUES ('1', '103', 'admin', '若依', '00', 'ry@163.com', '15888888888', '1', '', '29c67a30398638269fe600f73a054934', '111111', '0', '0', '127.0.0.1', '2020-11-24 13:32:43', 'admin', '2018-03-16 11:33:00', 'ry', '2020-11-24 13:32:15', '管理员');INSERT INTO `sys_user` VALUES ('2', '105', 'ry', '若依', '00', 'ry@qq.com', '15666666666', '1', '', '8e6d98b90472783cc73c17047ddccf36', '222222', '0', '0', '127.0.0.1', '2018-03-16 11:33:00', 'admin', '2018-03-16 11:33:00', 'ry', '2018-03-16 11:33:00', '测试员');
复制代码

在当前客户端 A 设置事务隔离级别可重复读,开启事务,为 user_id=1 数据添加排他锁

SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;START TRANSACTION;SELECT * FROM sys_user WHERE user_id=1 FOR UPDATE;
复制代码

命令行执行结果

同样在客户端 B 对 user_id=2 进行相同操作,执行结果如图

之后在终端 A 为 user_id=2 添加排他锁,线程卡住,返回错误信息

错误信息

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
复制代码

此时在终端 B 为 user_id=1 添加排他锁,返回错误信息


错误信息内容

1213 - Deadlock found when trying to get lock; try restarting transaction
复制代码

通过命令查看 Mysql 死锁信息

SHOW ENGINE INNODB STATUS;
复制代码

结果如图


日志主要内容

...2022-09-01 16:07:39 0x7f7dac0b0640*** (1) TRANSACTION:TRANSACTION 1306, ACTIVE 29 sec starting index readmysql tables in use 1, locked 1LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s)MySQL thread id 12, OS thread handle 140177734313536, query id 157 localhost root statisticsSELECT * FROM sys_user WHERE user_id=2 FOR UPDATE*** (1) WAITING FOR THIS LOCK TO BE GRANTED:RECORD LOCKS space id 25 page no 3 n bits 72 index PRIMARY of table `mytest`.`sys_user` trx id 1306 lock_mode X locks rec but not gap waitingRecord lock, heap no 3 PHYSICAL RECORD: n_fields 22; compact format; info bits 0...*** (2) TRANSACTION:TRANSACTION 1307, ACTIVE 18 sec starting index readmysql tables in use 1, locked 13 lock struct(s), heap size 1136, 2 row lock(s)MySQL thread id 20, OS thread handle 140177734043200, query id 158 49.7.61.210 root statisticsSELECT * FROM sys_user WHERE user_id=1 FOR UPDATE*** (2) HOLDS THE LOCK(S):RECORD LOCKS space id 25 page no 3 n bits 72 index PRIMARY of table `mytest`.`sys_user` trx id 1307 lock_mode X locks rec but not gapRecord lock, heap no 3 PHYSICAL RECORD: n_fields 22; compact format; info bits 0...*** (2) WAITING FOR THIS LOCK TO BE GRANTED:RECORD LOCKS space id 25 page no 3 n bits 72 index PRIMARY of table `mytest`.`sys_user` trx id 1307 lock_mode X locks rec but not gap waitingRecord lock, heap no 2 PHYSICAL RECORD: n_fields 22; compact format; info bits 0...*** WE ROLL BACK TRANSACTION (2)------------TRANSACTIONS------------Trx id counter 1308Purge done for trx's n:o < 1301 undo n:o < 0 state: running but idleHistory list length 0LIST OF TRANSACTIONS FOR EACH SESSION:---TRANSACTION 421653067758304, not started0 lock struct(s), heap size 1136, 0 row lock(s)---TRANSACTION 1306, ACTIVE 43 sec3 lock struct(s), heap size 1136, 2 row lock(s)MySQL thread id 12, OS thread handle 140177734313536, query id 157 localhost root...
复制代码

或者可以通过命令查看锁信息

select * from information_schema.innodb_trx;
复制代码

查询结果如图

既然模拟了死锁,也知道死锁产生的四个条件,那么死锁该如何避免呢?

死锁的避免

1.尽量让数据检索都通过索引完成,避免无效索引导致行锁升级成了表锁;

2.合理设计索引,尽量缩小锁的范围;

3.尽量控制事务大小,减少一次事务锁定的资源数量,缩短资源锁定时间;

4.尽量减少查询条件范围;

5.尽可能使用低级别的事务隔离机制。

总结

以上就是 Mysql 锁相关内容,希望对大家有所帮助。

发布于: 刚刚阅读数: 3
用户头像

让技术不再枯燥,让每一位技术人爱上技术 2022.07.22 加入

还未添加个人简介

评论

发布
暂无评论
一文看懂Mysql锁_MySQL_六月的雨在infoQ_InfoQ写作社区