mysql 等待_MySQL锁等待和死锁

使用数据库时,有时会出现死锁。对于实际应用来说,就是出现系统卡顿。

死锁是指两个或两个以上的事务在执行过程中,因争夺资源而造成的一种互相等待的现象。就是所谓的锁资源请求产生了回路现象,即死循环,此时称系统处于死锁状态或系统产生了死锁。常见的报错信息为“Deadlock found when trying to get lock...”。

5f92a10407ba80e457eac24136e5653d.png

死锁展示

上图中,很明显是右侧的四辆汽车造成了死锁。

死锁发生以后,只有部分或完全回滚其中一个事务,才能打破死锁。多数情况下只需要重新执行因死锁回滚的事务即可。下面我们通过一个实例来了解死锁是如何产生的。

例 1

为了方便读者阅读,操作之前我们先查询 tb_student 表的数据和表结构。

mysql> SELECT * FROM tb_student;

+----+------+------+------+------+

| id | name | age | sex | num |

+----+------+------+------+------+

| 1 | 张三 | 31 | 男 | 4 |

| 2 | 李四 | 28 | 男 | 4 |

| 3 | 王五 | 13 | 女 | 4 |

| 4 | 张四 | 13 | 女 | 4 |

| 5 | 王四 | 15 | 男 | 4 |

| 6 | 赵六 | 12 | 女 | 4 |

+----+------+------+------+------+

6 rows in set (0.01 sec)

mysql> DESC tb_student;

+-------+-------------+------+-----+---------+----------------+

| Field | Type | Null | Key | Default | Extra |

+-------+-------------+------+-----+---------+----------------+

| id | int(4) | NO | PRI | NULL | auto_increment |

| name | varchar(25) | NO | | NULL | |

| age | int(11) | YES | MUL | NULL | |

| sex | char(1) | YES | | NULL | |

| num | int(11) | YES | | NULL | |

+-------+-------------+------+-----+---------+----------------+

5 rows in set (0.00 sec)

以下操作需要打开两个会话窗口,即下面所提到的 A窗口和 B窗口。

在 A窗口中执行以下命令:

mysql> BEGIN;

mysql> UPDATE tb_student SET num=5 WHERE age=13;

Query OK, 2 rows affected (0.04 sec)

Rows matched: 2 Changed: 2 Warnings: 0

紧接着在 B窗口中执行以下命令。由于 age 是索引字段,与 A窗口中更新的是不同行的数据,所以这时不会出现锁等待现象。

mysql> BEGIN;

mysql> UPDATE tb_student SET num=8 WHERE age=15;

Query OK, 1 row affected (0.01 sec)

Rows matched: 1 Changed: 1 Warnings: 0

然后在 A窗口中,执行以下命令,这时就会出现锁等待现象了。

mysql> UPDATE tb_student SET num=10 WHERE age=15;

最后在 B窗口中,执行以下命令,这时会出现相互等待资源的现象,也就是死锁现象。

mysql> UPDATE tb_student SET num=12 WHERE age=13;

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

我们可以通过 SHOW ENGINE INNODB STATUS 命令查看死锁的信息,运行结果如下(这里只展示了部分信息):

LATEST DETECTED DEADLOCK

------------------------

2020-08-24 16:22:23 0x3944

*** (1) TRANSACTION:

TRANSACTION 22656, ACTIVE 108 sec starting index read

mysql tables in use 1, locked 1

LOCK WAIT 5 lock struct(s), heap size 1136, 6 row lock(s), undo log entries 2

MySQL thread id 33, OS thread handle 8808, query id 1689 localhost ::1 root updating

UPDATE tb_student SET num=10 WHERE age=15

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 197 page no 8 n bits 80 index index_age of table `test`.`tb_student` trx id 22656 lock_mode X waiting

Record lock, heap no 5 PHYSICAL RECORD: n_fields 2; compact format; info bits 0

0: len 4; hex 8000000f; asc ;;

1: len 4; hex 80000005; asc ;;

......

通过以上日志,我们就能确定造成死锁的事务和 SQL 语句。

死锁检测

InnoDB 的并发写操作会触发死锁,同时 InnoDB 也提供了死锁检测机制。通过设置 innodb_deadlock_detect 参数的值来控制是否打开死锁检测。

innodb_deadlock_detect = ON :默认值,打开死锁检测。数据库发生死锁时,系统会自动回滚其中的某一个事务,让其它事务可以继续执行。

innodb_deadlock_detect = OFF:关闭死锁检测。发生死锁时,系统会用锁等待来处理。

锁等待是指在事务过程中产生的锁,其它事务需要等待上一个事务释放锁,才能占用该资源。如果该事务一直不释放,就需要持续等待下去,直到超过了锁等待时间。当超过锁等待允许的最大时间,就会出现死锁,然后当前事务执行失败,自动执行回滚操作。

MySQL 通过 innodb_lock_wait_timeout 参数控制锁等待的时间,单位是秒。

mysql> SHOW VARIABLES LIKE '%innodb_lock_wait%';

+--------------------------+-------+

| Variable_name | Value |

+--------------------------+-------+

| innodb_lock_wait_timeout | 120 |

+--------------------------+-------+

1 row in set, 1 warning (0.02 sec)

在实际应用中,我们要尽量防止锁等待现象的发生,下面介绍几种避免死锁的方法:

如果不同程序会并发存取多个表,或者涉及多行记录时,尽量约定以相同的顺序访问表,这样可以大大降低死锁的发生。

业务中要及时提交或者回滚事务,可减少死锁产生的概率。

在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率。

对于非常容易产生死锁的业务部分,可以尝试使用升级锁粒度,通过表锁定来减少死锁产生的概率(表级锁不会产生死锁)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值