复合唯一索引是数据库层防重复的最后一道防线,通过唯一约束拦截前端、幂等、分布式锁均失效时的重复写入,需按业务重复维度设计非空字段组合,并在java中精准捕获1062错误码返回友好提示。

复合唯一索引本身不能直接“实现防重复提交”,但它可以作为数据库层的兜底机制,在业务逻辑偶发失效(如幂等校验漏判、分布式锁异常释放)时,靠 MySQL 的唯一约束强制拦截重复数据,成为真正意义上的最后一道防线。
为什么需要这道防线
前端防重(按钮置灰)、接口幂等(token/traceId 去重)、分布式锁(Redis 锁)都可能因网络超时、服务重启、代码 bug 或并发竞争而失效。此时若业务主键或关键字段未加唯一约束,就可能写入多条重复记录——比如同一用户重复下单、同一手机号重复注册、同一订单号多次支付成功。复合唯一索引正是在这种“所有上层防护都失守”的极端场景下,由数据库引擎主动拒绝插入,保证数据一致性。
如何设计有效的复合唯一索引
关键不是随便加 UNIQUE,而是让索引字段组合能精准表达“什么算重复”。它必须覆盖业务判定重复的核心维度,且这些字段在 INSERT 时必须都有值(避免 NULL 导致唯一性失效)。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 例如用户注册:对 (mobile, tenant_id) 建唯一索引,防止同一租户下手机号重复注册
- 例如订单创建:对 (user_id, external_order_no) 建唯一索引,防止同一用户用相同外部单号重复提交
- 例如优惠券领取:对 (user_id, coupon_template_id, activity_id) 建唯一索引,确保一人一券一活动不重复
注意:不要包含可空字段(如 status、create_time),NULL 值在 MySQL 中不参与唯一性比较,会导致多条 NULL 记录被允许插入。
Java 代码中如何安全捕获并处理冲突
不能依赖 try-catch 捕获 SQLException 后简单吞掉或打日志——要明确识别是唯一索引冲突,并转化为友好的业务提示。
- 使用 Spring JDBC 或 MyBatis 时,捕获 SQLState = '23000' 或 MySQL 错误码 1062(Duplicate entry)
- MyBatis-Plus 可通过
@ExceptionHandler(DuplicateKeyException.class)统一处理 - 推荐封装工具类判断异常类型:
SQLException.getSQLState().equals("23000") && e.getErrorCode() == 1062 - 捕获后应返回明确提示,如“操作已存在,请勿重复提交”,而非 500 错误页
必须配合的业务习惯
复合唯一索引不是银弹,它只拦住“写入阶段”的重复,不解决“读取阶段”的脏数据或并发读写问题。
- INSERT 前仍需做必要校验(如查库判断是否存在),索引只是失败后的保险
- 避免在高并发场景下仅靠索引兜底——冲突抛异常本身有性能损耗,且用户体验差
- 建索引前确认字段选择无歧义:比如用 order_no 而非 pay_no,因为前者更稳定可控
- 上线前用压测验证索引生效:模拟重复请求,观察是否真触发唯一冲突而非静默失败
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










