MySQL中发生死锁时应如何处理?
MySQL 会自动检测到死锁情况,意思是两个或多个事务互相等待对方释放资源,导致无法继续执行。为了避免系统停滞,MySQL会通过回滚一个事务来打破死锁,通常会选择回滚占用最少资源的事务。
如果你想查看死锁的详细信息,可以使用 SHOW ENGINE INNODB STATUS 这个命令,它会显示当前的死锁信息,你也可以手动终止死锁事务。
简单来说,MySQL 自动帮助你解决死锁问题,保证数据库继续正常运行。
📚 知识内容:
🔄 1. 死锁检测与自动回滚
自动检测:
MySQL 使用 innodb_deadlock_detect 参数启用死锁检测功能。这个功能会在检测到死锁时自动回滚其中一个事务来打破死锁,通常会选择回滚持有最少资源的事务来解除死锁。
死锁回滚示例:
假设有两个事务 T1 和 T2,T1 执行了一些操作并获得了 A 锁,T2 获得了 B 锁。之后 T1 请求 B 锁并等待 T2 释放,而 T2 请求 A 锁并等待 T1 释放。此时就发生了死锁,MySQL 会回滚其中一个事务。
锁等待超时:
除了死锁检测,MySQL 还使用 innodb_lock_wait_timeout 参数来控制锁等待的超时。当一个事务等待锁的时间超过该阈值时,MySQL 会自动回滚该事务,释放占用的锁。
– 设置锁等待超时
SET GLOBAL innodb_lock_wait_timeout = 50; – 设置为 50 秒
🛠️ 2. 手动解决死锁
当死锁无法自动解决时,管理员可以手动介入。
步骤一:查看死锁日志
SHOW ENGINE INNODB STATUS;
这条命令会返回当前死锁的详细信息,包括相关事务及锁的详细情况,您可以从中找出导致死锁的事务和锁。
步骤二:查看锁的状态
通过 INFORMATION_SCHEMA 中的 INNODB_LOCKS 和 INNODB_LOCK_WAITS 表,您可以查看当前所有的锁和等待信息。
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
这些查询将帮助您找出哪些事务和锁正在等待其他事务的锁。
步骤三:终止阻塞的事务
根据 SHOW ENGINE INNODB STATUS 或上面查询的信息,您可以找出死锁的事务 ID,然后使用 KILL 命令终止这些事务。
– 终止事务
KILL
其中
📜 3. 死锁日志示例
以下是 MySQL 返回的一个死锁日志示例,它展示了死锁的具体细节:
LATEST DETECTED DEADLOCK
170219 13:31:31
*** (1) TRANSACTION:
TRANSACTION 2A8BD, ACTIVE 11 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 376, 1 row lock(s)
MySQL thread id 448218, OS thread handle 0x2abe5fb5d700, query id 18923238 renjun.fangcloud.net 121.41.41.92 root updating
delete from test where a = 2
…
在此日志中,我们可以看到事务 T1 和 T2 的锁状态及锁等待关系。事务 T1 正在等待锁,而事务 T2 正在持有锁,导致死锁的发生。
🌱 知识拓展:
🚀 常见避免死锁的策略
避免大事务
解释: 大事务会占用锁的时间较长,增加死锁的风险。通过将大事务拆分成多个小事务,可以减小死锁的发生概率。
举例:
假设我们有一个订单表,订单表有大量数据,如果我们一次性更新所有订单的状态,可能会锁住很长时间,从而增加死锁的风险。我们可以将更新拆分成多个小批量事务来处理:
– 大事务
START TRANSACTION;
UPDATE orders SET status = ‘shipped’ WHERE order_date < ‘2024-01-01’;
COMMIT;
– 拆分成多个小事务
START TRANSACTION;
UPDATE orders SET status = ‘shipped’ WHERE order_date BETWEEN ‘2023-12-01’ AND ‘2023-12-10’;
COMMIT;
START TRANSACTION;
UPDATE orders SET status = ‘shipped’ WHERE order_date BETWEEN ‘2023-12-11’ AND ‘2023-12-20’;
COMMIT;
通过拆分事务,每个事务锁定的范围变小,减少了其他事务的阻塞和死锁的可能性。
调整锁的申请顺序
解释: 确保所有事务按照相同的顺序请求锁,避免因锁获取顺序不同而导致死锁。
举例:
假设事务 A 和事务 B 都需要同时更新 orders 表和 customers 表。如果两个事务先后顺序不一致,就可能导致死锁:
事务 A:先获取 orders 锁,然后获取 customers 锁。
事务 B:先获取 customers 锁,然后获取 orders 锁。
为避免死锁,可以统一事务的锁请求顺序:
– 统一锁请求顺序,先锁 orders 再锁 customers
START TRANSACTION;
UPDATE orders SET status = ‘shipped’ WHERE order_id = 1;
UPDATE customers SET balance = balance - 100 WHERE customer_id = 1;
COMMIT;
– 统一锁请求顺序,先锁 orders 再锁 customers
START TRANSACTION;
UPDATE orders SET status = ‘shipped’ WHERE order_id = 2;
UPDATE customers SET balance = balance - 150 WHERE customer_id = 2;
COMMIT;
通过统一锁的顺序,可以避免因先后顺序不同而导致的死锁。
更改数据库隔离级别
解释: 提高数据库的并发性,降低死锁发生的几率。选择适当的事务隔离级别能够减少锁的使用。例如,将事务的隔离级别从 可重复读(Repeatable Read)更改为 读已提交(Read Committed),可以减少间隙锁和临键锁,从而降低死锁的风险。
举例:
在默认的 可重复读 隔离级别下,InnoDB 会在读取数据时加上间隙锁和临键锁,这可能导致死锁。如果将隔离级别设置为 读已提交,这些锁会减少,从而降低死锁的概率。
– 设置事务隔离级别为读已提交
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT * FROM orders WHERE order_id = 1;
UPDATE orders SET status = ‘shipped’ WHERE order_id = 1;
COMMIT;
合理建立索引
解释: 如果查询能够命中索引,锁的范围就会局限于特定的行,从而减少锁的争用和死锁的发生。
举例:
假设我们有一个 orders 表,如果查询没有使用索引,那么整个表的行都会被锁定,增加了死锁的概率。通过建立合适的索引,可以确保只对相关的行加锁,减少死锁发生的几率。
– 创建索引来优化查询
CREATE INDEX idx_order_date ON orders (order_date);
– 使用索引来减少锁的范围
START TRANSACTION;
SELECT * FROM orders WHERE order_date = ‘2023-12-01’;
UPDATE orders SET status = ‘shipped’ WHERE order_date = ‘2023-12-01’;
COMMIT;
通过使用索引,查询只会锁定符合条件的行,减少了全表扫描的锁竞争。
开启死锁检测并调整锁等待时长
解释: 开启死锁检测功能,并通过调整 innodb_lock_wait_timeout 参数,设置合理的锁等待超时阈值,以便及时检测并解决死锁。
举例:
可以通过设置 innodb_lock_wait_timeout 来控制锁等待的最大时间,从而避免长时间的锁等待导致死锁。
– 设置锁等待超时时间为 50 秒
SET GLOBAL innodb_lock_wait_timeout = 50;
– 执行查询并等待锁
START TRANSACTION;
UPDATE orders SET status = ‘shipped’ WHERE order_id = 1;
COMMIT;
当一个事务等待锁的时间超过 50 秒,它将自动被回滚,从而防止死锁的进一步发生。
📊 原理图:
死锁的常见情景:
+————————–+
| 事务 T1 |
| |
| 请求锁A —> 锁定 A |
| 等待锁B —> 等待锁B |
+————————–+
↑
|
|
+————————–+
| 事务 T2 |
| |
| 请求锁B —> 锁定 B |
| 等待锁A —> 等待锁A |
+————————–+
在这个简单的示意图中,T1 和 T2 由于相互等待对方释放锁,导致死锁的发生。MySQL 会选择回滚其中一个事务来打破死锁。
🎯 总结:
MySQL 中的死锁是由于事务间相互等待对方释放锁造成的,自动检测和手动解决都是有效的解决方案。
为了减少死锁的发生,可以通过拆分事务、调整锁顺序、优化查询和合理配置 MySQL 参数等方法来避免死锁。
定期查看死锁日志,并根据实际情况优化数据库结构和事务流程,是维护高效系统的必要手段。🔑
非常感谢您的反馈!我会在文章中增加更多的 emoji 图标,以提升文章的跳跃性、吸引力和可读性。以下是加入更多 emoji 后的文章版本:
