不同索引查询时数据不匹配
Data mismatch when querying with different indexes
我偶然发现了一个非常奇怪的案例。我们有一个 SQL Server 2012 数据库和这样一个 table
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
)
这个 table 有多个索引,但我想在这里谈论的两个是 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 事务,然后执行过程(如下所示),从而锁定条目 selects,然后计算一些东西和在 table 中插入新行并提交事务。锁定过程执行此 SQL 语句
SELECT TOP 1
id ,
Amount ,
TypeId ,
GameProviderId ,
UserId ,
[Checksum] ,
[Date] ,
ExternalKey
FROM ActiveTransactions WITH ( UPDLOCK )
WHERE @UserId = UserId
ORDER BY Id DESC
现在是这样的。当我查看此 table 中的一些条目时,似乎有多个(同时请求的)条目 select 为相同的 @UserId
编辑了相同的条目。确切地说,有 5 个新条目(当时要求,因为它们具有完全相同的 [Date]
值,这是在代码端计算的)所有 selected 相同的条目然后重新计算一些东西(所有 5 个都计算相同的东西)并同时插入 5 个新行,而不是一个一个地插入(这应该是由 SELECT 查询末尾的 WITH(UPDLOCK)
语句引起的,我相信)。
然后我尝试做这样的事情,我打开了三个新查询 windows,我在一个 window 中用 BEGIN TRAN
命令开始了一个事务,然后在上面执行了 SELECT 语句,在其他两个 windows 中,我做了同样的事情,当我提交第一个语句时,第二个查询立即获取了它,在提交第二个语句之后,第三个查询获取了它。 (一切都按预期工作),在查询末尾添加 WITH INDEX(INDEX_NAME)
(UPDLOCK
仍然存在)后,事情开始变得奇怪。对于第一个 select,我指定了 WITH INDEX(PK_ActiveTransactions)
(主键),对于另外两个,我指定了 WITH INDEX(IX_ActiveTransactions_UserIdAmount)
。在 运行 所有 3 个命令之后,加上与第一个命令相同的 table 中的 INSERT
,(第二个和第三个仍在等待第一个完成)但是当我提交时第一个命令,第二个获取旧条目,第三个同时获取新条目。我认为这种行为可能导致了上面解释的错误,但这怎么可能呢?
服务器会不会SQL服务器同时对同一个查询使用两个不同的执行计划(因此使用不同的索引)?这个 table 在一天结束时达到 10-15 百万个条目,但每天早上大约早上 6 点执行作业,这使得 table 只有 1-2 百万行。这会导致 SQL 服务器意外切换索引吗?但无论如何,我认为这是一个系列问题,意味着即使在提交之后,索引中也可能不包含提交的数据。
上面的问题只发生过几次,我能确定它们发生了两次
您需要检查在您的 table AND 索引上获取了哪些锁(参见下面的 link)。 SQL 服务器能够对索引和数据分别进行锁定。默认情况下它不会锁定所有索引。
注:以下为猜测。
查询 #1 从未获取 IX_ActiveTransactions_UserIdAmount 上的锁,因此查询 #2 能够搜索索引并在其上获取锁,然后等待行数据锁被释放以完成其操作。一旦这个锁被释放,查询 #2 就会抓住它并持有它,同时执行你的其他代码。
同时,查询 #3 仍在等待数据和索引锁。一旦查询 #2 完成并释放所有锁,查询 #3 才能使用索引进行搜索,从而搜索最新数据。
总结:
查询 #1 和查询 #2 都能够并行搜索 table 和 return 同一行。查询 #2 必须等待查询 #1 完成才能获得更新锁。由于查询 #1 实际上并没有修改最后一行,而是插入了一个新行,因此对于查询 #2 而言,索引没有更改。
请参阅 https://www.mssqltips.com/sqlservertip/1485/using-sql-server-indexes-to-bypass-locks/ 以了解有关您的问题的反面的讨论。
其他评论:
我认为为特定用户 ID 锁定 "Users" table(如果存在)而不是依赖索引锁定正常工作会更可靠并且可能会提供更好的性能。
我偶然发现了一个非常奇怪的案例。我们有一个 SQL Server 2012 数据库和这样一个 table
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
)
这个 table 有多个索引,但我想在这里谈论的两个是 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 事务,然后执行过程(如下所示),从而锁定条目 selects,然后计算一些东西和在 table 中插入新行并提交事务。锁定过程执行此 SQL 语句
SELECT TOP 1
id ,
Amount ,
TypeId ,
GameProviderId ,
UserId ,
[Checksum] ,
[Date] ,
ExternalKey
FROM ActiveTransactions WITH ( UPDLOCK )
WHERE @UserId = UserId
ORDER BY Id DESC
现在是这样的。当我查看此 table 中的一些条目时,似乎有多个(同时请求的)条目 select 为相同的 @UserId
编辑了相同的条目。确切地说,有 5 个新条目(当时要求,因为它们具有完全相同的 [Date]
值,这是在代码端计算的)所有 selected 相同的条目然后重新计算一些东西(所有 5 个都计算相同的东西)并同时插入 5 个新行,而不是一个一个地插入(这应该是由 SELECT 查询末尾的 WITH(UPDLOCK)
语句引起的,我相信)。
然后我尝试做这样的事情,我打开了三个新查询 windows,我在一个 window 中用 BEGIN TRAN
命令开始了一个事务,然后在上面执行了 SELECT 语句,在其他两个 windows 中,我做了同样的事情,当我提交第一个语句时,第二个查询立即获取了它,在提交第二个语句之后,第三个查询获取了它。 (一切都按预期工作),在查询末尾添加 WITH INDEX(INDEX_NAME)
(UPDLOCK
仍然存在)后,事情开始变得奇怪。对于第一个 select,我指定了 WITH INDEX(PK_ActiveTransactions)
(主键),对于另外两个,我指定了 WITH INDEX(IX_ActiveTransactions_UserIdAmount)
。在 运行 所有 3 个命令之后,加上与第一个命令相同的 table 中的 INSERT
,(第二个和第三个仍在等待第一个完成)但是当我提交时第一个命令,第二个获取旧条目,第三个同时获取新条目。我认为这种行为可能导致了上面解释的错误,但这怎么可能呢?
服务器会不会SQL服务器同时对同一个查询使用两个不同的执行计划(因此使用不同的索引)?这个 table 在一天结束时达到 10-15 百万个条目,但每天早上大约早上 6 点执行作业,这使得 table 只有 1-2 百万行。这会导致 SQL 服务器意外切换索引吗?但无论如何,我认为这是一个系列问题,意味着即使在提交之后,索引中也可能不包含提交的数据。
上面的问题只发生过几次,我能确定它们发生了两次
您需要检查在您的 table AND 索引上获取了哪些锁(参见下面的 link)。 SQL 服务器能够对索引和数据分别进行锁定。默认情况下它不会锁定所有索引。
注:以下为猜测。
查询 #1 从未获取 IX_ActiveTransactions_UserIdAmount 上的锁,因此查询 #2 能够搜索索引并在其上获取锁,然后等待行数据锁被释放以完成其操作。一旦这个锁被释放,查询 #2 就会抓住它并持有它,同时执行你的其他代码。
同时,查询 #3 仍在等待数据和索引锁。一旦查询 #2 完成并释放所有锁,查询 #3 才能使用索引进行搜索,从而搜索最新数据。
总结:
查询 #1 和查询 #2 都能够并行搜索 table 和 return 同一行。查询 #2 必须等待查询 #1 完成才能获得更新锁。由于查询 #1 实际上并没有修改最后一行,而是插入了一个新行,因此对于查询 #2 而言,索引没有更改。
请参阅 https://www.mssqltips.com/sqlservertip/1485/using-sql-server-indexes-to-bypass-locks/ 以了解有关您的问题的反面的讨论。
其他评论:
我认为为特定用户 ID 锁定 "Users" table(如果存在)而不是依赖索引锁定正常工作会更可靠并且可能会提供更好的性能。