数据人成长交流的一站式服务平台

网站首页 > 数据编程 正文

「每天一道面试题」Mysql死锁解决

nandi88 2024-11-09 14:13:23 数据编程 2 ℃

Mysql死锁解决

什么是死锁

所谓死锁:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

由于资源占用是互斥的,当某个进程提出申请资源后,使得有关进程在无外力协助下,永远分配不到必需的资源而无法继续运行,这就产生了一种特殊现象死锁。

产生死锁条件

产生死锁的四个必要条件:

  1. 互斥条件:一个资源每次只能被一个进程使用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

死锁的影响

当产生某表死锁的一开始,所有涉及这张表的操作都将受到阻塞。假设这张表在业务逻辑上是读写频繁的,那就会使很多操作在那里排队等待,而排队等待会占用数据库连接,当该达到该数据库连接数的最大承载数之后,就会使所有数据库操作均无法再继续下去,致使数据库各项指标异常,导致整个环境崩溃。

在生产环境中出现这种问题,那是相当致命的,当发现数据库指标异常时因快速处理!

如何排查死锁

查询数据库进程

主要看 State 字段,如果出现大量 waiting for ..lock 即可判定死锁:

SHOW FULL PROCESSLIST;

注意:需要拥有 root 组权限(supper),否则只能看到当前用户的进程,无法查询所有。

查看当前的事务

SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;

INNODB_TRX 表包含信息关于每个事务(排除只读事务)当前执行的在 InnoDB,包含是否事务是等待一个锁, 当事务启动后, SQL 语句事务是正在执行。

INNODB_TRX Columns 相关列信息:

字段

描述

trx_id

innodb 存储引擎内部事务唯一的事务 id。

trx_state

当前事务的状态。

trx_started

事务开始的时间。

trx_requested_lock_id

等待事务的锁 id,如 trx_state 的状态为 LOCK WAIT,那么该值代表当前事务之前占用锁资源的 id,如果 trx_state 不是 LOCK WAIT 的话,这个值为 null。

trx_wait_started

事务等待开始的时间。

trx_weight

事务的权重,反映了一个事务修改和锁住的行数。在 innodb 的存储引擎中,当发生死锁需要回滚时,innodb 存储引擎会选择该值最小的事务进行回滚。

trx_mysql_thread_id

正在运行的 mysql 中的线程 id,show full processlist 显示的记录中的 thread_id。

trx_query

事务运行的sql语句,在实际中发现,有时会显示为 null 值,当为 null 的时候,就是 t2 事务中等待锁超时直接报错(ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction)后,trx_query 就显示为 null 值。 比如事务 t2 正在运行 trx_query: update test.t1 set b='t2' where a=1 的 sql 语句,t1 先执行,所以是 trx_state: RUNNING 先申请的资源一直在运行,而 t2 后run 的所以是 trx_state: LOCK WAIT 一直在等待 t1 执行完后释放资源。 但是并不能仔细判断锁的一些详细情况,我们需要再去看当前锁定的事务表数据。

查看当前锁定的事务

SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;

INNODB_LOCKS 表包含信息关于每个锁一个 InnoDB 事务已经请求,但是没有获得锁,每个 lock 一个事务持有是堵塞另外一个事务。INNODB_LOCKS Columns 相关列信息:

字段

描述

lock_id

锁的 id 以及被锁住的空间 id 编号、页数量、行数量。

lock_trx_id

锁的事务 id。

lock_mode

锁的模式。

lock_type

锁的类型,表锁还是行锁。

lock_table

要加锁的表。

lock_index

锁的索引。

lock_space

innodb 存储引擎表空间的 id 号码。

lock_page

被锁住的页的数量,如果是表锁,则为 null 值。

lock_rec

被锁住的行的数量,如果表锁,则为 null 值。

lock_data

被锁住的行的主键值,如果表锁,则为 null 值。

如以下查询 :

mysql> select * from INNODB_LOCKSG

