" T-SQL查询" book(http://www.amazon.com/Inside-Microsoft-Querying-Developer-Reference/dp/0735626030)有一个有趣的例子,其中,在聚簇索引键列更新期间查询默认事务隔离级别下的表,您可能会错过一行或读取一行两次。它看起来是可以接受的,因为无论如何更新表/实体密钥都不是一个好主意。但是,我更新了此示例,以便在更新非聚集索引键列值时发生相同的情况。
以下是表格结构:
SET NOCOUNT ON;
USE master;
IF DB_ID('TestIndexColUpdate') IS NULL CREATE DATABASE TestIndexColUpdate;
GO
USE TestIndexColUpdate;
GO
IF OBJECT_ID('dbo.Employees', 'U') IS NOT NULL DROP TABLE dbo.Employees;
CREATE TABLE dbo.Employees
(
empid CHAR(900) NOT NULL, -- this column should be big enough, so that 9 rows fit on 2 index pages
salary MONEY NOT NULL,
filler CHAR(1) NOT NULL DEFAULT('a')
);
CREATE INDEX idx_salary ON dbo.Employees(salary) include (empid); -- include empid into index, so that test query reads from it
ALTER TABLE dbo.Employees ADD CONSTRAINT PK_Employees PRIMARY KEY NONCLUSTERED(empid);
INSERT INTO dbo.Employees(empid, salary) VALUES
('A', 1500.00),('B', 2000.00),('C', 3000.00),('D', 4000.00),
('E', 5000.00),('F', 6000.00),('G', 7000.00),('H', 8000.00),
('I', 9000.00);
这是第一次连接时需要完成的操作(每次更新时,行将在2个索引页之间跳转):
SET NOCOUNT ON;
USE TestIndexColUpdate;
WHILE 1=1
BEGIN
UPDATE dbo.Employees SET salary = 10800.00 - salary WHERE empid = 'I'; -- on each update, "I" employee jumps between 2 pages
END
这是在第二个连接中需要做的事情:
SET NOCOUNT ON;
USE TestIndexColUpdate;
DECLARE @c INT
WHILE 1 = 1
BEGIN
SELECT salary, empid FROM dbo.Employees
if @@ROWCOUNT <> 9 BREAK;
END
通常,此查询应返回我们在第一个代码示例中插入的9条记录。但是,很快,我看到有8条记录被退回。该查询从&#34; idx_salary&#34;中读取所有数据。 index,由先前的示例代码更新。 这似乎是对SQL Server数据一致性的态度相当宽松。当从索引读取数据时,我期望一些锁定协调,而其关键列正在更新。
我是否正确解释了这种行为?这是否意味着,即使是非聚集索引键也不应该更新?
更新 要解决此问题,您只需启用&#34;快照&#34;在db(READ_COMMITTED_SNAPSHOT ON)上。没有更多的死锁或丢失行。我在这里总结了所有这些:http://blog.konstantins.net/2015/01/missing-rows-after-updating-sql-server.html
更新2: 这似乎是同样的问题,就像这篇好文章:http://blog.codinghorror.com/deadlocked/
答案 0 :(得分:3)
我能正确理解这种行为吗?
是
这是否意味着,即使是非聚集索引键也不应该更新?
没有。您应该使用适当的隔离级别或使应用程序容忍READ COMMITTED
允许的不一致。
此缺少行的问题不仅限于聚簇索引。它是由在b树中移动一行引起的。聚簇和非聚簇索引实现为b树,它们之间只有很小的物理差异。
所以你看到了完全相同的物理现象。每次查询从b树中读取一系列行时,它都适用。该范围的内容可以四处移动。
使用隔离级别为您提供所需的保证。对于只读事务,快照隔离级别通常是一种非常优雅且完全的并发解决方案。它似乎适用于您的情况。
这似乎是对SQL Server数据一致性的态度相当宽松。当从索引读取数据时,我期望一些锁定协调,而其关键列正在更新。
这是一个可以理解的要求。另一方面,您特别要求低水平的隔离。您可以一直拨到最需要的SERIALIZABLE
。 SERIALIZABLE
为您提供 - 如果是连续执行。
缺少行只是{em>一个 READ COMMITTED
允许的许多效果的特殊情况。在允许各种其他不一致的情况下专门防止它们是没有意义的。
SET NOCOUNT ON;
USE TestIndexColUpdate;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
DECLARE @c INT
WHILE 1 = 1
BEGIN
DECLARE @count INT
SELECT @count = COUNT(*) FROM dbo.Employees WITH (INDEX (idx_salary))
WHERE empid > '' AND CONVERT(NVARCHAR(MAX), empid) > '__'
AND salary > 0
if @count <> 9 BREAK;
END