能,但仅在单实例mysql中可靠;分布式环境下需全局id服务或唯一映射表保障,因分库后各实例索引互不感知。

唯一索引能拦住并发插入重复数据吗
能,但只在单实例 MySQL 里可靠。只要两个事务同时执行 INSERT,且都试图写入同一组 UNIQUE 字段值,其中一个会被 Deadlock found when trying to get lock 或更常见的是 Duplicate entry 'xxx' for key 'uk_xxx' 直接拒绝——这是 MySQL 唯一索引最核心的保障机制。
实操建议:
- 必须把业务上“不能重复”的字段组合(比如
order_no、user_id + biz_type)明确定义为UNIQUE KEY,不能只靠应用层校验 - 建索引时注意字段顺序和
NULL处理:MySQL 认为NULL != NULL,所以含NULL的列参与唯一约束时,可能意外绕过检查 - 不要依赖
SELECT + INSERT判断是否存在——这中间有竞态窗口,高并发下必出问题
分布式环境下唯一索引为什么失效
不是索引失效,是“唯一性”这个语义被拆到了多个 MySQL 实例上。比如分库分表后,user_id = 1001 的记录可能落在 db1 和 db2 两个库,各自都有自己的 UNIQUE 约束,但彼此不感知对方数据。
常见错误现象:
- 用
sharding_key分片后,仍对全局唯一的pay_no建本地唯一索引 → 各分片都能插入相同pay_no - 误以为
INSERT IGNORE或ON DUPLICATE KEY UPDATE能跨库生效 → 它们只作用于当前连接的单个实例 - 用雪花算法生成 ID,却没把机器 ID 或数据中心 ID 纳入分片路由逻辑 → 不同实例可能生成相同 ID
怎么在分库分表中真正保唯一
得把“唯一性判定”从存储层上移到一个全局可协调的环节。MySQL 自身不提供跨库唯一性保证,必须引入外部机制或重构设计。
实操建议:
- 全局唯一 ID 必须由中心服务生成(如
TinyID、Leaf、自建号段服务),且该服务本身要支持高可用和容错,不能成为单点瓶颈 - 对业务强依赖唯一性的字段(如手机号、邮箱),单独建一张全局唯一映射表(
global_unique_map),所有写操作先INSERT INTO global_unique_map ... ON DUPLICATE KEY UPDATE,成功后再写业务表 - 避免在分片键之外再搞复杂唯一约束;如果真需要,考虑改用
UNIQUE INDEX+ 应用层幂等控制 + 补偿任务兜底,而不是强一致
唯一索引和事务隔离级别的关系
唯一索引的冲突检测发生在 INSERT 执行时,与事务隔离级别无关;但 SELECT 阶段的可见性会影响你“以为没重复”的判断。比如在 READ COMMITTED 下,事务 A 插入未提交,事务 B 查不到那条记录,就可能跟着插一次,然后 A 提交,B 就报唯一冲突。
关键点:
-
REPEATABLE READ也不能避免唯一冲突——它只保证本事务内多次SELECT结果一致,不阻止其他事务插入 - 唯一约束的检查是加在索引记录上的
next-key lock,不是行锁也不是表锁,但会阻塞其他事务对相同唯一值的插入尝试 - 如果业务允许最终一致性,可以接受
Duplicate entry错误,并在应用层捕获ER_DUP_ENTRY后走重试或降级逻辑
真正麻烦的从来不是怎么建索引,而是当你的数据开始跨库、跨机房、跨 AZ 时,“唯一”这件事就不再由数据库自动兜底了。这时候得想清楚:哪一层该承担这个责任?能不能接受短暂不一致?有没有足够快的补偿通道?










