承接前文並發控制概念與 SQL 觀察方法,本文詳細檢視 LINQ to SQL 如何發現並發更新衝突。
初始資料:
VideoID | Introduction | SiteID
1 | Introduction 1 | 1
LinqToSqlDemoDataContext dataContext = new LinqToSqlDemoDataContext();
dataContext.Log = Console.Out;
Video video = dataContext.Videos.Single(v => v.VideoID == 1);
video.Introduction = "1235";
dataContext.SubmitChanges();
輸出 SQL:
SELECT [t0].[VideoID], [t0].[Introduction], [t0].[SiteID]
FROM [dbo].[Video] AS [t0]
WHERE [t0].[VideoID] = @p0
UPDATE [dbo].[Video]
SET [Introduction] = @p3
WHERE ([VideoID] = @p0) AND ([Introduction] = @p1) AND ([SiteID] = @p2)
-- @p0: 1, @p1: Introduction 1, @p2: 1, @p3: 1235
UPDATE 陳述式 WHERE 條件包含 主鍵 + 所有原始欄位值。只有當「更新時」資料列狀態與「讀取時」完全一致,更新才成功。這就是樂觀並發控制 —— 直到更新時才判斷並發問題。
嚴格來說,這只是樂觀並發控制的一部分,完整還包含衝突發生後的「處理問題」過程。
特點:不關心記錄是否「被修改過」,只比較狀態是否相同 (可能記錄被修改但數值未變)。
try
{
LinqToSqlDemoDataContext dataContext = new LinqToSqlDemoDataContext();
Video video = dataContext.Videos.Single(v => v.VideoID == 1);
// 中斷點:在此處透過 SQL 修改資料庫記錄
video.Introduction = "12356";
dataContext.SubmitChanges();
}
catch (ChangeConflictException e)
{
Console.WriteLine(e.Message);
}
Console.ReadLine();
中斷點處執行 UPDATE Video SET SiteID = 10 WHERE VideoID = 1,繼續執行拋出異常:
Row not found or changed.
原因:WHERE 條件不滿足,ExecuteNonQuery 回傳 0,LINQ to SQL 偵測到記錄被修改或刪除。
LinqToSqlDemoDataContext dataContext = new LinqToSqlDemoDataContext();
dataContext.Log = Console.Out;
Video video = new Video();
video.VideoID = 1;
dataContext.Videos.Attach(video, false);
video.Introduction = "123";
dataContext.SubmitChanges();
產生 SQL:
UPDATE [dbo].[Video]
SET [Introduction] = @p0
WHERE 0 = 1
-- @p0: 123
WHERE 0 = 1 永不成立,無記錄更新,同樣拋出 ChangeConflictException。Attach 方法與第二個參數 false (表示未修改) 導致 LINQ to SQL 無法取得原始值進行並發檢查。
此種「逐欄位比對」樂觀並發控制是否顯得累贅?下文將探討更優雅的 Timestamp 機制。