UPDATE 語句行為
我對關於 SQL Server 的 UPDATE 語句的內部工作方式有疑問。我試圖了解如果同時收到或服務以下 2 個更新語句會發生什麼:
| Session 1 - Statement 1 | Session 2 - Statement 2 | -------------------------+------------------------- | update dbo.Table1 | update dbo.Table1 | set Value = 10 | set Value = 20 | where ID = 1 | where ID = 1 ▼ and Value = 0; | and Value = 0; (t)
據我了解,更新將首先選擇需要使用共享鎖更新的行,這意味著兩個更新語句都可以選擇特定行。然後它會請求一個排他鎖來更新列值。因此,結果似乎將 Value 設置為 20。
我錯過了什麼還是我的理解正確?
連接處的隔離級別設置為 READ UNCOMMITTED。
我對關於 SQL Server 的 UPDATE 語句的內部工作方式有疑問。
該語句描述了對數據庫的期望邏輯更改。執行時物理上發生的事情取決於執行計劃、數據庫狀態以及其他並發執行的語句/查詢正在做什麼。
我提到這一點是因為您的問題與實現細節非常相關。
據我了解,更新將首先選擇需要使用共享鎖更新的行,這意味著兩個更新語句都可以選擇特定行。
這通常不會發生。
在大多數隔離級別(包括
READ UNCOMMITTED
問題中指定的)下,SQL Server 將U
在定位要更新的行時使用更新 () 鎖。這是一個添加的實現細節,以提供一些針對常見形式的轉換死鎖的保護(它並不總是足夠的,但它確實有幫助)。注意:SQL Server 可以選擇任何方便的訪問方法來定位要更新的行。例如,它可能選擇使用非聚集索引。更新最終仍會更新基表和任何適用的二級索引,但操作順序可能會有所不同。
這意味著
U
鎖不一定像人們期望的那樣序列化。例如,問題中的一條更新語句可能選擇通過二級索引定位記錄,在這種情況下,該索引中的條目已U
應用鎖定。沒有什麼可以阻止第二個更新語句選擇使用基表(堆或集群)掃描來定位記錄的計劃。在這種情況下,第二次更新可能會在基表中的行上獲取鎖,而第一次更新會在連結到同一基表行的二級索引中的行上
U
持有鎖。U
在另一種情況下,SQL Server 可能能夠選擇單個運算符更新計劃(例如,如果聚集索引存在於 上
(ID, Value)
)。在這種情況下,X
會立即在聚集索引上獲取排他鎖 - 沒有先前的U
鎖。然後它會請求一個排他鎖來更新列值。
數據更改操作總是
X
在進行更改之前鎖定。這個鎖被持有到事務結束。它可能涉及也可能不涉及將目前持有的U
鎖轉換為X
.例如,如果要更新的行是使用基表定位的,則 a
U
將被保存並轉換為X
基表更新運算符。如果該行是使用二級索引定位的,則保留在該索引上,並在基表上獲取U
一個新鎖。X
兩者都U
同時X
舉行,在不同的資源上。因此,結果似乎將 Value 設置為 20。
根據兩個更新語句中的每一個選擇的時間和執行計劃,有許多可能的結果。僅選擇一些更有趣的:
場景一:
- 會話 1
U
在讀取時獲得對基表行的鎖定。- 會話 2 阻塞等待
U
在基表中的同一行上獲取。- 會話 1 將 Value 設置為 10 並送出。
- 會話 2 獲得了它的
U
鎖並且發現無事可做,因為 Value != 0 現在。結果:值設置為 10。
場景二:
- 會話 2
U
在讀取時獲得對基表行的鎖定。- 會話 1 阻塞等待
U
在基表中的同一行上獲取。- 會話 2 將 Value 設置為 20 並送出。
- 會話 1 獲得了它的
U
鎖並且發現無事可做,因為 Value != 0 現在。結果:值設置為 20。
場景 3:
- 會話 1 獲得
U
二級索引上的鎖(定位要更新的行)。- 會話 2 獲得
U
對基表的鎖定(定位要更新的行)。- 會話 1 需要獲取
X
基表行來執行更新,但被會話 2 持有的鎖阻塞。U
- 會話 2 將其
U
對基表的鎖定轉換為X
並將 Value 設置為 20。- 會話 2 現在需要維護二級索引(假設索引包含 Value 列)。
- 會話 2 需要獲取
X
二級索引行來更新它,但被會話 1 持有的鎖阻塞。U
- 僵局。其中一個會話因錯誤而回滾;另一個成功完成。
結果:不確定。值可能最終為 10 或 20。
這些只是一些可能性。試圖預測詳細的引擎鎖定行為通常是一個錯誤(在我看來)。而是專注於為您要進行的更改編寫正確的邏輯規範,並使用提供所需保證的隔離級別。
關於問題的編寫方式,我認為您可能真的在問“失去的更新”。如果是這種情況,請在提出更具體的新問題之前查看現有的問答,如下所示: