MySQL死锁产生的原因和解决方法

一.什么是死锁

要想知道MYSQL死锁产生的原因,就要知道什么是死锁?在了解什么是死锁之前,先来看一个概念:线程安全问题

 1.线程安全问题

 1.1什么是线程安全问题

线程安全问题,指的是在多线程环境当中,线程并发访问某个资源,从而导致的原子性,内存可见性,顺序性问题,以上这三个问题,会导致程序在执行的过程当中,出现了超出预期的结果

1.2 线程安全问题产生的原因是什么

根本原因是在于各个线程是由操作系统随机进行调度,并且各个线程是抢占式执行的.在多线程的环境当中,存在线程共享的数据,并且:

  • 当其中多个线程尝试修改共享变量的值,就有可能引发线程安全问题
  • 如果单个线程仅仅读但是不修改共享的变量就不会存在线程安全问题
  • 如果多个线程串行化,”排好队",挨个修改变量的值就不会引发线程安全问题

下面就上面列举的三种情况作出说明

(1).修改的操作的原子性问题

如果修改操作是原子的,那也不存在线程安全问题,但是大部分的修改都是非原子性的,如何理解修改操作的原子性呢?举个例子说明,见下面代码以及图示:

 static class Counter{int count;public void add(){count++;}}

如图所示,此时方法add就是尝试对成员变量"count"进行修改,修改为count+1,这个看似“原子”的操作,站在编译器底层,也就是汇编代码的角度,实际上是非原子的,分为以下三个步骤:

  • ①把count的值从内存当中读取出来;(load)
  • ②执行count++;(add)
  • ③把自增操作之后的count返回到内存当中。(save)

        假设count的值为0,这三个操作当中,如果有一个线程(Thread1)执行到load指令,然后被操作系统调度离开了CPU,接着另外一个线程(Thread2)被调度到CPU内核上面执行,连续执行load,add,save指令,此时,save的到内存当中的值为1。

        这个时候,Thread1右被重新调度到CPU内核上面执行,由于线程调度是有上下文的,因此,Thread1会继续在刚刚load的值的基础上面进行add,save操作。

        那么,此时Thread1 save的值仍然为1.与原先期待的两次++操作之后值为2的期待不一样,如果把load,add,save这三个操作,变成原子的,不可以在中间细分开,那么就会达到预计的两次++操作后值为2

总结:

        所谓的原子性问题,就是当线程进入一段代码块,还没有执行完毕的时候,允许其他线程进入这一段代码块,执行代码中的操作

(2).内存可见性问题

 内存可见性问题就是:一个线程针对变量进行修改,可以被其他的线程及时看到,这里先来了解一下(JMM)模型:

       在Java虚拟机当中,线程之间共享的变量存在于主内存(main memory)当中,每一个线程都拥有一个自己的"工作内存",当线程想要读取一个共享的变量的时候,会先把变量从主内存当中加载到自己的工作内存当中,然后先从工作内存当中修改这个变量的值,修改完之后再把修改操作同步到主内存当中,但是,在多线程的环境下面,一旦有线程修改了共享变量的值,由于编译器优化等等的原因,其他线程读取这个被修改的变量时候,不一定继续读取来自于主内存当中的值,而是仅仅继续读取自己的"工作内存“当中的值,这样,也就造成了读取数据的错误,也就引发了线程安全问题

对应到操作系统当中,就是这样的场景:

        当一个线程尝试修改内存当中变量的值的时候,是按照:把内存的变量的值读取到寄存器当中,然后首先修改寄存器当中的值,修改完成之后,再修改内存当中的值,但是,在多线程的环境下面,编译器有可能对这一系列指令进行优化,导致其中一个线程修改完内存当中的变量的值之后,其他线程想再次获取这个变量的值的时候,读取到的数据不是来自于内存,而是仅仅读取寄存器当中还没有来得及更新的值,这样,也就引发了线程安全问题

(3).代码顺序性问题

       编译器在保持原有代码逻辑仍然不变的情况下,对一系列指令进行了重新排序,如果在单线程的环境下面,是没有任何问题的,但是在多线程的环境下面,仍然执行指令重排序,就有可能被”优化“过之后的代码逻辑发生了改变,从而引发线程安全问题

2.那么怎么保证线程的安全性呢

这里引入synchronized关键字,还是以上面的代码执行案例说明

在代码当中,引入synchronized(),让synchronized修饰的代码块当中的所有指令变为原子性,从而实现让Thread1的三个操作"load,add,save"变成原子,就是使用sychronized修饰add方法.让”load,add,save"这三个操作变为不可再分的原子,代码如下:

 static class Counter{int count;synchronized public void add(){count++;}}

可以理解为互斥使用,也有的地方称为不可中断:

        加上锁(synchronized)之后,执行的效果就变成:当两个线程同时调用add方法的时候,其中一个线程,假如是(thread1)会竞争到"锁",另外一个线程(thread2)会进入阻塞状态,进入阻塞队列,也就是Thread类的状态当中的BLOCKED状态,等待竞争到锁的线程执行完add方法之后,此时会自动"解锁",也就是"缩释放”,Thread2会从阻塞队列当中离开,重新回到就绪队列当中,这个就是所谓的互斥使用:两个线程不可以同时拥有一把锁

        所谓的原子性,就是:当其中一个线程进入synchronized修饰的代码块当中,要么全部执行完毕,再允许其他的线程进入这一段代码块,要么都不执行

synchronized还保证了内存的可见性:
 在JMM当中,对于synchronized的内存可见性做了两条规定

  • 线程进入同步代码块的时候,将清空这个线程工作内存中共享变量的值,从而使得其他线程如果针对这个变量进行读取操作,一定要去主内存当中读取
  • 线程离开synchronized修饰的代码块的时候,将会把这个变量修改之后的状态同步到主内存当中,以此来保证共享变量的可见性。

可以理解为:synchronized可以保证内存的刷新

synchronized还保证了有序性:
       在synchronized代码块当中,每次只允许一个线程进入,可以理解为:在synchronized代码块内部,同一时刻是单线程执行的,因此,即使编译器针对一部分代码进行了优化,但是仍然不影响代码原有的执行逻辑。

        如果是多个线程同时进入同步代码块进行修改,此时再发生编译器优化,就有可能导致线程安全问题的发生。

        synchronized保证有序性的原理可以理解为:加synchronized后,依然会发生重排序,只不过,有同步代码块,可以保证只有一个线程执行同步代码中的代码,因此保证了有序性。需要注意的是:synchronized虽然保证了代码的有序性,但是,不可以阻止编译器进行指令重排序

 3. 思考一个问题:一个线程连续针对同一把锁,连续加锁两次,是否会产生死锁

一个线程连续针对同一把锁,连续加锁两次,是否会产生死锁?这个问题有两种情况:

  • 如果加锁后不会阻塞自己,那么这个锁就是一个特殊的锁,叫做可重入锁
  • 如果加锁后会阻塞自己,这个锁就不是可重入锁,如果阻塞了自己,那么就相当于产生了"死锁"

案例说明:

class Counter{public int count;synchronized public void add(){synchronized (this) {count++;}}
}

分析上面代码的执行流程:

        当多个线程并发调用add()方法的时候,其中一个线程(Thread1)可以获取到锁,其中,该线程针对调用的对象,count1加锁,此时该线程获得了锁,其余线程进入阻塞状态,被加锁的对象就是(count1)

       当进入add()方法内部的时候,遇到了synchronized修饰的代码块,此时Thread1继续尝试获取锁,前面也提到,加锁是线程针对对象加锁,可是当调用add()方法的时候,已经针对(this,也就是该方法的调用者count1)加锁了,当再次尝试获取锁的时候,是否会阻塞呢?

        回顾一下什么情况下会出现因为加锁而产生的线程阻塞当多个线程尝试竞争同一个未被加锁的对象的时候,没有竞争到锁的线程会进入阻塞状态,换而言之:如果一个对象被加锁了,那么其他线程想继续获取到这个对象的锁,那么其他线程都无法获取到,都会进入阻塞状态.

        那么此时,针对count1,也就是下一个synchronized代码块当中的this,不允许其他线程继续对this加锁,但是,如果Thread1此时也不允许对synchronized代码块中的代码加锁的话,Thread1也会继续进入阻塞状态,这样的话,所有的线程都进入了阻塞状态. 

         总结一下:如果一个线程针对同一把锁连续两次加锁,在第二次尝试加锁的时候,不会让自身进入阻塞等待的状态,那么称这个锁是可重入锁,根据代码的执行情况,synchronized就是可重入锁

 4.死锁产生的条件

上面介绍了线程安全相关的问题,以及synchronized锁,现在回过头来看看死锁产生的相关条件

举个形象点例子:

       张三和李四一起下馆子吃饺子,其中张三先拿到了酱油,张三对李四说:你把醋给我,我就把酱油给你。李四一听很不乐意,他反过来说,你如果把酱油先给我,我才会把醋给你。这个时候,两个人就僵持住了,谁也给不出,也就产生了“死锁"

(1).死锁准确的定义

死锁指的是在一组线程当中,由于线程竞争共享的资源,因为互相等待,因而导致了“永久”阻塞的现象,如果没有外力终止程序,将永远无法结束进程

(2).死锁产生的四个必要条件

1)互斥使用

        在上面的场景当中,线程张三拿到了锁(酱油);线程李四也拿到了锁(醋),这个时候,如果线程张三尝试获取被李四加锁的对象”醋“,那么张三就会获取锁失败,进而进入阻塞的状态,李四如果想获取张三的锁(酱油)也同理.所以,互斥使用的含义就是其中一个线程拿到了锁,其他与当前线程竞争锁的线程就必须进入阻塞状态,等待锁的释放。

2)不可抢占

      线程1拿到锁之后,线程1一定要主动释放锁,其他线程才可以获取到

3)请求和保持

       在上述的场景当中,线程张三获取到锁"酱油"之后,当他想再次获取到李四的醋的时候,不会因为去尝试获取李四的醋而丢失了本来属于自己的锁(酱油),所以,请求和保持的含义就是:线程1获取到对应的一把锁之后,如果想尝试再次获取其他的锁,当前拥有的锁仍然是保持的,不会丢失

4)循环等待

       如上面的场景,张三和李四都在等待对方先释放锁,但是都没有自行先释放锁,这个就是循环等待.再举个例子说明:例如有进程A、进程B和进程C三个进程,进程A请求的资源被进程B占用,进程B请求的资源被进程C占用,进程C请求的资源被进程A占用,于是形成了循环等待条件,如下图:

在线程当中,循环等待的场景就是:两个线程都在等待获取到对应的锁之后,都在等待对方释放锁,这就是循环等待

       注意:以上四个条件,缺一不可,是出现死锁的充分必要条件,但是,归根结底,还是因为synchronized是必须要等到获取到锁的线程执行完加锁的代码块之后,其他线程才能继续获取到当前的锁,但是,其他的锁不一定跟synchronized一样

5.如何处理死锁死锁

处理死锁有4种方法:分别为预防死锁、避免死锁、检测死锁和解除死锁

预防死锁:

        处理死锁最直接的方法就是破坏造成死锁的4个必要条件中的一个或多个,以防止死锁的发生

避免死锁应当从产生死锁的原因出发:

  • 1.尽量避免一个线程同时获取多把锁
  • 2.可以考虑一些特定的业务场景,使用lock.tryLock(timeout),限制加锁的时间,来代替synchronized,因为synchronized是一定要等到正在执行同步代码块的线程解锁之后,其他线程才可以获取锁
  • 3.针对锁进行"排序",规定所有线程加锁的顺序

        假设一个进程当中有n个线程和m把锁,如果想要加锁的话,可以在代码编写的时候对加锁的顺序进行规定,所有的线程都必须按照从1号锁到2号锁......到m号锁的顺序进行加锁,不可以打乱这个加锁的顺序

        在上面的案例中,如果分别对酱油和醋,这两把锁进行编号,规定酱油位1,醋位2,张三和李四都必须按照1,2的顺序来拿酱油和醋,就不会发生相互阻塞的情况了

 检测死锁:

        这种方法允许系统在运行过程中发生死锁,但是能够检测死锁的发生,并采取适当的措施清除死锁

解除死锁:

        当检测出死锁后,采用适当的策略和方法将进程从死锁状态解脱出来

在实际工作中,通常采用有序资源分配法和银行家算法这两种方式来避免死锁,大家可自行了解

好了,死锁的相关问题(线程安全,产生条件,避免死锁,处理死锁)就了解完了,归根到底,总结一下:

死锁指两个或多个事务相互等待对方释放锁,从而导致进程无法继续执行的一种情况.当一个事务需要锁定一些资源时,如果这些资源已经被其他事务锁定,则该事务必须等待其他事务释放锁,才能继续执行.如果多个事务同时等待对方释放锁,就会发生死锁

那么,在mysql当中,也会出现死锁,这里的死锁又是什么呢? 

 二.MYSQL死锁详解

要想了解MYSQL死锁相关知识,就需要知道MYSQL有哪些锁以及锁的级别

1.锁机制

见:关于mysql锁机制详解

几个锁概念:

  • 共享锁(S):允许多个事务同时读取同一份数据,但不允许对数据进行修改,当一个事务获得共享锁时,其他事务只能获得共享锁,不能获得排他锁 
  • 排他锁(X):只允许一个事务对数据进行修改,其他事务不能读取或修改该数据,当一个事务获得排他锁时,其他事务不能获得任何类型的锁 
  • 意向共享锁(IS):表示事务想要在某个数据上获得共享锁,但并不是真正的共享锁,只是一个辅助锁,当一个事务获得意向共享锁时,其他事务可以获得共享锁或意向共享锁,但不能获得排他锁 
  • 意向排他锁(IX):表示事务想要在某个数据上获得排他锁,但并不是真正的排他锁,只是一个辅助锁,当一个事务获得意向排他锁时,其他事务只能获得意向共享锁,不能获得任何类型的锁
  •  记录锁(record lock):这是一个索引记录锁,它是建立在索引记录上的锁(主键和唯一索引都算),很多时候,锁定一条数据,由于无索引,往往会导致整个表被锁住,建立合适的索引可以防止扫描整个表。如:开两个会话,两个事务,并且都不commit,该表有主键,两个会话修改同一条数据,第一个会话update执行后,第二个会话的update是无法执行成功的,会进入等待状态,但是如果update别的数据行就可以成功.      再例如:开两个会话,两个事务,并且都不commit,并且该表无主键无索引,那么第二个会话不管改什么都会进入等待状态。因为无索引的话,整个表的数据都被第一个会话锁定了。
  • 间隙锁(gap lock):MySQL默认隔离级别是可重复读,这个隔离级别为了避免幻读现象,引入了这个间隙锁,对索引项之间的间隙上锁

2.锁级别

mysql锁级别:页级、表级、行级

  • 表级锁:开销小,加锁快,不会出现死锁,锁定粒度大,发生锁冲突的概率最高,并发度最低
  • 行级锁:开销大,加锁慢,会出现死锁,锁定粒度最小,发生锁冲突的概率最低,并发度也最高
  • 页面锁:开销和加锁时间界于表锁和行锁之间,会出现死锁,锁定粒度界于表锁和行锁之间,并发度一般

从上面可以看出,表级锁不会产生死锁.所以解决死锁主要还是针对于最常用的InnoDB,而InnoDB存储引擎采用了一种叫作等待图(wait-for graph)的方法来自动检测死锁,如果发现死锁,就会自动回滚一个事务,下面来看看MySQL中的死锁案例

3.案例讲解

案例一

第一步

打开终端A,登录MySQL,将事务隔离级别设置为可重复读,开启事务后为account数据表中id为1的数据添加排他锁,如下所示:

mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)mysql> select * from account where id =1 for update;
+----+--------+---------+
| id | name   | balance |
+----+--------+---------+
|  1 | 张三   |     300 |
+----+--------+---------+
1 row in set (0.00 sec)
第二步

打开终端B,登录MySQL,将事务隔离级别设置为可重复读,开启事务后为account数据表中id为2的数据添加排他锁,如下所示:

mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)mysql> select * from account where id =2 for update;
+----+--------+---------+
| id | name   | balance |
+----+--------+---------+
|  2 | 李四   |     350 |
+----+--------+---------+
1 row in set (0.00 sec)
 第三步

在终端A为account数据表中id为2的数据添加排他锁,如下所示:

mysql> select * from account where id =2 for update;

 此时,线程会一直卡住,因为在等待终端B中id为2的数据释放排他锁

第四步

在终端B中为account数据表中id为1的数据添加排他锁,如下所示:

