1

前言:我希望在未来使用快照隔离,但现在,我想了解我的查询发生了什么。

我了解脏读(READ UNCOMMITTED、NOLOCK)不仅会忽略锁,而且由于脏读期间发生的页面拆分等情况,它们也可能会出现不一致的结果。但是,我看到每次都返回相同的错误结果,我无法将其与我读过的任何一致性缺陷联系起来。

我有一个名为 RUN 的大表,其中有数百万行。我们从不更新行,我们只插入。我有一个查询试图检索过去几天内传入的数据。此查询以 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
4

0 回答 0