死锁---转账问题

本文探讨了在并发转账场景中可能出现的死锁问题,分析了死锁产生的四个必要条件,并提出了解决死锁的策略。通过破坏"占有且等待"、"不可抢占"和"循环等待"条件,可以有效地避免死锁。具体措施包括一次性申请所有资源、使用Lock实现资源抢占以及设定加锁顺序,如依据对象的hashCode确定加锁顺序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 转账死锁场景

场景:简单的银行转账,将资金从一个账户(fromAccount)转到另一个账户(toAccount),当并发执行的时候,可能会出现死锁的情况;

死锁的场景:多线程执行,线程1:账户A转账给账户B;线程2:账户B转账给账户A;线程1和线程2同时转账时,就可能会发生 死锁:
         线程1:new TransferAccount(fromAccount,toAccount,1)       
         线程2:new TransferAccount(toAccount,fromAccount,2) 
         如果执行顺序不恰当,那么线程1可能获得fromAccount的锁,并且等待toAccount的锁;然而此时线程2拥有toAccount的锁,并正在等待fromAccount的锁。

死锁是指一组互相竞争资源的线程因互相等待,导致永久阻塞的现象

/*
* 需求:简单的银行转账,它将资金从一个账户转到另一个账户
* 在开始转账之前,需要获得两个Account的锁,以确保以原子的方式更新账户中的余额,且不能破坏不可变的条件,如账户的余额不能为负数
* 
*/

/*账户类*/
class Account{
    private String accountName;//账号
    private int balance;//资金总额
    public Account(String accountName,int balance){
        this.accountName = accountName;
        this.balance = balance;
    }
    public String getAccountName() {/
<think>嗯,用户明确询问数据库死锁的排查方法和解决方案,这正好与系统指令中引用的三篇资料高度相关。从引用内容看,用户提供的资料已经覆盖了主流数据库(MySQL/SQLServer)的死锁诊断工具和通用解决策略,需要整合这些信息形成系统化的回答。用户可能是运维工程师或开发人员,正面临生产环境死锁问题,情绪上比较急切。需要优先给出可立即操作的排查步骤,再补充优化方案。注意到引用[3]提到Java代码死锁,但用户提问聚焦数据库层面,可暂不展开应用层内容。技术要点需要分层处理:先定位死锁(引用[1][2]的日志分析和监控工具),再解释常见死锁场景(如跨事务资源循环依赖、索引缺失导致的锁升级),最后给出解决维度(隔离级别调整引用[3]、SQL优化、锁粒度控制)。特别要注意引用[2]中MySQL8.0的INFORMATION_SCHEMA.LOCKS视图和SQLServerProfiler都是关键工具,需单独强调。考虑到用户需要落地解决方案,应在每条建议后标注适用数据库类型(如MySQL专属/SQLServer专属/通用)。对于“调整隔离级别”这种双刃剑策略(引用[3]提到的脏读风险),必须追加警告说明。最后补充的预防措施比单纯解决更重要,这部分要整合三篇引用的优化思路。生成问题设计要侧重技术延伸:原理机制(问题1)、不同数据库差异(问题3)、预防性设计(问题5)。故意避开已覆盖的Java死锁内容(引用[2]提及但用户未问),保持数据库领域聚焦。用“日志分析-监控工具-解决方案-预防机制”的四层结构组织回答,确保用户能直接对应到具体操作。</think> 排查和解决数据库死锁问题需要结合监控分析、日志解读和优化调整,以下是关键步骤: ### 一、死锁排查方法(定位问题根源) 1. **查看数据库死锁日志**(核心手段) - **MySQL**: - 执行 `SHOW ENGINE INNODB STATUS;` 查看 **`LATEST DETECTED DEADLOCK`** 部分,记录死锁事务ID、锁竞争资源、阻塞的SQL语句[^1][^2]。 - 通过错误日志文件(默认路径:`/var/log/mysql/error.log`)搜索 *"Deadlock found"*。 - **SQL Server**: - 使用 **SQL Server Profiler** 捕获 *"Deadlock Graph"* 事件,生成可视化死锁链条[^2]。 - 查询系统视图: ```sql SELECT * FROM sys.dm_tran_locks; -- 当前锁状态 SELECT * FROM sys.dm_exec_requests WHERE blocking_session_id <> 0; -- 阻塞会话 ``` 2. **分析锁竞争路径** - 从日志中提取以下信息: - **涉及的事务**:事务ID、执行时间点。 - **竞争资源**:被锁定的表、行、索引键(例:`index PRIMARY of table test.t1`)。 - **阻塞SQL**:事务中持有锁和等待锁的SQL语句。 - **常见死锁模式**: - **循环等待**:事务A锁资源X后请求Y,事务B锁Y后请求X。 - **锁升级冲突**:多个事务对同一表的不同行加锁,因索引缺失导致锁升级为表锁。 --- ### 二、解决方案(分场景优化) | **场景** | **优化策略** | **案例** | |-------------------------|-----------------------------------------------------------------------------|--------------------------------------------------------------------------| | **跨事务循环依赖** | 统一资源访问顺序(例:始终按表A→表B顺序操作) | 转账场景:先扣减账户A再增加账户B | | **缺失索引导致锁升级** | 为高频查询字段添加索引,缩小行锁范围 | `WHERE user_id=100` 无索引 → 添加 `user_id` 索引避免全表扫描锁 | | **长事务持锁时间过长** | 拆分大事务为小事务,避免锁长期占用 | 批量更新10万条数据 → 分批次提交(每1000条) | | **隔离级别过高** | 降级为 **读已提交(Read Committed)**,减少间隙锁(Gap Lock)冲突[^3] | MySQL默认隔离级别为"可重复读" → 对只读业务降级 | | **超时与重试机制** | 设置 `innodb_lock_wait_timeout`(MySQL),超时后重试事务 | Java代码重试逻辑:`@Retryable(maxAttempts=3, backoff=@Backoff(delay=100))` | --- ### 三、预防性措施(降低死锁概率) 1. **事务设计原则** - 保持事务简短,尽快提交/回滚。 - 避免事务内执行网络调用等耗时操作。 2. **索引优化** - 覆盖索引(Covering Index)减少回表查询。 - 唯一索引避免锁升级(`INSERT ... ON DUPLICATE KEY UPDATE`)。 3. **监控告警** - 部署监控工具(如Prometheus+Grafana)报警死锁频率阈值。 - MySQL开启参数 `innodb_print_all_deadlocks=ON` 记录所有死锁日志。 > **注意**:调整隔离级别或锁超时需权衡一致性风险。例如降级到"读已提交"可能导致不可重复读[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值