java后端开发怎么解决数据库的死锁问题?

来源:个人站长网作者:会飞的猪头衔:草根站长
导读:本期聚焦于小伙伴创作的《java后端开发怎么解决数据库的死锁问题?》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《java后端开发怎么解决数据库的死锁问题?》有用,将其分享出去将是对创作者最好的鼓励。

在java后端开发的高并发业务场景中,数据库死锁是经常出现的性能问题,当两个或多个事务互相持有对方需要的锁资源且都不释放时,就会形成死锁,导致事务无法继续执行,严重时会影响整个服务的可用性。

java后端开发怎么解决数据库的死锁问题?

什么是数据库死锁

数据库死锁指的是两个或者多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。比如事务A锁定了表的第一行数据,想要锁定第二行,而事务B锁定了第二行,想要锁定第一行,双方都不释放已有的锁,就会形成死锁。

死锁的常见成因

  • 事务执行的顺序不一致,不同事务对相同资源的加锁顺序不同,容易形成互相等待的情况
  • 事务持有锁的时间过长,长时间不提交或者回滚,导致其他事务等待锁的超时时间增加
  • 索引使用不当,没有合适的索引时数据库可能会进行全表扫描,加锁范围扩大,增加死锁概率
  • 事务隔离级别设置过高,比如使用串行化隔离级别,会大幅提升锁的竞争程度

死锁的排查方法

数据库层面排查

以常用的MySQL InnoDB引擎为例,可以通过查看死锁日志来定位问题,执行以下SQL语句可以获取最近的死锁信息:

-- 查看InnoDB引擎状态,包含死锁相关信息
SHOW ENGINE INNODB STATUS;

在返回的结果中,找到LATEST DETECTED DEADLOCK部分,里面会记录死锁发生时的两个事务的SQL语句、持有的锁信息以及等待的锁信息,通过这些内容可以定位到具体是哪部分业务代码触发了死锁。

java代码层面排查

可以在java代码中捕获死锁相关的异常,比如MySQL的死锁异常错误码是1213,当捕获到该异常时可以打印对应的业务参数和调用栈,方便定位问题:

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.springframework.jdbc.core.JdbcTemplate;

public class DeadLockTest {
    private JdbcTemplate jdbcTemplate;

