You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SQL中如何实现SELECT判断后执行INSERT 规避并发重复插入问题

PostgreSQL 原子化实现「不存在则插入」规避并发重复写入

针对多并发下的竞态重复插入问题,不要在应用层拆分SELECT判断+INSERT写入两步操作,直接用PostgreSQL提供的原生原子语法即可,配合唯一约束可以100%拦住重复数据。

核心前置要求

必须先给判重维度加数据库级别的唯一约束,这是所有方案生效的基础,任何代码层判断都替代不了数据库约束的兜底作用。
你的判重逻辑是「同个product_id当日只允许一条payment记录」,因为created_at一般是带时分秒的时间戳类型,需要建表达式唯一索引:

CREATE UNIQUE INDEX idx_payment_product_daily ON payment (product_id, (created_at::date));

推荐写法:INSERT ... ON CONFLICT(PG 9.5+支持)

这是性能最好、并发安全性最高的方案,整条语句是原子执行的,不会出现两个请求同时通过判断、重复插入的问题。

INSERT INTO payment (product_id, created_at, amount /* 其余需要插入的字段 */)
VALUES ('1', CURRENT_TIMESTAMP, 99.9 /* 对应字段的值 */)
-- 冲突判定字段和上面建的唯一索引保持一致
ON CONFLICT (product_id, (created_at::date))
DO NOTHING
-- 加RETURNING可以直接拿到插入结果:返回行代表插入成功,无返回代表当日该产品已存在记录
RETURNING id, product_id, created_at;

不推荐但可用的写法:INSERT ... SELECT ... WHERE NOT EXISTS

如果暂时没法加唯一约束(非常不建议长期这么用),可以用下面的写法,注意高并发场景下这个写法依然有小概率出现重复,需要把事务隔离级别提升到可串行化才安全,性能损耗更大:

INSERT INTO payment (product_id, created_at, amount /* 其余字段 */)
SELECT '1', CURRENT_TIMESTAMP, 99.9 /* 对应字段值 */
WHERE NOT EXISTS (
    SELECT 1 FROM payment
    WHERE product_id = '1'
    AND created_at::date = CURRENT_DATE
)
RETURNING id, product_id, created_at;
Django ORM 对应实现

不要自己手写filter().exists()判断后再调用create(),这是典型的应用层非原子判断,并发下必然会出现重复插入。

  1. 先在模型里定义唯一约束,执行makemigrations和migrate生效:
from django.db import models
from django.db.models.functions import TruncDate

class Payment(models.Model):
    product = models.ForeignKey('Product', on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    amount = models.DecimalField(max_digits=10, decimal_places=2)
    # 其余字段定义

    class Meta:
        constraints = [
            models.UniqueConstraint(
                'product',
                TruncDate('created_at'),
                name='uniq_payment_product_per_day'
            )
        ]
  1. 插入数据直接用Django内置的get_or_create即可,ORM会自动适配数据库的原子冲突处理逻辑:
from datetime import date

payment, is_created = Payment.objects.get_or_create(
    product_id='1',
    created_at__date=date.today(),
    defaults={
        'amount': 99.9
        # 其余非判重字段的默认插入值放在这里
    }
)
# is_created为布尔值:True代表本次新插入成功,False代表当日该产品已有记录

如果需要更灵活的控制,也可以直接用数据库游标执行上面写的原生ON CONFLICT语句,拿到返回结果判断状态。

常见误区说明
  • 不要以为把SELECT和INSERT放在同一个事务里就安全,默认的读已提交隔离级别下,两个并发事务完全可能同时执行SELECT查到记录不存在,再依次执行INSERT产生重复数据
  • 加表级排他锁虽然能解决重复问题,但会把写入操作串行化,吞吐量会大幅下降,远不如数据库原生的ON CONFLICT效率高
  • 唯一约束是最后一道兜底,哪怕代码逻辑写漏了分支,数据库层面也不会允许重复数据写入,不会产生脏数据

内容的提问来源于stack exchange,提问作者devnext

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 16:15:33