Java JDBC并发插入唯一键冲突应“不靠查、靠试、快反馈”,即弃用先查后插,改用数据库原子语句(如INSERT IGNORE、ON CONFLICT)并精准捕获驱动特有异常,解析约束名或冲突值分类响应,增强可观测性。

Java JDBC 处理并发插入的唯一键冲突异常,核心是“不靠查、靠试、快反馈”,即放弃“先查再插”的脆弱逻辑,改用数据库原生机制兜底,并在代码中精准捕获、区分场景、友好响应。
精准捕获并识别冲突来源
不要用通用 Exception 捕获,而是针对驱动特有异常类型做判断:
- MySQL:捕获 MySQLIntegrityConstraintViolationException(继承自 SQLIntegrityConstraintViolationException)
- PostgreSQL:捕获 PSQLException,再检查
e.getSQLState()是否为"23505" - SQL Server:捕获 SQLServerException,检查错误号如
2627(主键)或2601(唯一索引) - 统一做法:解析异常消息,提取约束名(如
uk_user_email)或冲突字段值(如Key (email)=(a@b.com)),用于返回具体提示
避免“先查后插”陷阱
高并发下,“SELECT 判断不存在 → INSERT”必然失败。两个请求几乎同时查到“不存在”,接着都执行 INSERT,第二个必报唯一冲突。
Java开发手册规约集合,基于阿里巴巴Java开发手册(嵩山版)。 涵盖7大维度:编程规约、异常日志、单元测试、安全规约、MySQL数据库、工程结构、设计规约。 当用户需要:(1) 编写或审查Java代码 (2) 检查命名/代码规范 (3) 处理异常和日志 (4) 编写单元测试 (5) 安全编码 (6) 数据库设...
- 删掉所有类似
if (!userExists(email)) { insertUser(...) }的逻辑 - 改用原子性语句替代:
- MySQL:
INSERT IGNORE INTO users (...) VALUES (...)或INSERT ... ON DUPLICATE KEY UPDATE ... - PostgreSQL:
INSERT ... ON CONFLICT (email) DO NOTHING或DO UPDATE - SQL Server:
MERGE INTO ... WHEN NOT MATCHED THEN INSERT
- MySQL:
- 这些语句由数据库保证原子性,无需应用层加锁,性能更高也更可靠
按业务场景分类响应
同一类异常,不同业务含义不同,不能一概提示“已存在”:
立即学习“Java免费学习笔记(深入)”;
- 注册场景:用户重复提交 → 提示“该手机号已被注册,请直接登录”
- 幂等写入(如日志、事件):允许忽略 →
catch后静默处理或记录 warn 日志 - 覆盖更新场景(如配置同步):冲突即需更新 → 改用
ON CONFLICT ... DO UPDATE或先UPDATE再判断影响行数为 0 时INSERT - 生成业务编码(如单号):禁止重码 → 不依赖查询计数,改用数据库序列、UUID 或雪花 ID
增强可观测性辅助定位
线上出问题时,光看异常堆栈不够,需快速知道哪条数据、哪个约束、哪个字段撞了:
- 打印完整 SQL 和绑定参数(如用 P6Spy 或 log4jdbc)
- 在 catch 块中记录冲突字段值:从异常消息正则提取(例:
Pattern.compile("Key \((\w+)\)=\(([^)]+)\)")) - 开发/测试环境 JDBC URL 开启驱动级日志(如 PostgreSQL 加
?loggerLevel=DEBUG) - 数据库侧开启慢日志或唯一约束冲突审计(如 MySQL 的
log_error_verbosity=3)

















