AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / dba / 问题 / 337710
Accepted
J.D.
J.D.
Asked: 2024-03-14 05:46:16 +0800 CST2024-03-14 05:46:16 +0800 CST 2024-03-14 05:46:16 +0800 CST

我的队列表实现竞争条件安全吗?

  • 772

大家好,比我聪明的人!我创建了一个队列表系统,但它似乎太简单,无法避免竞争条件。我是否遗漏了什么或者以下竞争条件安全吗?

模式

我有一张桌子,我们称之为ProductQueue:

CREATE TABLE dbo.ProductQueue
(
    SerialId BIGINT PRIMARY KEY,
    QueuedDateTime DATETIME NOT NULL -- Only using this for reference, no functionality is tied to it
);

我有添加到队列的过程,称为AddToProductQueue:

CREATE PROCEDURE dbo.AddToProductQueue (@SerialId BIGINT)
AS
BEGIN
    INSERT INTO dbo.ProductQueue (SerialId, QueuedDateTime)
    OUTPUT Inserted.SerialId
    SELECT @SerialId, GETDATE();
END

我还有一个从队列中删除的过程,称为RemoveFromProductQueue:

CREATE PROCEDURE dbo.RemoveFromProductQueue (@SerialId BIGINT)
AS
BEGIN
    DELETE FROM dbo.ProductQueue
    OUTPUT Deleted.SerialId
    WHERE SerialId = @SerialId;
END

注意,对于源数据库/系统中的SerialIda 来说是全局唯一的。Product即,a 的两个实例Product不可能具有相同的SerialId。这就是数据库方面的范围。

工作流程

  • 我有一个每小时运行的申请流程。
  • SerialIds该进程从源系统获取变量列表。
  • 它迭代地调用其列表中AddToProductQueue每个的过程SerialId。
  • 如果该过程尝试插入表SerialId中ProductQueue已存在的 ,则会引发主键冲突错误,并且应用程序进程会捕获该错误并跳过该错误SerialId。
  • 否则,该过程会成功地将其添加SerialId到ProductQueue表中并将其返回给应用程序进程。
  • 然后,应用程序进程将成功排队的添加SerialId到单独的列表中。
  • 应用程序进程完成迭代所有要SerialIds入队的候选者列表后,它会迭代其成功排队的新列表,并在每个的单独线程SerialIds中对它们进行外部工作。(这项工作与数据库无关。)SerialId
  • 最后,当每个线程完成其外部工作时,该异步线程中的最后一步是通过调用该过程将其SerialId从表中删除。(请注意,会实例化一个新的数据库上下文对象,并为每个异步调用此过程创建一个新连接,因此它在应用程序端是线程安全的。)ProductQueueRemoveFromProductQueue

附加信息

  • 表上没有任何索引ProductQueue,并且表中的行数永远不会超过 1,000 行。(实际上,大多数时候它实际上只有几行。)
  • 相同的SerialId可以再次成为在应用程序进程的未来执行时被重新添加到队列表中的候选者。
  • 没有安全措施可以阻止应用程序进程的第二个实例同时运行,无论是意外还是第一个实例运行时间超过 1 小时等。(这是我最关心的并发部分。)
  • 队列表和过程所在的数据库(以及正在建立的连接)的事务隔离级别是默认隔离级别Read Committed。

潜在问题

  • 应用程序进程的运行实例以未处理的方式崩溃,卡SerialIds在队列表中。这对于业务需求来说是可以接受的,我们计划提供异常报告来帮助我们手动修复这种情况。
  • 应用程序进程同时执行多次,并SerialIds在其初始源列表中的实例之间获取一些相同的内容。我还无法想到这种情况的任何负面影响,因为排队过程是原子的,并且SerialIds由于该原子排队过程,应用程序进程将处理的实际列表应该是独立的。我们并不关心应用程序进程的哪个实例实际处理每个进程SerialId,只要SerialId两个进程实例不同时处理相同的进程即可。
sql-server
  • 4 4 个回答
  • 863 Views

