查询不同索引时数据不匹配

时间:2016-04-20 07:25:13

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

我偶然发现了一个非常好奇的案子。我们有一个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)。在运行所有这三个命令之后,加上第一个命令在同一个表中的INSERT,(第二个和第三个仍在等待第一个命令完成)但是当我提交第一个命令时,第二个命令获得旧命令进入,第三个同时获得了新的条目。我认为这种行为可能导致上面解释的错误,但这怎么可能?

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

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

1 个答案:

答案 0 :(得分: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,而不是依赖索引锁定正常工作。