mysql> select * from account where id = 1 for update;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

 此时发生了死锁,通过如下命令可以查看死锁的日志信息:

show engine innodb status

通过命令行查看LATEST DETECTED DEADLOCK选项相关的信息,可以发现死锁的相关信息,或者通过配置innodb_print_all_deadlocks(MySQL 5.6.2版本开始提供)参数为ON,将死锁相关信息打印到MySQL错误日志中

案例二

假设有下面这一张表:

id(主键索引)no(非主键索引)name
11001小明
21002小李
31003小华
41004小黄

在这一张表当中,id为主键索引no为二级索引,name这一列没有任何索引的约束,现在这张表当中,有以上的一些数据,现在,有两个事务,一个事务A,另外一个事务B,下面,根据步骤,分析一下两个事务的执行流程:

第一步

       在上述两个事物当中,事务A首先开启了,然后执行一条查询的sql语句,也就是select...for update这样的语句,因为记录的最大值为1004,1007不在这一个范围当中,此时,事务A对于表当中no范围为(1004,+∞)的no索引加上了一把锁间隙锁

第二步

       事务B开启了,因为no值为1008的记录,不在范围(1004,+∞)的范围之内,因此,事务B也会加一个间隙锁,范围是(1004,+∞);由于间隙锁之间是互容的,因此事务B在执行select语句的时候,不会发生阻塞

第三步

       事务A执行了一条插入的索引为1007的数值,但是,由于事务B对于事务A插入的范围加上了间隙锁,因此事务A一定要等待到事务B释放锁,才可以继续执行

第四步

       事务B执行了一条插入的索引值为1008的sql语句,但是,由于事务A对于(1004,+∞)的范围加锁了,因此,事务B一定需要等待到事务A释放锁,才可以继续执行

可以看到,此时,两个事务互相阻塞了

那么在上面的insert语句中,是怎么加锁来执行的呢?

Insert语句在正常执行的时候,是不会生成锁结构的,它是靠聚簇索引自带的一个被称为trx_id的字段来作为隐式锁保护记录的,只有在特殊的情况下面,才会把隐式锁转化为显示锁,也就是真正加锁的过程

        举两个例子来说明隐式锁转换为显示锁的场景:

       (1).范围(a,b)内加有间隙锁,当有一条记录在范围(a,b)之内插入记录的时候,就会转化为显示锁

       (2).如果insert语句插入的记录已有的记录之间出现了主键,也无法插入

记录之间加有间隙锁,还是上面这个表:

id(主键索引)no(非主键索引)name
11001小明
21002小李
31003小华
41004小黄

此时,这一张表当中,假如有一条语句,执行:

select * from t_order where order_no = 1006 for update;

此时,事务如果插入一条语句,insert....values(1007...),这个时候,由于插入的数据正好在前一个sql语句插入的范围之内,因此会被阻塞

案例三

将投资的钱拆成几份随机分配给借款人,程序思路如下:

        投资人投资后,将金额随机分为几份,然后随机从借款人表里面选几个,然后通过一条条select for update 去更新借款人表里面的余额.例如:两个用户同时投资,A用户金额随机分为2份,分给借款人1,2; B用户金额随机分为2份,分给借款人2,1.由于加锁的顺序不一样,就出现了死锁

        那么怎么进行改进,避免出现死锁呢,解决:把所有分配到的借款人直接一次锁住,加锁就会从小到大的加锁,然后顺序执行,这就避免了死锁,代码如下:

select * from xxx where id in (xx,xx,xx) for update

在in里面的列表值mysql是会自动从小到大排序,加锁也是一条条从小到大加的锁

第一个会话:

        注意:关闭掉自动提交set autocommit=0;

mysql> select * from goods where goods_id in (2,3) for update;
+----+--------+------+---------------------+
| good_id | goods_name | price             |
+----+--------+------+---------------------+
|  2 | bbbb     | 1.00 |
|  3 | vvv     | 3.00 |
+----+--------+------+---------------------+

第二个会话:

select * from goods where goods_id in (3,4,5) for update;

锁等待中……

案例四

表锁的死锁情况

用户A访问表A(锁住了表A),然后又访问表B;另一个用户B访问表B(锁住了表B),然后企图访问表A;这时用户A由于用户B已经锁住表B,它必须等待用户B释放表B才能继续,同样用户B要等用户A释放表A才能继续,这样就产生了死锁

 

行锁的死锁情况

如果在事务中执行了一条没有索引条件的查询,引发全表扫描,把行级锁上升为全表记录锁定(等价于表级锁),多个这样的事务执行后,就很容易产生死锁和阻塞

解决办法

以上死锁基本是由于程序的BUG产生的,在对数据库的多表或单表操作时,尽量按照相同的顺序进行处理且避免同时锁定两个资源.必须同时锁定两个资源时,要保证在任何时刻都应该按照相同的顺序来锁定资源

select for update:

  • 如果有唯一索引,命中了唯一记录:行锁,互斥锁;
  • 如果有唯一索引,没命中:gap锁,另一个事务也可以获得这个gap锁,但是不能插入数据;(后续有死锁可能)
  • 如果有普通索引,命中了记录:行锁+gap锁;(后续有死锁可能)
  • 如果有普通索引,没有命中记录:gap锁,和情况2相同;(后续有死锁可能)
  • 如果没有索引,直接锁全表,互斥,直接阻塞别的事务
针对上面出现的情况,再次举个列子说明
CREATE TABLE `test_lock` (`id` int NOT NULL AUTO_INCREMENT,`user_name` varchar(255) DEFAULT NULL,`age` int DEFAULT NULL,`city` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`),UNIQUE KEY `idx_user_name` (`user_name`),KEY `idx_city` (`city`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4;insert into test_lock(id,user_name,age,city) values(1,'张三',10,'深圳');
insert into test_lock(id,user_name,age,city) values(5,'李四',32,'成都');
insert into test_lock(id,user_name,age,city) values (9,'王五',55,'广州');
主键索引 
BEGIN;
select * from test_lock where id = 1 for update;select * from performance_schema.data_locks;ROLLBACK;

查询条件是主键索引,命中数据库表记录时,一共会加两把锁:一把IX意向排他锁(表锁,不影响插入),一把对应主键的X排他锁(行锁,影响对应主键那一行的插入) 

唯一索引
BEGIN;
select * from test_lock where user_name ='张三' for update;select * from performance_schema.data_locks;ROLLBACK;

查询条件是唯一索引,命中数据库表记录时,一共会加三把锁:一把IX意向排他锁 (表锁,不影响插入),一把对应主键的X排他锁(行锁),一把对应唯一索引的X排他锁 (行锁) 

普通索引
BEGIN;
select * from test_lock where city ='成都' for update;select * from performance_schema.data_locks;ROLLBACK;

查询条件是普通索引,命中数据库表记录时,一共会加四把锁:一把IX意向排他锁 (表锁,不影响插入),一把对应主键的X排他锁(行锁),一把对应普通索引的X排他锁 (行锁),一把对应普通索引的Gap间隙锁 (锁住一个范围,会影响插入)

非索引
BEGIN;
select * from test_lock where age =10 for update;select * from performance_schema.data_locks;ROLLBACK;

 查询条件不是索引,命中或非命中数据库表记录时,都会加一个IX锁(表锁,不影响插入),每一行实际记录行的X锁,还有对应于supremum pseudo-record的虚拟全表行锁(通俗点讲,其实就是锁表了)

索引未命中
查询条件是中间值
BEGIN;
select * from test_lock where id = 4 for update;select * from performance_schema.data_locks;ROLLBACK;

查询条件是主键,未命中数据库表记录时,查询条件是中间值,会加一个IX锁(表锁,不影响插入),一把对应主键索引的X排他锁 (行锁),一把对应主键索引的Gap间隙锁 (锁住一个范围,会影响插入)

查询条件非中间值 
BEGIN;
select * from test_lock where id = 10  for update;select * from performance_schema.data_locks;ROLLBACK;

 查询条件是主键,未命中数据库表记录时,查询条件非中间值,会加一个IX锁(表锁,不影响插入),每一行实际记录行的X锁,还有对应于supremum pseudo-record的虚拟全表行锁(通俗点讲,其实就是锁表了)

4.死锁排查

查看死锁日志:

        通过 show engine innodb status命令查看近期死锁日志信息,主要关注日志中的 LATEST DETECTED DEADLOCK部分,死锁记录只记录最近一个死锁信息,若要将每个死锁信息都保存到错误日志,启用以下参数:

show variables like 'innodb_print_all_deadlocks';

查当前正在运行的InnoDB事务的信息,可以kill长期占用锁的事务对应的线程id

select * from information_schema.INNODB_TRX;

5.MySQL中,避免死锁的几种方式

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

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

  3. 尽量减少查询条件的范围,尽量避免间隙锁或缩小间隙锁的范围

  4. 尽量控制事务的大小,避免长事务,减少一次事务锁定的资源数量,缩短锁定资源的时间和占用范围,这样可以大大减少死锁产生的概率

  5. 如果一条SQL语句涉及事务加锁操作,则尽量将其放在整个事务的最后执行

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

  7. 设置任务超时等待时间:当在一个任务的等待时间超过了这个时间之后,就进行回滚;在 InnoDB 中,参数innodb_lock_wait_timeout是用来设置超时时间的,默认值时50秒

  8. 主动开启死锁检测: 将参数innodb_deadlock_detect设置为 on,当innodb检测发现死锁之后,就会进行回滚死锁的事务,但是死锁检测会比较耗资源

  9. 对于更新频繁的字段,采用唯一索引的设置方案:例如在上面的例子当中,可以把no字段设置成唯一索引

  10. 保证资源的加锁顺序,避免循环等待的产生

  11. 使用乐观锁mvcc机制,读取数据不上锁,在读情况下共享资源

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/702386.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RocketMQ快速实战以及集群架构原理详解

RocketMQ快速实战以及集群架构原理详解 组成部分 启动Rocket服务之前要先启动NameServer NameServer 提供轻量级Broker路由服务,主要是提供服务注册 Broker 实际处理消息存储、转发等服务的核心组件 Producer 消息生产者集群,通常为业务系统中的一个功…

板块二 JSP和JSTL:第四节 EL表达式 来自【汤米尼克的JAVAEE全套教程专栏】

板块二 JSP和JSTL:第四节 EL表达式 一、什么是表达式语言二、表达式取值(1)访问JSP四大作用域(2)访问List和Map(3)访问JavaBean 三、 EL的各种运算符(1).和[ ]运算符&…

《The Art of InnoDB》第二部分|第4章:深入结构-磁盘结构-redo log

4.3 redo log 目录 4.3 redo log 4.3.1 redo log 介绍 4.3.2 redo log 的作用 4.3.3 redo log file 结构 4.3.4 redo log 提交逻辑 4.3.5 redo log 持久化逻辑 4.3.6 redo log 检查点 4.3.7 小结

汇编语言与接口技术实践——秒表

1. 设计要求 基于 51 开发板,利用键盘作为按键输入,将数码管作为显示输出,实现电子秒表。 功能要求: (1)计时精度达到百分之一秒; (2)能按键记录下5次时间并通过按键回看 (3)设置时间,实现倒计时,时间到,数码管闪烁 10 次,并激发蜂鸣器,可通过按键解除。 2. 设计思…

抖音数据抓取工具|短视频下载工具|视频内容提取软件

一、开发背景: 随着抖音平台的流行,越来越多的人希望能够下载抖音视频以进行个人收藏或分享。然而,目前在网上找到的抖音视频下载工具功能单一,操作繁琐,无法满足用户的需求。因此,我们决定开发一款功能强大…

java面试题之mysql篇

1、数据库索引 ​​​​​​​ 索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可快速访问数据库表中的特定信息。如果想按特定职员的姓来查找他或她,则与在表中搜索所有的行相比,索引有助于更快地获取信息。 索引的一个主要…

编程笔记 Golang基础 030 接口

编程笔记 Golang基础 030 接口 一、接口的定义:二、接口的实现:三、接收者类型四、应用示例五、接口的意义 在Go语言中,接口是一种类型定义,它描述了一组方法签名,任何实现了这些方法的类型都隐式地实现了这个接口。这…

k8s-创建命名空间的方法

使用命令式创建namespace kubectl create namespace test-namespace查看命名空间 kubectl get namespace使用声明式创建命名空间 a. 编写dev-namespace.yaml文件 apiVersion: v1 kind: Namespace metadata:name: dev-namespaceb. 使用dev-namespace.yaml,yaml文件创…

音视频开发之旅(69)-SD图生图

目录 1. 效果展示 2. ControlNet介绍 3. 图生图流程浅析 4. SDWebui图生图代码流程 5. 参考资料 一、效果展示 图生图的应用场景非常多,比较典型的应用场景有风格转化(真人与二次元)、线稿上色、换装和对图片进行扩图等,下面…

TCP/IP协议栈:模拟器实现基本的L2和L3功能

在C中实现的TCPI/IP网络堆栈模拟器。该模拟器实现基本的第2层(MAC地址,Arp)和第3层(路由,IP)功能。 TCP/IP协议栈是一个网络通信的基础架构,包含了多层次的协议和功能。在模拟实现基本的L2和L3…

神经网络2-卷积神经网络一文深度读懂

卷积神经网络(Convolutional Neural Network, CNN)是一类包含卷积计算且具有深度结构的前馈神经网络(Feedforward Neural Networks),主要用于图像识别、语音识别和自然语言处理等任务,是深度学习&#xff0…

使用决策树算法预测隐形眼镜类型

目录 谷歌笔记本(可选) 编写算法:决策树 准备数据:拆分数据集 测试算法:构造注解树 使用算法:预测隐形眼镜类型 谷歌笔记本(可选) from google.colab import drive drive.mount…

ubuntu20.04 tvm 安装教程

ubuntu20.04 tvm 安装教程: 参考: 1. https://tvm.hyper.ai/docs/install/from_source/ 2. https://blog.csdn.net/wenwen_2020/article/details/134856293 步骤: 1. 创建容器:docker run -itd --name tvm --gpusall --ipchost…

Springboot之压缩逻辑源码跟踪流程

背景 在项目开发过程中,前后端参数比较多,导致网络传输耗时比较多,因此想将数据压缩传输,以减少网络传输的耗时,从而减少接口的响应时间,可以自己实现,但是spring相关的框架已经内置了该功能&am…

堆排序、快速排序和归并排序

堆排序、快速排序和归并排序是所有排序中最重要的三个排序,也是难度最大的三个排序;所以本文单独拿这三个排序来讲解 目录 一、堆排序 1.建堆 2.堆排序 二、快速排序 1.思想解析 2.Hoare版找基准 3.挖坑法找基准 4.快速排序的优化 5.快速排序非…

C语言--左旋字符/右旋字符实现及其判断

1.题目解释 左旋就是把对应的左边的放到右边 例如ABCDEF左旋2个字符就是BCDEFAB&#xff0c;左旋3个字符就是DEFABC&#xff1b; 2.代码实现 void leftmove(char* str, int k) {int j 0;assert(str);for (j 0; j < k; j){char temp *str;int len strlen(str);int i …

单个文件实现cpu的信息检测:ruapu.h的学习笔记

https://github.com/nihui/ruapu是nihui大佬开发的用单文件检测CPU特性的项目 ruapu.h的使用 "ruapu.h"主要提供了两个函数 ruapu_init 和 ruapu_supports&#xff0c;分别用于初始化和检测指令集支持。 // 使用示例见&#xff1a;https://github1s.com/nihui/rua…

MyBatis核心配置文件

1、properties属性&#xff1a; 将变量提取出来变成全局变量 enable-default-value&#xff1a;启动默认值 数据库环境四要素 2、settings属性 &#xff1a; 开启二级缓存&#xff0c;开启延迟加载懒加载 消极懒加载积极懒加载 <setting name"cacheEnable" valu…

Spring数据脱敏实现

在当今的数字化时代&#xff0c;数据安全和个人隐私保护变得日益重要。为了遵守各种数据保护法规&#xff0c;如欧盟的GDPR&#xff08;通用数据保护条例&#xff09;&#xff0c;企业在处理敏感信息时需要格外小心。数据脱敏是一种常见的技术手段&#xff0c;用于隐藏敏感数据…

Servlet使用Cookie和Session

一、会话技术 当用户访问web应用时&#xff0c;在许多情况下&#xff0c;web服务器必须能够跟踪用户的状态。比如许多用户在购物网站上购物&#xff0c;Web服务器为每个用户配置了虚拟的购物车。当某个用户请求将一件商品放入购物车时&#xff0c;web服务器必须根据发出请求的…