Mysql对死锁的处理

什么是死锁

官方定义如下: 两个事务都持有对方需要的锁, 并且在等待对方释放, 并且双方都不会释放自己的锁。

MySQL有两种死锁处理方式

  • 等待, 直到超时(innodb_lock_wait_timeout=50s) 。
  • 发起死锁检测, 主动回滚一条事务, 让其他事务继续执行(innodb_deadlock_detect=on) 。

由于性能原因, 一般都是使用死锁检测来进行处理死锁。

死锁检测

死锁检测的原理是构建一个以事务为顶点、 锁为边的有向图, 判断有向图是否存在环, 存在即有死锁。

回滚

检测到死锁之后, 选择插入更新或者删除的行数最少的事务回滚,
基于INFORMATION_SCHEMA.INNODB_TRX 表中的 trx_weight 字段来判断。

对于锁的建议

  • 收集死锁信息:
    利用命令 SHOW ENGINE INNODB STATUS查看死锁原因。
    调试阶段开启 innodb_print_all_deadlocks, 收集所有死锁日志。
  • 减少死锁:
    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)。
    8.如果上述都无法解决问题, 那么尝试使用 lock tables t1, t2, t3 锁多张表

对于事务的建议

innodb存储引擎由于实现了行级锁, 颗粒更小, 实现更复杂。
但是innodb行锁在并发性能上远远要高于表锁页锁。
在使用方面可以尽量做到以下几点;

  1. 控制事务大小, 减少锁定的资源量和锁定时间长度。
  2. 所有的数据检索都通过索引来完成, 从而避免因为无法通过索引加锁而升级为表锁。
  3. 减少基于范围的数据检索过滤条件, 避免因为间隙锁带来的负面影响而锁定了不该锁定的数据。
  4. 在业务条件允许下, 尽量使用较低隔离级别的事务隔离。 减少隔离级别带来的附加成本。
  5. 合理使用索引, 让innodb在索引上面加锁的时候更加准确。
  6. 在应用中尽可能做到访问的顺序执行
  7. 如果容易死锁, 就可以考虑使用表锁来减少死锁的概率
### 解决MySQL死锁问题的方法 #### 使用`SHOW ENGINE INNODB STATUS` 为了诊断死锁,可以利用命令 `SHOW ENGINE INNODB STATUS\G;` 来获取最近一次发生的死锁信息。这有助于理解哪些查询参与到了死锁之中以及涉及的具体表和索引[^1]。 ```sql SHOW ENGINE INNODB STATUS\G; ``` 此命令返回的信息非常详尽,包含了发生死锁的时间戳、事务ID、锁定的记录以及其他相关细节。 #### 设置合理的隔离级别 调整事务的隔离级别可以在一定程度上减少死锁的发生概率。较低级别的隔离(如读已提交)可能降低死锁的风险,但这取决于应用程序的需求和数据一致性要求[^2]。 设置会话或全局范围内的隔离级别: ```sql SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; -- 或者对于整个服务器生效 SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED; ``` #### 控制事务大小并尽早释放锁 保持事务尽可能短小,并尽快完成操作以释放所持有的任何锁。长时间运行的大事务增加了与其他事务冲突的可能性,从而提高了死锁的概率[^4]。 启动一个新的事务时应考虑如下方式: ```sql START TRANSACTION; -- 执行必要的 SQL 语句... COMMIT; ``` #### 尝试捕获异常重试逻辑 由于无法完全预防死锁,在应用层面上实现一个简单的重试机制是一个有效的策略。当检测到错误码为1213(即发生了死锁),可以选择回滚当前事务并重新尝试执行相同的SQL序列。 Python伪代码示例展示如何处理这种情况: ```python import pymysql def execute_with_retry(connection, sql_query, max_retries=3): retries = 0 while True: try: with connection.cursor() as cursor: cursor.execute(sql_query) connection.commit() break except pymysql.MySQLError as e: if e.args[0] == 1213 and retries < max_retries: retries += 1 continue raise execute_with_retry(conn, "YOUR_SQL_QUERY_HERE") ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

InterestingFigure

迈克 Let's Go

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值