C# 如何在一个事务中移动记录?SQL中的生产者或消费者模式

C# 如何在一个事务中移动记录?SQL中的生产者或消费者模式,c#,sql,sql-server,tsql,design-patterns,C#,Sql,Sql Server,Tsql,Design Patterns,令人惊讶的是,我找不到与我的问题相匹配的答案。我有一个表,我需要使用一个数据源插入到另一个表中,然后删除插入的内容。这必须以分块方式完成,即,如果同时执行相同的查询/SP,则不得移动相同的记录,从而创建重复记录 我觉得这是一件相对简单的事情,但我不确定我是否完全理解SQL中的锁定是如何工作的。它在C语言中看起来非常简单,只是一个监视器,但SQL 作为一个例子,可以考虑生产者-消费者模型,即某种作为队列的表,一些线程插入其中一些消耗。插入显然不是问题,但消费是我想知道的 更新: 我有两个很好的备选

令人惊讶的是,我找不到与我的问题相匹配的答案。我有一个表,我需要使用一个数据源插入到另一个表中,然后删除插入的内容。这必须以分块方式完成,即,如果同时执行相同的查询/SP,则不得移动相同的记录,从而创建重复记录

我觉得这是一件相对简单的事情,但我不确定我是否完全理解SQL中的锁定是如何工作的。它在C语言中看起来非常简单,只是一个监视器,但SQL

作为一个例子,可以考虑生产者-消费者模型,即某种作为队列的表,一些线程插入其中一些消耗。插入显然不是问题,但消费是我想知道的

更新: 我有两个很好的备选解决方案:

使用SELECT FOR UPDATE需要计算行锁的长度 持有 在对记录进行操作之前,请使用字段标记记录 还需要弄清楚可序列化IL的东西

谢谢大家的努力和回复——这个社区太棒了。

一些建议

确保您的表具有正确的键或唯一的约束,以便不能插入重复项

我将使用存储过程在begin/commit事务中执行大容量移动插入+删除。我还将确保选择要移动的行是通过行级锁定完成的。但是,如果这些表有大量select请求,这可能会对性能产生影响

或者,您可以实际锁定调用此操作的C代码,阻塞以确保没有用户可以同时输入调用方法


使用“选择更新”锁定源表中的行,将这些行复制到目标表,然后删除这些行。另一个执行相同逻辑的线程将在select for update调用处等待。

如果使用SQL Server 2008,则可以使用编辑1:添加了一个有关ANSI_警告和ARITHABORT OFF的小说明


如果您使用SQL Server 2008,我发现您对此版本有疑问,可以尝试一下

简单解决方案:

INSERT  Target
SELECT  q.Id, q.Name, q.Type
FROM
(
        DELETE  Source 
        OUTPUT  deleted.Id, deleted.Name, deleted.Type
        WHERE   Type = @Type --or another search condition 
) q;
复杂场景,包括错误:

1.第一个测试用例演示了这种技术

2.第二个测试演示了在语句执行过程中遇到错误时的行为:语句INSERT+DELETE输出被取消,但批处理直到最后一条语句才被执行

3.对于第三个测试,您可以看到错误会中止整个批处理,并且语句INSERT+DELETE OUTPUT也被取消

在此脚本中,有关错误的行为使用三种设置进行控制:和。当ANSIU WARNINGS和ARITHABORT两个设置都关闭时,此表达式1/0将被计算为NULL=>因此将被插入。。。空

SET NOCOUNT ON;

CREATE TABLE dbo.Source (Id INT PRIMARY KEY, Name VARCHAR(10) NOT NULL, Type TINYINT NOT NULL);
INSERT  dbo.Source (Id, Name, Type) VALUES (1,'A',1), (2, 'B',1), (3, 'C',2), (4, 'D',2), (5, 'E',2);

CREATE TABLE dbo.Target (Id INT PRIMARY KEY, Name VARCHAR(10) NOT NULL, Type TINYINT /*NOT*/ NULL);


    --***** Test 1 Ok *****
        DECLARE @Type INT = 1;

        SELECT  'Test 1 Ok' AS Description;
        BEGIN TRAN;
        INSERT  Target
        SELECT  q.Id, q.Name, q.Type
        FROM
        (
                DELETE  Source 
                OUTPUT  deleted.Id, deleted.Name, deleted.Type
                WHERE   Type = @Type
        ) q;

        SELECT  * FROM Target;
        SELECT  * FROM Source;
        --It will be fine to COMMIT transaction but I will cancel to run the second and third test
        ROLLBACK TRAN 
        SELECT  'End of Test 1 Ok' AS Description;
        GO
    --***** End of Test 1 *****

    --***** Test 2 Err *****
        --Start another batch
        GO 
        SET ARITHABORT ON;
        SET ANSI_WARNINGS ON;
        SET XACT_ABORT OFF;

        DECLARE @Type INT = 1;

        SELECT  'Test 2 Err' AS Description, SESSIONPROPERTY('ARITHABORT') [ARITHABORT_STATUS], SESSIONPROPERTY('ANSI_WARNINGS') [ANSI_WARNINGS_STATUS];

        INSERT  Target
        --Divide by zero => Abort statement only
        SELECT  q.Id, q.Name, CASE WHEN q.Id <> 2 THEN q.Type ELSE  1/0 END 
        FROM
        (
                DELETE  Source 
                OUTPUT  deleted.Id, deleted.Name, deleted.Type
                WHERE   Type = @Type
        ) q;

        SELECT  * FROM Target;
        SELECT  * FROM Source;
        SELECT  'End of Test 2 Err' AS Description;
    --***** End of Test 2 *****

    --***** Test 3 *****
        --Start another batch
        GO 
        SET ANSI_WARNINGS OFF;
        SET ARITHABORT ON;
        SET XACT_ABORT OFF;

        DECLARE @Type INT = 1;

        SELECT  'Test 3 Err' AS Description, SESSIONPROPERTY('ARITHABORT') [ARITHABORT_STATUS], SESSIONPROPERTY('ANSI_WARNINGS') [ANSI_WARNINGS_STATUS];

        INSERT  Target
        --Divide by zero => Abort batch
        SELECT  q.Id, q.Name, CASE WHEN q.Id <> 2 THEN q.Type ELSE  1/0 END
        FROM
        (
                DELETE  Source 
                OUTPUT  deleted.Id, deleted.Name, deleted.Type
                WHERE   Type = @Type
        ) q

        --This statement is not executed 
        SELECT  * , 1 AS Statement  FROM Target;
        --This statement is not executed 
        SELECT  * , 1 AS Statement FROM Source;
        --This statement is not executed 
        SELECT  'End of Test 3 Err' AS Description

        GO --Start another batch    
        SELECT  * , 2 AS Statement FROM Target;
        SELECT  * , 2 AS Statement FROM Source;
    --***** End of Test 3 *****

    DROP TABLE dbo.Source;
    DROP TABLE dbo.Target;

C在多台机器的情况下锁定不起作用。对于SQL,任何事务隔离级别都不允许阻塞。虽然我仍在试图理解SERIALIZABLE的含义,但目前看来SQL Server从未阻塞。这只是从一个或另一个事务中可以看到的数据。虽然序列化可能会有所不同…@Schultz9999您是对的…我不确定这是服务器端代码还是客户端代码,以及服务器端、单个实例等是否是我目前正在查看的。他们说它类似于可序列化的IL,我对此缺乏了解。老实说,我一点也不喜欢锁。随着数据库受到越来越多的影响,性能问题越来越多。您可能需要考虑一种不同的方法,例如为删除标记行。基本上标记的行永远不会被选中,因此您可以在空闲时删除它们,而不必担心太多的锁定问题。事实上,我认为您的观点很好,我也很喜欢,mike01010。假设一个ReadCommitted IL,更新状态字段是原子的,在选择之前或之后应该只考虑该标志。这对我的案子有用。谢谢问题不是很清楚,你能给出代码示例吗?到目前为止,我了解到有两个表source和target,插入source的记录必须经过处理,然后插入target并从source中删除。这是正确的吗?我相信生产者-消费者模式是可以理解的例子。我感到困惑的一部分是源代码中的重复插入。通常,生成的每条消息都是单独使用的。在您的情况下,必须忽略源中重复的并发插入,但如果第二次插入发生在第一次插入之后,则将对其进行处理,对吗?还是不应该将其插入目标?在后一种情况下,是否也要求不处理该行?有关SERIALIZABLE@Schultz9999-hmm的一些见解。。。这是一个有趣的建议。我来看看。同时,我也发现了这个。信息技术
看起来也很有趣,但是更新似乎是一个持续时间更长的锁,我假设它一直持续到当前事务结束,而输出允许原子地执行几个SQL操作。仅供参考,选择一些经验死锁:mike01010给了我一个很好的线索,但我仍然喜欢你的帖子。所以+1.MERGE语句只能从目标表中插入、更新和删除记录。因此,无法从源表中删除行。