并发 - 一个进程更新,另一个插入

时间:2010-11-09 16:17:58

标签: sql-server tsql linq-to-sql concurrency

我有两个进程可以处理同一个表中的数据。

每天一个进程逐个插入(纯ADO.NET),目标表中大约有20000条记录。

第二个进程调用(定期,每15分钟)一个存储过程

  1. 通过查看7天前的所有记录来检测这20000条记录中的重复项,并将其标记为此类记录。
  2. 使用“ToBeCopied”标记标记所有不重复的记录。
  3. 从标记为“ToBeCopied”的记录中选择一些列并返回该集。
  4. 有时这两个进程重叠(由于数据处理的延迟),我怀疑如果第一个进程在第二个进程介于1和2之间时插入新记录,那么记录将被标记为“ToBeCopied”而没有经过重复筛选。

    这意味着现在存储过程正在返回一些重复项。

    这是我的理论,但在实践中我无法复制它......

    我正在使用LINQ to SQL来插入重复项(每秒40-50左右)并且在运行时我手动调用存储过程并存储其结果。

    当存储过程正在运行时,插入暂停......这样最后没有重复项使其进入最终结果集。

    我想知道LINQ to SQL或SQL Server是否具有阻止并发的默认机制,并且在选择或更新发生时暂停插入。

    您怎么看?

    编辑1:

    '重复'不是相同的行。鉴于这些记录所代表的业务/逻辑实体,它们是“等效的”。每行都有一个唯一的主键。

    P.S。使用NOLOCK选择结果集。尝试在SQL Server 2008上重现。据称在SQL Server 2005上会出现问题。

2 个答案:

答案 0 :(得分:3)

我怎么想?

  • 为什么数据库中有重复项?数据纯度从应用程序绘图板的客户端开始,应该有一个不允许重复的数据模型。
  • 为什么数据库中有重复项?如果客户端应用程序出错,检查约束应该可以防止这种情况发生
  • 如果您有重复项,读者必须准备好处理它们。
  • 你不能分两个阶段检测重复(看起来然后标记),它必须是一个单一的原子标记。事实上,你不能在数据库中做两个阶段的“外观和标记”。所有'寻找记录然后标记发现的记录'并发过程失败。
  • NOLOCK会给你inconsistent reads。记录将丢失或读取两次。使用SNAPSHOT隔离。
  • Linq-To-SQL没有小精灵可以取代糟糕的设计。

更新

例如考虑这个:

具有以下结构的临时表:

 CREATE TABLE T1 (
  id INT NOT NULL IDENTITY(1,1) PRIMARY KEY, 
  date DATETIME NOT NULL DEFAULT GETDATE(), 
  data1 INT NULL, 
  data2 INT NULL, 
  data3 INT NULL);

进程A正在暂时插入此表。它会进行任何验证,它只是将原始记录转储到:

INSERT INTO T1 (data1, data2, data3) VALUES (1,2,3);
INSERT INTO T1 (data1, data2, data3) VALUES (2,1,4);
INSERT INTO T1 (data1, data2, data3) VALUES (2,2,3);
...
INSERT INTO T1 (data1, data2, data3) VALUES (1,2,3);
INSERT INTO T1 (data1, data2, data3) VALUES (2,2,3);
...
INSERT INTO T1 (data1, data2, data3) VALUES (2,1,4);
...

进程B的任务是提取此临时表并将已清理的数据移动到表T2中。它必须删除重复项,这些重复项按业务规则表示data1,data2和data3中具有相同值的记录。在一组重复项中,只应保留date的第一条记录:

set transaction isolation snapshot;
declare @maxid int;

begin transaction
-- Snap the current max (ID) 
--
select @maxid = MAX(id) from T1;

-- Extract the cleaned rows into T2 using ROW_NUMBER() to
-- filter out duplicates
--
with cte as (
 SELECT date, data1, data2, datta3,
   ROW_NUMBER() OVER 
      (PARTITION BY data1, data2, data3 ORDER BY date) as rn
 FROM T1
 WHERE id <= @maxid)
MERGE INTO T2 
USING (
  SELECT date, data1, data2, data3
  FROM cte
  WHERE rn = 1
) s ON s.data1 = T2.data1
  AND s.data2 = T2.data2
  AND s.data3 = T2.data3
WHEN NOT MATCHED BY TARGET
  THEN INSERT (date, data1, data2, data3)
  VALUES (s.date, s.data1, s.data2, s.data3);

-- Delete the processed row up to @maxid
--
DELETE FROM T1 
  WHERE id <= @maxid;
COMMIT;

假设只插入进程A,此过程将安全地处理登台表并提取已清理的重复项。当然,这只是一个框架,一个真正的ETL过程将通过BEGIN TRY / BEGIN CATCH进行错误处理,并通过批处理控制事务日志大小。

答案 1 :(得分:0)

您何时在数据上下文中调用submit?我相信这是在交易中发生的。

关于你的问题,你所说的听起来似乎有道理 - 你可能更有意义的是你加载到临时表(如果它很慢)然后做一个

SELECT * FROM StagingTable INTO ProductionTable 

一旦你的装载完成了吗?