如何防止在数据库中重复数据(插入、更新)

例如在数据库auditparam表插入某个参数的相关记录,如何防止重复插入操作呢

aspx.cs页面加lock可行吗

好问题。你是在想:既然 InitedRuWeiRange 的 IF NOT EXISTS 不是原子操作、有并发/重复风险,那在 aspx.cs 页面调用处加个 lock 是不是就能堵住?

我直说结论:在 aspx.cs 页面加 lock 基本不可行,也解决不了根因。 原因如下。

为什么说"页面 lock"不可行(如果确实只有一个进程,lock 能覆盖所有并发请求)

1. lock 只能锁当前进程内的当前 AppDomain
lock 是 .NET 进程内、线程级的同步原语。但你的调用链是:

aspx.cs (Web 服务器, e.g. IIS w3wp.exe)
↓ WCF 调用
TdBidCheckWCF

TdBidCheckService

TdBidCheckDao (真正执行 SQL 的地方)
lock 加在 aspx.cs 这一层,只能挡住"同一个 w3wp 进程里的多个线程"。一旦满足下面任一情况,lock 形同虚设:

多实例/多服务器部署(负载均衡、Web 场):两个 w3wp 在不同机器/进程,各自的 lock 互不可见,照样并发 INSERT。
WCF 服务是独立进程/独立宿主:即使单台机器,aspx 锁住后,另一个请求如果走的是另一处直接调 WCF(比如别的页面、后台任务、同步工具)调用 InitedRuWeiRange,根本不经过你的 lock。
事务隔离级别:lock 和数据库事务、锁是两回事。两个请求即使被 lock 串行化,如果第一个 INSERT 还没提交、第二个 IF NOT EXISTS 在默认 READ COMMITTED 下可能仍看不到未提交行(取决于时序),lock 帮不上忙。
2. 你的根因根本不全是"并发"
我前面分析过,最稳定的重复来源是:回退/重置流程清掉了 InitedRuWeiRange 标记,但没清掉 td_AuditPara_Bid 里的 IsRuWeiRange 旧行,再进入页面又 INSERT。

这是单线程、跨多次操作的时序问题,lock 完全管不到——因为两次 INSERT 根本不在同一次请求里,中间隔着一次"回退"操作。

3. lock 在 Web 层是反模式
在 aspx.cs 用 static object 做 lock,会锁住整个应用中所有项目的所有用户的该操作,高并发下直接拖垮页面响应。
而且它可能把"确定入围"这种专家组长的关键操作变成串行瓶颈,得不偿失。
真正对的位置
并发/重复的本质发生在数据库写入那一瞬,所以正确做法是把"防重复"下推到数据访问层(DAO)的 SQL 里,而不是上推到 Web 页面:

方案①(已建议):把 TdBidCheckDao.InitedRuWeiRange 的 INSERT 改成 INSERT ... WHERE NOT EXISTS (同键),这是单条 SQL 的原子判断,数据库层面保证不插重复,不依赖任何进程锁,天然跨进程、跨调用方。
方案④(暂缓):唯一索引是数据库层的"终极锁",但提前加有抛异常风险,故暂缓。
这两层都是在"写库的那个点"解决问题,比在 aspx.cs 加 lock 正确得多。

那 lock 有没有任何用武之地?
有,但不在你这个场景:

如果是一个单机、单进程、且只有这一个入口的少量工具程序(比如 ConsoleApp、SyncProject.Service 那种 Windows 服务),在程序入口做 lock/Monitor 防自身并发还说得过去。但 Web 站点(IIS 多进程/多实例)绝对不适合。
即便如此,也比不过 DAO 层 NOT EXISTS 来得干净。
结论
aspx.cs 加 lock:不可行。

跨进程/跨服务器/跨调用方都挡不住;
管不到"回退清标记→再 INSERT"这种跨请求的时序根因;
还会在 Web 层引入串行瓶颈。
正确做法还是我之前定的三件套:① DAO 层 NOT EXISTS 幂等 INSERT(治本);② 清存量重复数据;③ 报表 LEFT JOIN 去重兜底。这三步都不需要、也不应该在 aspx.cs 加 lock。

 

数据库【写入幂等】的根治方案:写入幂等堵住写入入口(治本,必须做)

把 TdBidCheckDao.InitedRuWeiRange 的 INSERT 改成幂等:先按 (ProjectID, BidID, ParaName) 去重/存在即跳过,而不是靠外部 InitedRuWeiRange 标记。最简单可靠的做法是把第134-148行的 INSERT 改成"存在则跳过":

让"是否已有同键行"的判断和插入在同一条 SQL 里完成——这已经是单条语句的原子操作,数据库层面保证不会插出重复行。

INSERT INTO dbo.td_AuditPara_Bid (ID, aupa_ProjectID, aupa_BidID, aupa_ParaType, aupa_ParaName, aupa_ParaValue, aupa_Memo)
SELECT NEWID(), bi.biin_ProjectID, bi.biin_BidID, 0, 'IsRuWeiRange',
       CASE bg.bigr_State WHEN '0' THEN 'true' ELSE 'false' END, ''
FROM dbo.td_BidInfo bi
LEFT JOIN dbo.td_BidGrade bg ON bi.biin_ProjectID=bg.bigr_ProjectID AND bi.biin_BidID=bg.bigr_BidID
WHERE bi.biin_ProjectID='{0}'
  AND NOT EXISTS (                                          -- 关键:同键不存在才插
        SELECT 1 FROM dbo.td_AuditPara_Bid x
        WHERE x.aupa_ProjectID=bi.biin_ProjectID
          AND x.aupa_BidID=bi.biin_BidID
          AND x.aupa_ParaName='IsRuWeiRange'
  );

 

 

添加CREATE UNIQUE INDEX

-- 只约束已知唯一的参数,避免误伤其它可能允许重复的 ParaName
CREATE UNIQUE INDEX UQ_td_AuditPara_Bid_RuWeiRange
  ON dbo.td_AuditPara_Bid(aupa_ProjectID, aupa_BidID, aupa_ParaName)
  WHERE aupa_ParaName='IsRuWeiRange';

 

 

其他防止重复插入的方案

posted @ 2026-08-19 16:33  无聊的蚂蚁  阅读(11)  评论(0)    收藏  举报