    public void updateData(String param1, String param2) {
        String sql1 = "UPDATE user SET age = ? WHERE id = 1";
        String sql2 = "UPDATE user SET age = ? WHERE id = 2";
        Connection conn = null;
        try {
            conn = jdbcTemplate.getDataSource().getConnection();
            // 关闭自动提交,手动管理事务
            conn.setAutoCommit(false);
            PreparedStatement ps1 = conn.prepareStatement(sql1);
            ps1.setInt(1, 20);
            ps1.executeUpdate();
            PreparedStatement ps2 = conn.prepareStatement(sql2);
            ps2.setInt(1, 25);
            ps2.executeUpdate();
            conn.commit();
        } catch (SQLException e) {
            // MySQL死锁错误码为1213
            if (e.getErrorCode() == 1213) {
                System.err.println("发生死锁,业务参数:param1=" + param1 + ", param2=" + param2);
                e.printStackTrace();
            }
            if (conn != null) {
                try {
                    conn.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
        } finally {
            if (conn != null) {
                try {
                    conn.setAutoCommit(true);
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

java后端解决死锁的具体方案

统一事务内的加锁顺序

确保所有事务对相同资源的加锁顺序一致,比如所有更新用户数据的操作都按照用户id从小到大的顺序加锁,避免不同事务反向加锁形成死锁。示例如下:

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.springframework.jdbc.core.JdbcTemplate;

public class FixDeadLock {
    private JdbcTemplate jdbcTemplate;

    // 统一按照id升序的顺序更新数据,避免加锁顺序不一致
    public void updateUserInOrder(int id1, int age1, int id2, int age2) {
        // 先排序,保证加锁顺序一致
        int firstId = Math.min(id1, id2);
        int firstAge = id1 == firstId ? age1 : age2;
        int secondId = Math.max(id1, id2);
        int secondAge = id1 == secondId ? age1 : age2;

        Connection conn = null;
        try {
            conn = jdbcTemplate.getDataSource().getConnection();
            conn.setAutoCommit(false);
            // 先更新id小的记录
            PreparedStatement ps1 = conn.prepareStatement("UPDATE user SET age = ? WHERE id = ?");
            ps1.setInt(1, firstAge);
            ps1.setInt(2, firstId);
            ps1.executeUpdate();
            // 再更新id大的记录
            PreparedStatement ps2 = conn.prepareStatement("UPDATE user SET age = ? WHERE id = ?");
            ps2.setInt(1, secondAge);
            ps2.setInt(2, secondId);
            ps2.executeUpdate();
            conn.commit();
        } catch (SQLException e) {
            if (conn != null) {
                try {
                    conn.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            e.printStackTrace();
        } finally {
            if (conn != null) {
                try {
                    conn.setAutoCommit(true);
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

缩短事务持有锁的时间

尽量把事务中的非数据库操作移出事务范围,比如不要在事务中做远程接口调用、文件读写等耗时操作,减少锁的占用时间。同时尽量把事务拆解,避免大事务长时间持有锁。

优化索引使用

给经常作为查询条件的字段添加合适的索引,避免数据库进行全表扫描导致加锁范围扩大到整个表。比如更新用户数据时如果where条件用的是id,就给id字段添加主键索引,确保加锁只锁定对应的行。

合理设置事务隔离级别

如果不是特别需要,不要使用过高的隔离级别,比如普通的业务场景使用读已提交隔离级别就可以满足需求,避免使用串行化隔离级别大幅提升锁竞争。可以在数据库连接配置中设置隔离级别:

import java.sql.Connection;
import java.sql.SQLException;
import org.springframework.jdbc.core.JdbcTemplate;

public class IsolationConfig {
    private JdbcTemplate jdbcTemplate;

    public void setIsolationLevel() {
        Connection conn = null;
        try {
            conn = jdbcTemplate.getDataSource().getConnection();
            // 设置事务隔离级别为读已提交,对应值为2
            conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

死锁重试机制

当发生死锁时,数据库会回滚其中一个事务,我们可以在java代码中捕获死锁异常后,进行有限次数的重试,比如重试3次,每次重试间隔一定时间,减少业务失败的概率:

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.springframework.jdbc.core.JdbcTemplate;

public class DeadLockRetry {
    private JdbcTemplate jdbcTemplate;
    // 最大重试次数
    private static final int MAX_RETRY_TIMES = 3;
    // 重试间隔毫秒数
    private static final int RETRY_INTERVAL = 100;

    public void updateWithRetry(int id, int age) {
        int retryCount = 0;
        while (retryCount <= MAX_RETRY_TIMES) {
            Connection conn = null;
            try {
                conn = jdbcTemplate.getDataSource().getConnection();
                conn.setAutoCommit(false);
                PreparedStatement ps = conn.prepareStatement("UPDATE user SET age = ? WHERE id = ?");
                ps.setInt(1, age);
                ps.setInt(2, id);
                ps.executeUpdate();
                conn.commit();
                // 执行成功,跳出循环
                break;
            } catch (SQLException e) {
                if (e.getErrorCode() == 1213 && retryCount < MAX_RETRY_TIMES) {
                    // 死锁异常且还有重试次数,进行重试
                    retryCount++;
                    try {
                        Thread.sleep(RETRY_INTERVAL);
                    } catch (InterruptedException ex) {
                        Thread.currentThread().interrupt();
                    }
                } else {
                    // 其他异常或者重试次数用尽,回滚并抛出异常
                    if (conn != null) {
                        try {
                            conn.rollback();
                        } catch (SQLException ex) {
                            ex.printStackTrace();
                        }
                    }
                    e.printStackTrace();
                    break;
                }
            } finally {
                if (conn != null) {
                    try {
                        conn.setAutoCommit(true);
                        conn.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

总结

解决java后端的数据库死锁问题需要从代码逻辑、数据库配置、索引优化多个层面入手,首先要通过死锁日志定位问题根源,然后针对性地统一加锁顺序、缩短事务时间、优化索引,同时配合合理的隔离级别和重试机制,就能大幅降低死锁发生的概率,保障后端服务的稳定运行。

Java数据库死锁MySQL事务隔离级别InnoDB修改时间:2026-06-30 22:24:45

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。