输出行一:

lock_id: 3015646:797:3:2

lock_trx_id: 3015646

lock_mode: X

lock_type: RECORD

lock_table: test.t1

lock_index: PRIMARY

lock_space: 797

lock_page: 3

lock_rec: 2

lock_data: 1

行二:

lock_id: 3015645:797:3:2

lock_trx_id: 3015645

lock_mode: X

lock_type: RECORD

lock_table: test.t1

lock_index: PRIMARY

lock_space: 797

lock_page: 3

lock_rec: 2

lock_data: 1

这里我们可以看到当前的锁信息了,2 个事务都锁定了,看相同的数据 lock_space: 797、lock_page: 3、lock_rec: 2 可以得出事务 t1 和事务 t2 访问了相同的 innodb 数据块,再通过 lock_data 字段信息 lock_data: 1,看到锁定的数据行都是主键为 1 的数据记录,可见 2 个事务 t1 和 t2 都申请了相同的资源,因此会被锁住,事务在等待。

通过 lock_mode: X 值也可以看出事务 t1 和 t2 申请的都是排它锁。

PS:当执行范围查询更新的时候,这个 lock_data 的值并非是完全准确。当我们运行一个范围更新时,lock_data 只返回最先找到的第一行的主键值 id;另外如果当前资源被锁住了,与此同时由于锁住的页因为 InnoDB 存储引擎缓冲池的容量,而导致替换缓冲池页面,再去查看 INNODB_LOCKS 表时,这个 lock_data 会显示未 NULL 值,意味着 InnoDB 存储引擎不会从磁盘进行再一次查找。

查看当前等锁的事务

SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;

INNODB_LOCK_WAITS 表包含了 blocked 的事务的锁等待的状态。当事务量比较少,我们可以直观的查看,当事务量非常大,锁等待也时常发生的情况下,这个时候可以通过 INNODB_LOCK_WAITS 表来更加直观的反映出当前的锁等待情况。

INNODB_LOCK_WAITS Columns 相关列信息:

字段

描述

requesting_trx_id

申请锁资源的事务 id。

requested_lock_id

申请的锁的 id。

blocking_trx_id

阻塞的事务 id。

blocking_lock_id

阻塞的锁的 id。

如以下查询:

mysql> select * from INNODB_LOCK_WAITSG

输出:

requesting_trx_id: 3015646

requested_lock_id: 3015646:797:3:2

blocking_trx_id: 3015645

blocking_lock_id: 3015645:797:3:2

这里我们可以看到事务 t1(3015646) 申请了锁资源,而事务 t2(3015645) 则阻塞了事务 t1 的申请。

收集死锁信息

  1. 利用命令 SHOW ENGINE INNODB STATUS 查看死锁原因。
  2. 调试阶段开启 innodb_print_all_deadlocks,收集所有死锁日志。

如何处理死锁

查询对应死锁的数据库进程,可以直接杀掉

kill 进程ID

如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。

虽然不能完全避免死锁,但可以使死锁的数量减至最少。将死锁减至最少可以增加事务的吞吐量并减少系统开销,因为只有很少的事务回滚,而回滚会取消事务执行的所有工作。由于死锁时回滚而由应用程序重新提交。

如何防止死锁

  1. 使用事务,不使用 lock tables。
  2. 保证没有长事务。
  3. 操作完之后立即提交事务,特别是在交互式命令行中。
  4. 如果在用 (SELECT ... FOR UPDATE or SELECT ... LOCK IN SHARE MODE),尝试降低隔离级别。
  5. 修改多个表或者多个行的时候,将修改的顺序保持一致。
  6. 创建索引,可以使创建的锁更少。
  7. 最好不要用 (SELECT ... FOR UPDATE or SELECT ... LOCK IN SHARE MODE)。

如果上述都无法解决问题,那么尝试使用 lock tables t1, t2, t3 锁多张表。



最近发表
标签列表