应仅包裹 executeUpdate()等实际执行 SQL 的原子操作;连接初始化、SQL 拼接等应在前期独立校验;捕获 SQLException 后优先用 getSQLState()(如 ”23000″)判别业务异常,再结合 getErrorCode()细化处理;事务失败后必须 rollback()才能继续使用 Connection;try-with-resources 需警惕 close()抛出的压制异常。

SQL 更新失败时,try-catch到底该包哪一层?
别把整个数据库连接初始化、查询构造、结果处理全塞进一个 try 块里。更新失败的根源往往在语义层(比如违反唯一约束)或执行层(比如连接中断),混在一起捕获会导致错误定位失真。
只包裹真正可能抛异常的原子操作:即 executeUpdate()(Java)、cursor.execute()(Python)、mysqli_query()(PHP)这类实际触发票据的调用。连接建立、SQL 拼接、参数绑定这些环节该报错就该提前报,不该靠更新时的catch 兜底。
- Java JDBC 示例中,
conn.prepareStatement(sql)一般不抛 SQL 异常,但ps.executeUpdate()会抛SQLException - Python
sqlite3下,cursor.execute()可能直接 raisesqlite3.IntegrityError,不是所有异常都得等commit()才暴露 - 如果用了 ORM(如 MyBatis、Django ORM),异常通常发生在
save()或update()方法调用点,不是filter().update()的链式调用中间
捕获 SQLException 后,怎么区分是业务冲突还是系统故障?
硬判 e.getMessage().contains("duplicate") 这种写法脆弱又危险——不同驱动返回消息格式不一致,MySQL 8.0 和 PostgreSQL 的报错文本完全不同,连空格都可能变。
靠谱做法是查 SQLState 码或原生错误码:SQLState是跨数据库的标准五位码(如 "23000" 代表完整性约束违规),而 getErrorCode() 是厂商私有码(MySQL 的1062、PostgreSQL 的23505)。
- 优先用
e.getSQLState()判断大类:比如"23000"基本可认定为唯一键 / 外键冲突,属于预期中的业务异常 - 再结合
e.getErrorCode()做细粒度分支:MySQL1062是重复键,1205是死锁,需不同重试策略 - 不要忽略
SQLException.getNextException()——批量更新失败时,它可能链着多个子异常
事务里更新失败,catch住之后能继续用同一个 Connection 吗?
不能。一旦 executeUpdate() 抛出 SQLException,当前事务状态已损坏,JDBC 规范要求后续操作必须先rollback(),否则再调commit() 会直接抛 SQLException("No transaction is currently active") 或更隐蔽的静默失败。
更麻烦的是某些数据库(如 PostgreSQL)在语句级错误后会自动进入“failed transaction”状态,不 rollback() 就卡死,连新查询都拒绝。
- 必须在
catch块里显式调用conn.rollback(),哪怕你打算丢弃这个连接 - 别在
catch里试图conn.commit()——它大概率失败,且掩盖了原始错误 - 如果用连接池(如 HikariCP),记得
rollback()后正常close(),池子会负责回收或重建连接
为什么 try-with-resources 和catch一起用容易漏掉资源泄漏?
因为 try-with-resources 的close()可能抛新异常,把原始的 SQL 异常盖掉。比如更新失败后,PreparedStatement.close()又因网络断开抛 SQLException,你catch 的就变成关闭异常,而不是更新失败本身。
这不是理论风险——MySQL 驱动在连接已断时调 close() 确实会触发二次异常,而且堆栈里原始错误信息被吞掉。
- 要么放弃
try-with-resources,手动在finally里close()并忽略关闭异常(if (ps != null) try {ps.close(); } catch (SQLException ignored) {}) - 要么保留
try-with-resources,但在catch里检查e.getSuppressed()数组,里面可能藏着被压制的关闭异常 - 永远别在
close()逻辑里做任何依赖数据库状态的操作(比如记录日志到 DB),它本就不该失败
事务边界、异常类型识别、资源清理顺序——这三件事串起来才叫“优雅”,单靠套个 try-catch 只是把错误藏得更深。