Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/sql-server-2008/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Sql server 2008 并行调用存储过程以增加计数器并确保原子增量_Sql Server 2008_Concurrency_Locking_Parallel Processing_Azure Sql Database - Fatal编程技术网

Sql server 2008 并行调用存储过程以增加计数器并确保原子增量

Sql server 2008 并行调用存储过程以增加计数器并确保原子增量,sql-server-2008,concurrency,locking,parallel-processing,azure-sql-database,Sql Server 2008,Concurrency,Locking,Parallel Processing,Azure Sql Database,我正在创建一个存储过程,它可以增加计数器的值,并返回该调用是否负责达到MaxValue。棘手的是,这个过程将从不同的线程和不同的机器上快速并行调用 示例场景: 并行执行的两个线程调用相同的存储过程以递增相同的计数器。让我们假设CounterId=5作为这两个变量的参数传入。在执行任何一项之前,计数器记录当前的字段值为CounterValue=9和MaxValue=10 我想让其中一个过程成功地将CurrentValue增加到10,并返回一个结果,表明它负责进行导致CounterValue达到Ma

我正在创建一个存储过程,它可以增加计数器的值,并返回该调用是否负责达到MaxValue。棘手的是,这个过程将从不同的线程和不同的机器上快速并行调用

示例场景:

并行执行的两个线程调用相同的存储过程以递增相同的计数器。让我们假设CounterId=5作为这两个变量的参数传入。在执行任何一项之前,计数器记录当前的字段值为CounterValue=9MaxValue=10

我想让其中一个过程成功地将CurrentValue增加到10,并返回一个结果,表明它负责进行导致CounterValue达到MaxValue的更改。另一个过程不应增加该值(因为它将超过10),而应返回一个结果,表明计数器已达到MaxReach

我曾考虑在之前或之后执行查询,但这似乎会留下一个“漏洞”,在这个漏洞中,可以通过单独的线程进行更改,并导致返回假阳性/阴性

这只是一个开始的想法的程序。我觉得它需要锁定、交易或其他什么

UPDATE SomeCounters
SET CounterValue = (CounterValue + @AddValue),
    MaxReached = CASE WHEN MaxValue = (CurrentValue + 1) THEN 1 ELSE 0
WHERE CounterId = @CounterId
  AND MaxReached = 0

您需要将其包装到事务中,并在同一事务中添加select,如下所示:

BEGIN TRANSACTION; 

UPDATE SomeCounters
SET CounterValue = (CounterValue + @AddValue)
WHERE CounterId = @CounterId;

SELECT CASE WHEN MaxValue = CurrentValue THEN 1 ELSE 0 MaxReached
FROM SomeCounters
WHERE CounterId = @CounterId;

COMMIT TRANSACTION;

您可以将最后一部分放入输出参数中,以便从过程返回。

假设
MaxValue
是众所周知的,并且对于每个计数器都是相同的,则不需要事务:

UPDATE CounterTable
SET Counter=Counter+1
WHERE CounterId = @CounterId
这是一个数据库,不是多线程程序。这是对SQL Server的一个请求,用于增加表中一行的计数器列的值。SQL Server将做到这一点—我认为它不会允许表丢失一个请求

因此,在最坏的情况下,您可能会得到
Counter>MaxValue
。但是如果你知道MaxValue是什么,那么你就知道它上面的任何值实际上意味着
MaxValue
。不需要在同一事务中立即安排工作

因此,根据“额外工作”对时间的要求,只需让一个作业或其他程序查询表,查找任何大于或等于MaxValue的计数器值,然后立即执行该工作。在最坏的情况下,创建一个触发器在每次更新时触发,这只在计数器值较高时才起作用


不需要事务,除非您需要在计数器更新的同一事务中执行“额外工作”。既然您没有说现在正在使用事务,我怀疑您不需要在同一事务中进行“额外工作”。

使用
输出

