前言:我希望将来能够使用快照隔离,但就目前而言,我想了解我的查询是怎么回事。
我理解脏读(READ UNCOMMITTED,NOLOCK)不仅忽略锁,而且由于在脏读期间发生页面拆分等事情,它们也会受到不一致的结果。但是,我看到每次都返回相同的不正确结果,我无法将其与我读过的任何一致性陷阱联系起来。
我有一个名为RUN的大表,其中有数百万行。我们永远不会UPDATE行,我们只是INSERT。我有一个查询试图检索过去几天内进来的数据。此查询使用READ UNCOMMITTED运行,并且它始终返回相同的错误结果。
CREATE TABLE run
{
id INT IDENTITY(1,1) NOT NULL
, someTableID INT NOT NULL
, locationID INT NOT NULL
, statusID INT NOT NULL
, value INT NULL
, date_time DATETIME NOT NULL
, CONSTRAINT runs_pk PRIMARY KEY CLUSTERED (id asc)
}
-- My queries automatically use this index, and I only get correct results when READ COMMITTED
CREATE NONCLUSTERED INDEX run_ix ON run ( someTableID, locationID, date_time) INCLUDE (statusID)
-- When I force my queries to use this index, I get correct results even when READ UNCOMMITTED
CREATE NONCLUSTERED INDEX run_uk ON run ( date_time, locationID, statusID, someTableID, id) INCLUDE (value)
我预计会有60个结果回来,日期在2013-05-31到2013-06-05:
CREATE VIEW testView AS
SELECT * FROM (
SELECT *, RANK() over (PARTITION BY someTableID, locationID ORDER BY date_time ASC) rnk
FROM run
WHERE statusID = 1 AND date_time BETWEEN '2013-05-31 00:00:00' AND '2013-06-08 00:00:00'
) a WHERE rnk = 1
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
GO
-- Always returns 15
SELECT COUNT(*) FROM testView
GO
-- Always returns 15, 2013-06-05, 2013-06-05
SELECT COUNT(*), MIN(date_time), MAX(date_time) FROM testView
GO
-- Always returns 60 rows
SELECT * FROM testView
GO
请注意,选择所有结果实际上会返回所有行,而带有聚合的查询会返回不正确的结果。
我能够确定切换到READ COMMITTED返回了正确的结果。此外,我尝试以一种导致它使用不同索引的方式重写查询,这也给了我正确的结果(我这里没有这个例子)。
-- Always returns 60
SELECT COUNT(*) FROM testView WITH (INDEX(
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
GO
-- Always returns 60
SELECT COUNT(*) FROM testView
GO
-- Always returns 60, 2013-05-31 , 2013-06-05
SELECT COUNT(*), MIN(date_time), MAX(date_time) FROM testView
GO
-- Always returns 60 rows
SELECT * FROM testView
GO
该指数的碎片率约为6%。我不确定这是否代表其正常的碎片级别,因为我们有一个每周工作重建索引超过15%左右。重新调整查询使用的索引会暂时导致查询返回正确的结果,即使在脏读中也是如此,但在一两分钟之后我会回到不正确的结果。
我注意到在重组索引之后,它的碎片化程度立即达到了约0.68%,并且几分钟后可能会接近0.8%,可能来自新数据。我不确定碎片是否导致错误结果与否,但这就是我现在要做的全部。
我们每小时只有大约500个插页。每次运行查询时都不会发生页面拆分,导致数据不正确,即使有,也不会向我解释为什么我总能得到相同的结果。
注意:我们有数百个其他脏读查询,并且已有多年。我不能说这是第一次也是唯一一次出现问题,这是我们第一次发现这个问题。
有没有人对可能发生的事情有任何见解?它可能与索引的碎片级别有关吗?
我认为问题与IAM有关,而且我的查询正在执行分配订单扫描。
值列不包含在索引中。运行此查询会导致索引扫描设置为ORDERED:TRUE,并且我得到正确的结果
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
GO
SELECT value FROM testView
date_time列包含在索引中。运行此查询会导致索引扫描设置为ORDERED:FALSE,并且我得到错误的结果
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
GO
SELECT date_time FROM testView