【问题标题】:SQL problem - high volume trans and PK violationSQL 问题 - 大容量反式和 PK 违例
【发布时间】:2009-07-02 18:07:35
【问题描述】:

我正在编写一个大容量交易系统。我们每秒收到大约 300-500 条消息,然后这些消息需要尽快保存到数据库中。这些消息存放在消息队列中,然后从那里读取。

我已经实现了一个竞争消费者模式,它从队列中读取数据并允许对消息进行多线程处理。但是,当应用程序运行时,我经常遇到主键违规问题。

我们正在运行 SQL 2008。示例表结构为:

TableA
{
    MessageSequence INT PRIMARY KEY,
    Data VARCHAR(50)
}

一个存储过程被调用来持久化这条消息,看起来像这样:

BEGIN TRANSACTION

INSERT TableA(MessageSequence, Data )
SELECT @MessageSequence, @Data
WHERE NOT EXISTS
(
  SELECT TOP 1 MessageSequence FROM TableA WHERE MessageSequence = @MessageSequence
)

IF (@@ROWCOUNT = 0)
BEGIN

UPDATE TableA
SET Data = @Data
WHERE MessageSequence = @MessageSequence

END

COMMIT TRANSACTION

所有这些都在一个 TRY...CATCH 块中,所以如果出现错误,它会回滚事务。

我尝试过使用表格提示,例如 ROWLOCK,但并没有什么不同。由于插入被评估为单个语句,因此我仍然遇到“插入时的主键”问题似乎很可笑。

有人知道为什么会这样吗?您有什么想法可以为我指明解决方案的方向吗?

【问题讨论】:

  • 什么版本的sql server?如果是 2008 年,你可以试试 MERGE 语句
  • 我不知道它是否会提高性能,但试试新的 MERGE 语句。对于最近的一个项目,我决定忽略它,因为学习时间太长。不对。使用 BOL 示例的 15 分钟让我的 SP 正常工作。
  • 有趣的是,随着事务加载,可能会发生奇怪的事情。我首先想到的是为什么不使用 IDENTITY 作为密钥,但更好地阅读我知道 ID 在消息中。
  • 我确实尝试了新的 MERGE 语句,但它导致了同样的错误 - 主键违规。去图吧。

标签: sql multithreading sql-server-2008


【解决方案1】:

为什么会这样?

SELECT TOP 1 MessageSequence FROM TableA WHERE MessageSequence = @MessageSequence

此 SELECT 将尝试定位该行,如果未找到,则 EXISTS 运算符将返回 FALSE 并且 INSERT 将继续。然而,INSERT 的决定是基于在 SELECT 时为真的状态,但在 INSERT 时不再保证为真。换句话说,你有竞争条件,两个线程都可以查找相同的@MessageSequence,都返回 NOT EXISTS 并且都尝试插入,当只有第一个会成功时,第二个会导致 PK 冲突。

我该如何解决?

最快的解决方法是在 SELECT 中添加 WITH (UPDLOCK) 提示,这将强制保留 @MessageSequence 键上的锁,从而使 INSERT/SELECT 以原子方式运行:

INSERT TableA(MessageSequence, Data )
   SELECT @MessageSequence, @Data
   WHERE NOT EXISTS (
      SELECT TOP 1 MessageSequence FROM TableA WITH(UPDLOCK) WHERE MessageSequence = @MessageSequence)

为防止 SQL 执行页面锁定等花哨的操作,您还可以添加 ROWLOCK 提示。

但是,这不是我的建议。我的建议可能会让你大吃一惊,但就是这样:执行最有可能成功的操作,如果失败则处理错误。 IE。如果您的业务案例使 @MessageSequnce 更有可能是新的,请尝试 INSERT 并在失败时处理 PK。这样可以避免虚假查找,并且在第一次尝试成功时,捕获/重试的成本会在许多情况下分摊。

另外,使用built-in queues that come with SQL Server 可能值得研究。

【讨论】:

    【解决方案2】:

    【讨论】:

      【解决方案3】:

      可能与事务隔离级别有关。你可能需要

      SET TRANSACTION ISOLATION LEVEL READ COMMITTED

      在您开始交易之前。

      此外,如果您的更新多于插入,则应先尝试更新并检查行数,然后再进行插入。

      【讨论】:

        【解决方案4】:

        这与帖子939831 非常相似。最终你想使用提示(ROWLOCK、READPAST、UPDLOCK)。如果当前记录被锁定,READPAST 告诉 sql server 跳到下一条记录。 UPDLOCK 告诉 sql server 读锁将升级为更新锁。

        当我实现类似的东西时,我通过 threadID 锁定了下一条记录

        UPDATE TOP (1)
            foo
        SET
            ProcessorID = @PROCID
        FROM
            OrderTable foo WITH (ROWLOCK, READPAST, UPDLOCK)
        WHERE
            ProcessorID = 0
        

        然后选择记录

        SELECT *
        FROM foo WITH (NOLOCK)
        WHERE ProcessorID = @PROCID
        

        然后将其标记为已处理

        UPDATE foo
        SET ProcessorID = -1
        WHERE ProcessorID = @PROCID
        

        稍后我会在下班时间执行相对昂贵的操作,即执行删除操作以清除已处理记录的队列。

        【讨论】:

          【解决方案5】:

          以下语句的原子性就是你所追求的:

          INSERT TableA(MessageSequence, Data )
          SELECT @MessageSequence, @Data
          WHERE NOT EXISTS
          (
            SELECT TOP 1 MessageSequence FROM TableA WHERE MessageSequence = @MessageSequence
          )
          

          根据this person,这取决于当前的隔离级别。

          【讨论】:

          • 如果已经存在具有该 ID 的行,他想进行更新。
          • 是的,但相关问题是 INSERT 导致 PK 违规。
          【解决方案6】:

          切线,如果您正在考虑一个大容量交易系统,您可能需要考虑为此类数据设计的分时数据库 [我不确定您在此处存储的“消息”是什么],例如讨论例如在这个线程中:http://www.elitetrader.com/vb/showthread.php?threadid=81345

          这些通常是具有专有查询语言的内存解决方案。我们在我们的商店使用 kdb+。

          【讨论】:

            【解决方案7】:

            不确定您使用的是什么消息传递产品 - 但可能值得关注的不是 DB 级别的事务,而是 MQ 级别的事务。

            当然,如果您使用 TM(事务管理器),则以下两个操作:1)从 MQ 获取和 2)写入 DB 都在同一个父提交下“括起来”。

            所以我不确定您在这里使用的是隐式还是显式或任何 TM(例如,Microsoft 的 DTC)。

            • MessageSequence 是 PK,因此来自 MQ 的同一消息可能会被处理两次。
            • 当您从 MQ 执行“GET”时,请确保 GET 已提交(即不是 db-commit,而是 MQ-commit) - 这将确保相同的 MessageID 不会被下一个线程“弹出”将消息写入数据库。

            【讨论】:

              猜你喜欢
              • 2012-09-03
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-04-10
              • 1970-01-01
              • 1970-01-01
              • 2012-10-01
              • 2020-05-03
              相关资源
              最近更新 更多