DECLARE @temp TABLE (MaxReached BIT NOT NULL);

UPDATE SomeCounters
  SET CounterValue = (CounterValue + @AddValue),
      MaxReached = CASE WHEN MaxValue = (CurrentValue + 1) THEN 1 ELSE 0
  WHERE CounterId = @CounterId
    AND MaxReached = 0
  OUTPUT INSERTED.MaxReached INTO @temp

更新是原子的,然后您可以从@temp表中选择该值,并对其执行任何操作。通过这种方式,您将能够捕获导致MaxReach设置为true(1)的确切更新

实现你所追求的目标的一个方法是采取悲观的态度;这意味着每个存储过程仅在未被另一个存储过程修改的情况下更新记录,然后重试,直到达到最大值。为此,您需要在更新之前读取当前值,然后使用WHERE子句更新记录,该子句期望值相同。如果需要确保调用最终成功,还需要一个循环。使用这种方法,一次只有一个存储过程将更新表,并重试该工作,直到达到最大值

大概是这样的:

DECLARE @savedValue int
DECLARE @maxedReached int
-- read current values for concurrency
SELECT @savedValue = CounterValue, @maxedReached = MaxReached 
  FROM SomeCounters WHERE CounterId = @counterId)

WHILE(@maxedReached = 0)
BEGIN

  UPDATE SomeCounters
  SET CounterValue = (CounterValue + @AddValue),    
    MaxReached = CASE WHEN MaxValue = (CurrentValue + 1) THEN 1 ELSE 0 END
  WHERE 
    CounterId = @CounterId  
    AND MaxReached = 0
    -- the next clause ensures that only one stored procedure will succeed
    AND CounterValue = @savedValue  

  if (@@rowcount = 0)
  BEGIN
    -- failed... another procedure made the change?
    -- If @maxReached becomes 1, the loop will exit and you will
    -- know the maximum was reached; if not the loop will try updating
    -- the value again
    -- read the values for concurrency again.
    SELECT @savedValue = CounterValue, @maxedReached = MaxReached 
        FROM SomeCounters WHERE CounterId = @counterId)

  END
END

我正在研究的另一个策略是在事务中使用sp_getapplock。这似乎可以让我为试图更新的计数器创建一个唯一的字符串,并阻止其他并发执行,直到它完成

这似乎特别有用,因为我的过程还将包含一些IF-EXISTS。。。其他的处理首次创建计数器记录或更新现有计数器记录的逻辑


-sp_getapplock

您需要一个事务,这是真正的需求吗?这是干什么的?是的,这是一个真正的要求。我正在跟踪输入的数据流,我需要记录数据与特定条件匹配的次数,然后在满足MaxValue时触发额外的工作。当前系统将数据和计数器存储在Azure表存储中,并使用乐观并发通过执行所需的最终增量来管理谁“赢”。我需要更好的性能,因此我希望将计数器移动到SQL Azure。因此,使用事务包装这两个语句将确保在调用Select查询时,它将返回仅由上一个update语句所做更改的行?这意味着,在程序A有机会执行Select?@Vyrotek之前,不可能在程序B中进行更新-实际上,要获得该隔离级别,还需要将事务隔离级别设置为可序列化。不一定。当您执行更新时,它会自动对行执行写锁定。在您提交事务之前,没有其他人可以对其进行写入,并且只有在READ_UNCOMMITTED下运行的其他事务可以在提交事务之前读取更新的值。实际上,一条注释是,如果您运行快照隔离,事情可能会变得更糟。但大多数人不这么认为。它会对数据库的tempdb进行垃圾处理,如果数据库中的数据量很大,则会进行垃圾处理。谢谢您的评论。我非常确定服务器上的所有内容都设置为SQLServer2008的默认隔离级别。这似乎是迄今为止最直接的解决方案。我所说的额外工作是C#代码,只有当刚刚执行的更新是导致它到达m的更新时,它才应该在执行过程后立即运行