【问题标题】:Data mismatch when querying with different indexes使用不同索引查询时数据不匹配
【发布时间】:2016-04-20 07:25:13
【问题描述】:

我偶然发现了一个非常奇怪的案例。我们有一个 SQL Server 2012 数据库和这样一个表

CREATE TABLE [dbo].[ActiveTransactions]
(
    [Id] [BIGINT] IDENTITY(1,1) NOT NULL,
    [Amount] [DECIMAL](12, 4) NOT NULL,
    [TypeId] [SMALLINT] NOT NULL,
    [GameProviderId] [SMALLINT] NULL,
    [UserId] [INT] NOT NULL,
    [Checksum] [NVARCHAR](150) NOT NULL,
    [Date] [DATETIME2](7) NOT NULL,
    [ExternalKey] [VARCHAR](60) NULL,
    [ExternalDescription] [NVARCHAR](1000) NULL,
    [OperatorId] [SMALLINT] NULL,
    [GameId] [NVARCHAR](50) NULL
)

这个表有多个索引,但我想在这里讨论的两个是PK_ActiveTransactions(主键,聚集):

ALTER TABLE [dbo].[ActiveTransactions] 
    ADD CONSTRAINT [PK_ActiveTransactions] 
    PRIMARY KEY CLUSTERED ([Id] DESC)

IX_ActiveTransactions_UserIdAmount(非集群,非唯一):

CREATE NONCLUSTERED INDEX [IX_ActiveTransactions_UserIdAmount] 
ON [dbo].[ActiveTransactions] ([UserId] ASC, [Id] DESC)
INCLUDE ([Amount])

有一个查询依赖于我的解决方案的主要部分,并在某个进程启动时被调用。基本上每次在我的代码端调用SomeMethod 时,它都会启动 SQL 事务,然后执行过程(如下所示),从而锁定它选择的条目,然后计算一些东西并在该表中插入新行并提交交易。锁定过程执行此 SQL 语句

SELECT TOP 1
        id ,
        Amount ,
        TypeId ,
        GameProviderId ,
        UserId ,
        [Checksum] ,
        [Date] ,
        ExternalKey
FROM    ActiveTransactions WITH ( UPDLOCK )
WHERE   @UserId = UserId
ORDER BY Id DESC

现在情况就是这样。当我查看此表中的一些条目时,似乎有多个(同时请求的)条目为相同的@UserId 选择了相同的条目。确切地说,有 5 个新条目(当时请求,因为它们具有在代码端计算的完全相同的 [Date] 值),它们都选择了相同的条目,然后重新计算了一些东西(所有 5 个都计算了同样的事情)并同时插入5个新行,而不是一一插入(这应该是由SELECT查询末尾的WITH(UPDLOCK)语句引起的,我相信)。

然后我尝试做这样的事情,我打开了三个新的查询窗口,我在一个窗口中使用BEGIN TRAN 命令启动了一个事务,然后在上面的 SELECT 语句中执行,在其他两个窗口中我做了同样的事情,当我提交第一个语句,第二个查询在提交第二个语句之后立即获取它,第三个语句获取它。 (一切都按预期工作),在查询末尾添加WITH INDEX(INDEX_NAME)UPDLOCK 仍然存在)后,事情开始看起来很奇怪。对于第一个选择,我指定了WITH INDEX(PK_ActiveTransactions)(主键),对另外两个我指定了WITH INDEX(IX_ActiveTransactions_UserIdAmount)。在运行所有 3 个命令后,加上与第一个命令在同一个表中的 INSERT,(第二个和第三个仍在等待第一个命令完成)但是当我提交第一个命令时,第二个获取旧条目和第三个同时获得了新条目。我认为这种行为可能导致了上面解释的错误,但这怎么可能呢?

SQL Server 是否会同时对同一个查询使用两个不同的执行计划(因此使用不同的索引)?该表在一天结束时达到 10-15 百万个条目,但每天早上大约 6 点执行的作业只剩下 1-2 百万行。这会导致 SQL Server 意外切换索引吗?但无论如何,我认为这是一个系列问题,这意味着即使在提交之后,索引也可能不包含已提交的数据。

上述问题只发生过几次,我能够识别它们发生了两次

【问题讨论】:

  • 索引很可能使用行锁,而 PK 将使用表锁。
  • @SeanPearce 在这种情况下不同的数据来自哪里?你的意思是说用PK从表中选择一个条目会锁定整个表?
  • PK 在 id 上,搜索是通过 userid,所以锁的行为会有所不同。如果没有索引来定位要锁定的行,所有测试的行都会被锁定,并且对符合条件的行的锁定会一直保持到事务完成为止。

标签: sql-server database indexing locking sql-execution-plan


【解决方案1】:

您需要检查在您的表和索引上获取了哪些锁(请参阅下面的链接)。 SQL Server 能够对索引和数据进行单独的锁定。默认情况下它不会锁定所有索引。
注意:以下是猜测。
查询 #1 永远不会在 IX_ActiveTransactions_UserIdAmount 上获得锁,因此查询 #2 能够搜索索引并在其上获取锁,然后等待释放行数据锁以完成其操作。一旦这个锁被释放,查询#2 就会抓住它并持有它,同时执行你的其他代码。
与此同时,查询 #3 仍在等待数据锁和索引锁。一旦查询 #2 完成并释放所有锁,只有查询 #3 才能使用索引进行搜索,从而搜索最新数据。

总结:
查询 #1 和查询 #2 都能够并行搜索表并返回相同的行。查询 #2 必须等待查询 #1 完成才能获得更新锁。由于查询 #1 实际上并没有修改最后一行,而是插入一个新行,因此对于查询 #2,索引不会更改。
请参阅https://www.mssqltips.com/sqlservertip/1485/using-sql-server-indexes-to-bypass-locks/ 以讨论与您的问题相反的情况。

补充说明:
我认为锁定特定用户 ID 的“用户”表(如果存在)而不是依赖索引锁定正常工作会更可靠,并且可能会提供更好的性能。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-06
    • 1970-01-01
    • 2021-06-13
    • 1970-01-01
    相关资源
    最近更新 更多