如何使用表作为 GUID 池而不遭受竞争条件?

Q''*_*Q'' 1 sql-server blocking

我将获得一个由 SQL Server 数据库在运行时分配的 GUID 列表。

我的直接想法是为 GUID 创建一个表。当一批 GUID 被订购时,它们将被插入到表中。

然后,在运行时,将调用 SPROC 从表中选择 GUID。我可以检索下一个 GUID 并立即将其从表中删除,或者检索下一个 GUID 并更新另一列以声明其已使用。

但这让我想知道同时对数据库进行两次调用以获取 GUID 的竞争条件的可能性。在我看来,如果时间完全不方便的话,可以为这两个调用检索相同的 GUID。

Q1:我对这种潜在竞争状况的担忧是否正确?即它会发生吗?

Q2:有没有办法避免这种情况(例如设置一个 SPROC 以在每次调用时阻塞?)

我能想到的一种至少可以使竞争条件失败的方法是设置另一个进行 GUID 分配的表,并将 GUID 列设置为唯一。然后,在运行时过程中,当检索到 GUID 时,它将通过 GUID 分配表分配给产品。如果为两个产品颁发了相同的 GUID,那么当需要分配它们时,其中一个产品将无法分配并被拒绝。

Aar*_*and 5

基本上,您正在寻找对队列表中下一行的独占访问。假设有这样的表:

CREATE TABLE dbo.GUIDQueue
(
  RowNumber bigint IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
  TheGuid   uniqueidentifier     NOT NULL,
  Claimed   bit                  NOT NULL DEFAULT 0
);

-- insert some n number of rows
DECLARE @n int = 1000;

;WITH src(guid) AS 
(
  SELECT TOP (@n) NEWID() 
    FROM sys.all_objects AS o
    CROSS JOIN sys.all_objects AS s
)
INSERT dbo.GUIDQueue(TheGuid) SELECT guid FROM src;
Run Code Online (Sandbox Code Playgroud)

此过程将序列化删除,而不会阻止另一个会话获取下一个

ALTER PROCEDURE dbo.PluckFromQueue
  @NextGUID uniqueidentifier OUTPUT
AS
BEGIN
  DECLARE @g TABLE(NextGUID uniqueidentifier);

  ;WITH NextGUID AS 
  (
    SELECT TOP (1) TheGuid
      FROM dbo.GUIDQueue WITH (ROWLOCK, READPAST, UPDLOCK)
      ORDER BY RowNumber
  )
  DELETE NextGUID OUTPUT deleted.TheGUID INTO @g;
  
  SELECT @NextGUID = MAX(NextGUID) FROM @g;
END
GO
Run Code Online (Sandbox Code Playgroud)

或者如果您想更新:

ALTER PROCEDURE dbo.PluckFromQueue
  @NextGUID uniqueidentifier OUTPUT
AS
BEGIN
  DECLARE @g TABLE(NextGUID uniqueidentifier);

    ;WITH NextGUID AS 
    (
      SELECT TOP (1) RowNumber, TheGuid, Claimed
        FROM dbo.GUIDQueue WITH (ROWLOCK, READPAST, UPDLOCK)
        WHERE Claimed = 0
        ORDER BY RowNumber
    )
    UPDATE NextGUID SET Claimed = 1
      OUTPUT deleted.TheGUID INTO @g;
  
  SELECT @NextGUID = MAX(NextGUID) FROM @g;
END
GO
Run Code Online (Sandbox Code Playgroud)

您可以像这样从队列中获取下一个项目:

DECLARE @guid uniqueidentifier;
EXEC dbo.PluckFromQueue @NextGUID = @guid OUTPUT;
SELECT @guid;
Run Code Online (Sandbox Code Playgroud)

然后用于@guid您需要执行的任何后续处理。

要测试它是否已序列化,请将其放入两个窗口中:

BEGIN TRANSACTION;
DECLARE @guid uniqueidentifier;
EXEC dbo.PluckFromQueue @NextGUID = @guid OUTPUT;
SELECT @guid;
-- COMMIT TRANSACTION;
Run Code Online (Sandbox Code Playgroud)

运行一个,然后运行另一个,两者都会得到一个值。不要忘记同时提交两者。

Remus 和 gbn 讨论了为什么您希望对出列查询提供这三个锁定锁定提示。

如果您想在没有这些提示的情况下观察会发生什么(也许这就是您想要的行为),请将它们注释掉,更改过程,然后重复上面的实验 - 您将看到第一个块会阻止第二个块,直到第一个块提交为止。无论你认为时间有多接近,一个人会赢,另一个人会等待。但无论哪种情况,都不要通过将过程调用包装在事务中来加剧这种情况。您想要这样做的唯一原因是您想“撤消”出列操作。如果由于某种情况或异常而最终没有使用队列中的 GUID,那么好吧,将其视为损失(或者您始终可以将该值插入回队列中)。

如上所述,也不要忘记提交第二个事务。

我在这里写了类似的内容(生成一大堆唯一数字的美妙之处,您不必稍后确认它们是唯一的),但在那篇文章中,我没有解决如果您想回滚索赔该怎么办:

您可能会考虑添加某种后台进程来监视队列的大小(或者还剩下多少无人认领的行,在这种情况下,过滤索引可能会很方便),这样您就可以在到达队列时自动补充新行。某个点(而不是在交易中耗尽)。确保您的后台进程以智能方式计算行数