4 个回答

  • Voted
  1. Best Answer
    Paul White
    2024-03-14T17:09:44+08:002024-03-14T17:09:44+08:00

    在这种情况下,您要求数据库引擎做的唯一一件事就是强制执行PRIMARY KEY. 当然,它会在所有条件和隔离级别下执行此操作。

    潜在的竞争条件都是数据库外部的,这里的考虑因素并不是真正的主题。

    也就是说,我认为数据库参与竞争条件的唯一方法是添加候选序列 ID 的过程,并将其包装在数据库事务中,但您没有提到任何相关内容。

    也许进程可能会以意想不到的方式使用数据库事务,例如,如果您使用的 ORM 在没有明确询问的情况下通过魔法完成了有用的事情。或者也许您正在使用隐式事务。

    在这种情况下,应用程序实例 A 将开始在单个数据库事务中添加其(一长串)序列 ID。同时,应用程序实例 B(具有同样长的列表,至少包括 A 列表中存在的一个)在插入重叠序列 ID 时将被阻止(因为实例 A 尚未提交其事务)。

    在一系列不幸的事件中,实例 A 将在被阻止的实例 B 执行其插入(朝向其列表的末尾)之前完成异步处理重复的序列 ID(朝向其列表的开头)。

    在这种情况下,A 和 B 都会成功处理相同的序列 ID。

    考虑到您所描述的流程概要,这似乎不太可能,但并非不可能。

    • 6
  2. Quassnoi
    2024-03-15T02:41:43+08:002024-03-15T02:41:43+08:00

    我不确定为什么你的表的名称中有“队列”一词,但在我看来,你真正想要的是锁定机制。

    直接回答你的问题:如果你不关心过时的锁,并且只要INSERT事务、处理和DELETE事务处于因果关系(即在上一步报告成功之后按此顺序发生),我不认为没有看到任何双重处理的潜力。


    也就是说,以自动化方式解决您在“潜在问题”下列出的这两个问题的一种方法是实现锁定算法Redlock的单实例变体。

    1. 每个实例都有自己唯一的 ID(例如,一个 guid 或每次运行处理应用程序时生成的内容)

    2. 每当实例获取 a 时SerialId,它都会尝试获取它的锁:

      CREATE TABLE ItemLock (serialId BIGINT NOT NULL PRIMARY KEY, instance UNIQUEIDENTIFIER NOT NULL, expires DATETIME NOT NULL);
      
      WITH    source (serialId, instance, expires) AS
              (
              SELECT  @serialId, @instance, @expires
              )
      MERGE
      INTO    ItemLock target
      USING   source
      ON      target.serialId = source.serialId
      WHEN NOT MATCHED BY TARGET THEN
      INSERT  (serialId, instance, expires)
      VALUES  (serialId, instance, expires)
      WHEN MATCHED AND (target.instance = source.instance OR target.expires <= @now) THEN
      UPDATE
      SET     instance = source.instance,
              expires = source.expires
      OUTPUT  INSERTED.serialId
      

      查询的输出(如果有)将是您锁定的序列 ID。如果没有输出,则说明您没有锁定。

      将当前时间戳传入变量中@now,将未来五分钟的时间戳传入变量中@expires

    3. 运行一个保持活动的线程,每分钟或您需要的频率提交此查询,扩展变量中的值@expires,例如每次过去五分钟。您可以修改查询以扩展迄今为止在单个批次中尚未处理的锁。确保立即承诺。

    4. 完成处理后,仅删除属于您的锁:

      DELETE
      FROM    ItemLock
      WHERE   serialId = @serialId
              AND instance = @instance
      

    这边走:

    1. 如果你的应用程序死掉,锁会在 5 分钟内自行死掉。
    2. 只要您成功获取锁并保持其活动状态,就不会发生并发处理。

    当然,您可以只安装一个真实的 Redis 实例,并在您的应用程序中使用 Redlock 的现成实现,其中有很多。

    • 2
  3. S. Rojak
    2024-03-16T05:55:58+08:002024-03-16T05:55:58+08:00

    我发现 Service Broker 完全不能令人满意,造成了很大的开销。对话需要清理。你的桌子会更好。

    我还建议在主键后面的索引上允许行锁。

    • 0
  4. riccardo
    2024-03-15T17:33:16+08:002024-03-15T17:33:16+08:00

    您所做的事情没有任何问题,但 SQL Server 已经有一个内置服务 Broker Services 来处理和管理数据队列。我谦虚地建议你投入时间。谢谢😀

    • -1

相关问题

  • SQL Server - 使用聚集索引时如何存储数据页

  • 我需要为每种类型的查询使用单独的索引,还是一个多列索引可以工作?

  • 什么时候应该使用唯一约束而不是唯一索引?

  • 死锁的主要原因是什么,可以预防吗?

  • 如何确定是否需要或需要索引

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    连接到 PostgreSQL 服务器:致命:主机没有 pg_hba.conf 条目

    • 12 个回答
  • Marko Smith

    如何让sqlplus的输出出现在一行中?

    • 3 个回答
  • Marko Smith

    选择具有最大日期或最晚日期的日期

    • 3 个回答
  • Marko Smith

    如何列出 PostgreSQL 中的所有模式?

    • 4 个回答
  • Marko Smith

    列出指定表的所有列

    • 5 个回答
  • Marko Smith

    如何在不修改我自己的 tnsnames.ora 的情况下使用 sqlplus 连接到位于另一台主机上的 Oracle 数据库

    • 4 个回答
  • Marko Smith

    你如何mysqldump特定的表?

    • 4 个回答
  • Marko Smith

    使用 psql 列出数据库权限

    • 10 个回答
  • Marko Smith

    如何从 PostgreSQL 中的选择查询中将值插入表中?

    • 4 个回答
  • Marko Smith

    如何使用 psql 列出所有数据库和表?

    • 7 个回答
  • Martin Hope
    Jin 连接到 PostgreSQL 服务器:致命:主机没有 pg_hba.conf 条目 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane 如何列出 PostgreSQL 中的所有模式? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh 为什么事务日志不断增长或空间不足? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland 列出指定表的所有列 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney MySQL 能否合理地对数十亿行执行查询? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx 如何监控大型 .sql 文件的导入进度? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison 你如何mysqldump特定的表? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 对 SQL 查询进行计时? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas 如何从 PostgreSQL 中的选择查询中将值插入表中? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 列出所有数据库和表? 2011-02-18 00:45:49 +0800 CST

热门标签

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve