更新衝突不清楚
我有兩個問題:
1.為什麼我在這種情況下會出現更新衝突而不是阻塞:
-- prepare drop database if exists [TestSI]; go create database [TestSI]; go alter database [TestSI] set READ_COMMITTED_SNAPSHOT ON; alter database [TestSI] set ALLOW_SNAPSHOT_ISOLATION ON; go use [TestSI]; go drop table if exists dbo.call_test; create table dbo.call_test ( Id bigint CONSTRAINT [PK_Call] PRIMARY KEY CLUSTERED ( [Id] ASC ), additional int, incl int ); create index ix_Call on dbo.call_test ( additional ) include( incl ); insert into dbo.call_test select 1, 2, 3; go
第一屆:
use [TestSI]; go set transaction isolation level snapshot begin tran UPDATE dbo.call_test SET additional = 22 WHERE [Id] = 1
第二次會議:
use [TestSI]; go set transaction isolation level snapshot UPDATE dbo.call_test SET additional = 222 WHERE [Id] = 1
在第二次會議中,我立即得到:
消息 3960,級別 16,狀態 3,第 3 行快照隔離事務因更新衝突而中止。您不能使用快照隔離直接或間接訪問數據庫“TestSI”中的表“dbo.call_test”來更新、刪除或插入已被另一個事務修改或刪除的行。重試事務或更改更新/刪除語句的隔離級別。
如果我更新包含列incl而不是非聚集索引鍵,我也會有這種行為。
在這種情況下,非聚集索引對更新衝突有什麼影響?為什麼在這種情況下不使用鎖?
2.第二個理論問題:
SQL Server 如何處理包含列更新?
我的意思是當我們更新這個值時,SQL Server 如何更新所有具有包含列的非聚集索引?我在查詢計劃中看不到任何相關內容。
select @@version
Microsoft SQL Server 2016 (SP2) (KB4052908) - 13.0.5026.0 (X64) 2018 年 3 月 18 日 09:11:49 版權所有 (c) Microsoft Corporation Developer Edition (64-bit) on Windows 10 Pro 10.0 (Build 18363:) (Hypervisor) )
我在 SQL Server 2019 上檢查了這個範例,該伺服器上的行為與我預期的一樣:第二個會話被鎖定。這是一個錯誤還是我做錯了什麼?
為什麼在這種情況下我會遇到更新衝突而不是阻塞
這是產品缺陷,已在 SQL Server 2019 中修復。
當快照事務嘗試修改已由在快照事務開始後送出的另一個事務修改的行時,會發生快照寫入衝突。
您的範例中行為不正確的原因有些深奧。更新計劃使用稱為Rowset Sharing的東西。這意味著聚集索引查找和聚集索引更新共享一個公共行集。
這是一種優化,因此聚集索引更新不需要通過正常的查找操作來定位要更新的行。公用行集已由Clustered Index Seek正確定位。更新運算符對行集中的“目前行”執行其工作。
這會導致錯誤消息,因為查找所看到的行的版本(未送出更改之前的行)與更新運算符共享。更新發現它嘗試更新的行已更改,並得出(錯誤地)發生更新衝突的結論。
可以通過多種方式獲得正確的行為。重寫更新以便無法*共享行集的一種方法是強制搜尋使用不同的索引。*使用不同的訪問方法,沒有通用的行集可以共享:
UPDATE CT SET CT.additional = 222 FROM dbo.call_test AS CT WITH (INDEX(ix_Call)) WHERE CT.Id = 1;
更直接的方法是使用未記錄且不受支持的跟踪標誌來禁用行集共享優化(這僅用於展示目的,請勿在真實數據庫上使用它):
UPDATE dbo.call_test SET additional = 222 WHERE [Id] = 1 OPTION (QUERYTRACEON 8746);
該計劃看起來與原始計劃相同(預設情況下不公開行集共享屬性),但它會正確阻止而不是引發更新衝突錯誤。
您還可以通過強制執行寬(每個索引)更新計劃來避免錯誤(並為Clustered Index Update保留行集共享):
UPDATE dbo.call_test SET additional = 222 WHERE [Id] = 1 OPTION (QUERYTRACEON 8790);
遇到該錯誤需要行集共享和基表更新,該更新還維護二級索引(窄或每行更新)。
如果此行為導致您出現實際問題,您應該向 Microsoft 提出支持案例。
喬希正確回答了你的第二個問題。我將補充一點,您可以在 SSMS 中的聚群索引更新運算符上看到非聚群索引維護——您需要查看“屬性”視窗並展開“對象”節點: