在实际工作过程中遇到了数据库死锁的问题,在查阅资料的时候遇到了各种锁的概念。
共享锁、排它锁、表级锁、行级锁、记录锁、间隙锁、临键锁、插入意向锁、自增锁等等等等,这些概念如果能够弄清楚其中区别自然最好,但理清这些概念是在是太麻烦了。而且从实际工作情况出发,理清这些概念再去解决实际的工作问题效率太低。
所以我这里基于MySQL数据库的Innodb引擎(注意是MySQL数据库的Innodb引擎,其它引擎或数据库以下总结不完全适用,但有一定的参考价值),以及实际的工作场景,做了以下梳理:
又被称为读锁、S锁等,事务在对数据进行查询,需要先获取数据的共享锁
又被称为写锁、X锁等,事务在对数据进行修改时,需要先获取数据的排它锁
这里“需要先获取数据的共享锁、排它锁”不代表说操作哪条数据,就只锁哪条数据,有可能只操作一条数据,但却锁了某个数据区间、甚至是整张表。锁的范围见下方解释
共享锁和排它锁的兼容和互斥关系想必大家都清楚,这里还是做个简单说明:
如果一个事务给表已经加了S锁,则:
别的事务可以继续获得该表的S锁,也可以获得该表中某些记录的S锁。
别的事务不可以继续获得该表的X锁,也不可以获得该表中某些记录的X锁。
如果一个事务给表加了X锁,那么
别的事务不可以获得该表的S锁,也不可以获得该表某些记录的S锁。
别的事务不可以获得该表的X锁,也不可以继续获得该表某些记录的X锁。
即整张表的数据都被锁定了。
sql执行语句没有合理的构建索引;
MySQL优化器自行进行了全表扫描
即只锁定一行的数据。
即只锁定一个区间(间隙)的数据,这个产生场景最多,所以放在最后特别说明。
既然间隙锁锁定的是一个区间,那么这个区间是如何划分的呢?
根据检索条件向左寻找最靠近检索条件的记录值A,作为左区间,如果找不到记录值A,则左区间为无限小;
向右寻找最靠近检索条件的记录值B,作为右区间,如果找不到记录值B,则右区间为无限大
详细说明可以参考这里:https://blog.csdn.net/sfh2018/article/details/121016466,后续测试模拟的表格结构也参考自这里
平常学习中可以自行建表模拟事务的执行,虽然不难,但还是在这里说明下,以Navicat为例:
新开一个sql执行界面运行命令,即可开启一个事务
start transaction ; #开启事务
insert into test_gap_table value(16,5);#事务里面可以执行多条SQL语句
COMMIT#如果没有这条指令,则事务将会一直不提交,锁将一直持有,由此便可模拟多个事务进行锁的抢占
CREATE TABLE `test_gap_table` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`number` bigint DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_number` (`number`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
INSERT INTO test_gap_table ( id, number ) VALUE ( 1, 2 );
INSERT INTO test_gap_table ( id, number ) VALUE ( 3, 4 );
INSERT INTO test_gap_table ( id, number ) VALUE ( 6, 5 );
INSERT INTO test_gap_table ( id, number ) VALUE ( 8, 5 );
INSERT INTO test_gap_table ( id, number ) VALUE ( 10, 5 );
INSERT INTO test_gap_table ( id, number ) VALUE ( 13, 11 );
查询现在的死锁线程,然后强制杀死线程。线程id为第一步中查询到的trx_mysql_thread_id的值
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
kill 11135
以上的方法只是解决掉了当前存在的死锁情况,并未从根本上解决死锁的产生。因此还需要进行死锁日志的查询,判断出导致死锁是哪些业务产生的,然后针对性的进行修改。
show engine innodb status
navicate显示结果如下,没法直接浏览,直接选中返回结果,复制内容到文本编辑器即可
大家看到日志时估计是非常的头大,可能有1000多行的奇奇怪怪的内容,直接就放弃了。其实我们只需要注意关键字即可。
明白了以上的关键字之后,就能够很明白的知道哪些表之间产生了死锁,此时就可以根据实际的业务修改代码或者sql逻辑解决死锁问题了。例如上面的图示,就是两个事务互相持有了对方所需要的锁,互相等待对方释放锁,因此产生了死锁问题。
首先是老生常谈的产生死锁的四个条件:互斥条件、请求和保持条件、不可剥夺条件、循环等待条件。那么就有以下的消除死锁的方法:
我们经常在代码中一个方法就是一个事务,而一个方法中可能会有多条的sql操作,此时就应该分析,这些sql是否可以拆分到不同的方法中,用不同的事务去承载,由此就可以减少死锁出现的概率。
这个应该是最简单高效的方法,这种方式可以让进行sql操作时尽量产生的是行锁或间隙锁,避免产生表锁进而形成死锁。
有时因sql操作顺序不合理,会导致事务互相持有对方需要的锁,此时就需要调整sql执行的顺序,合理规划sql锁资源请求步骤。
本文从锁的概念出发,基于实际工作情况对一些概念性问题进行了精简。所以如果有遗漏或错误欢迎大家指正。本文参考的博客如下: