如何在SQL Server中可靠插入不存在的记录?(多用户环境兼容)
跨数据库可靠初始化数据方案(避免唯一索引冲突)
一、用数据库原生语法杜绝竞态问题
别用「先查询再插入」的逻辑——这种方式在多用户/多实例并发启动时必然出现竞态,导致重复插入触发索引冲突。直接用数据库原生的冲突忽略语法,这些语法是原子操作,由数据库内核层面保证并发安全:
SQLite 实现
使用 INSERT OR IGNORE 语法,唯一索引冲突时自动跳过插入,不会抛出错误:
INSERT OR IGNORE INTO your_table (unique_col, col1, col2) VALUES ('unique_value_1', 'val1', 'val2'), ('unique_value_2', 'val3', 'val4');
PostgreSQL 实现
使用 INSERT ON CONFLICT DO NOTHING 语法,可指定唯一索引对应的字段(复合索引或指定索引名称也支持):
INSERT INTO your_table (unique_col, col1, col2) VALUES ('unique_value_1', 'val1', 'val2'), ('unique_value_2', 'val3', 'val4') ON CONFLICT (unique_col) DO NOTHING;
如果是复合唯一索引,可写成 ON CONFLICT (col_a, col_b) DO NOTHING;也可通过索引名称指定:ON CONFLICT ON CONSTRAINT unique_index_name DO NOTHING。
二、事务锁的必要性说明
不需要额外手动设置事务锁,原因如下:
- 上述原生插入语句本身就是原子执行的,数据库会自动处理并发场景下的索引冲突,不会出现重复插入或报错。
- 如果需要批量插入多条初始化数据、或同时操作多张表,只需把语句放在普通事务中,确保初始化操作的原子性(要么全部完成,要么全部回滚)即可,无需加排他锁之类的特殊锁:
BEGIN TRANSACTION; -- 这里放SQLite/PostgreSQL对应的插入语句 INSERT OR IGNORE INTO table1 (...) VALUES (...); INSERT OR IGNORE INTO table2 (...) VALUES (...); COMMIT;
- 完全兼容「应用运行期间数据可变更」的需求:这些语法只会跳过已存在的记录,不会修改任何现有数据,不会影响业务数据的正常修改。
为什么部分Stack Overflow方案会失效?
那些失效的方案大多采用「SELECT检查是否存在 → 不存在则INSERT」的拆分逻辑,这两步没有原子性保证,多进程并发时会出现两个进程都通过SELECT检查、同时执行INSERT的情况,最终触发唯一索引冲突。而原生冲突处理语法把「检查+插入」合并成一个原子操作,从根源上避免了竞态问题。
内容的提问来源于stack exchange,提问作者Igor
相关产品推荐
相关产品推荐

