Bài trước trong loạt bài này đã chỉ ra cách một câu lệnh T-SQL chạy trong phân lập ảnh chụp nhanh được cam kết đã đọc ( RCSI ) thường thấy một dạng xem ảnh chụp nhanh về trạng thái đã cam kết của cơ sở dữ liệu như khi câu lệnh bắt đầu thực thi. Đó là mô tả tốt về cách mọi thứ hoạt động đối với các câu lệnh đọc dữ liệu, nhưng có sự khác biệt quan trọng cho các câu lệnh chạy trong RCSI có sửa đổi các hàng hiện có .
Tôi nhấn mạnh đến việc sửa đổi các hàng hiện có ở trên, vì những cân nhắc sau chỉ áp dụng cho UPDATE
và DELETE
hoạt động (và các hành động tương ứng của MERGE
tuyên bố). Để rõ ràng, INSERT
tuyên bố cụ thể bị loại trừ từ hành vi mà tôi sắp mô tả vì các phần chèn không sửa đổi hiện có dữ liệu.
Cập nhật phiên bản khóa và hàng
Sự khác biệt đầu tiên là cập nhật và xóa các câu lệnh không đọc phiên bản hàng dưới RCSI khi tìm kiếm các hàng nguồn để sửa đổi. Cập nhật và xóa các câu lệnh trong RCSI thay vào đó nhận được khóa cập nhật khi tìm kiếm các hàng đủ điều kiện. Sử dụng khóa cập nhật đảm bảo rằng hoạt động tìm kiếm tìm thấy các hàng để sửa đổi bằng cách sử dụng dữ liệu được cam kết gần đây nhất .
Nếu không có khóa cập nhật, tìm kiếm sẽ dựa trên phiên bản có thể đã lỗi thời của tập dữ liệu (dữ liệu đã cam kết như khi bắt đầu câu lệnh sửa đổi dữ liệu). Điều này có thể nhắc bạn nhớ đến ví dụ về trình kích hoạt mà chúng ta đã thấy lần trước, trong đó READCOMMITTEDLOCK
gợi ý đã được sử dụng để hoàn nguyên từ RCSI sang triển khai khóa của cách ly được cam kết đọc. Gợi ý đó được yêu cầu trong ví dụ đó để tránh dựa trên một hành động quan trọng đối với thông tin lỗi thời. Cùng một kiểu lập luận đang được sử dụng ở đây. Một điểm khác biệt là READCOMMITTEDLOCK
gợi ý có được các khóa chia sẻ thay vì khóa cập nhật. Ngoài ra, SQL Server tự động có được các khóa cập nhật để bảo vệ các sửa đổi dữ liệu theo RCSI mà không yêu cầu chúng tôi thêm một gợi ý rõ ràng.
Việc thực hiện khóa cập nhật cũng đảm bảo rằng câu lệnh cập nhật hoặc xóa sẽ chặn nếu nó gặp phải một khóa không tương thích, chẳng hạn như một khóa độc quyền bảo vệ việc sửa đổi dữ liệu trên chuyến bay được thực hiện bởi một giao dịch đồng thời khác.
Một điều phức tạp nữa là hành vi đã sửa đổi chỉ áp dụng vào bảng là mục tiêu của hoạt động cập nhật hoặc xóa. Các bảng khác trong giống nhau xóa hoặc cập nhật tuyên bố, bao gồm cả tài liệu tham khảo bổ sung vào bảng mục tiêu, tiếp tục sử dụng các phiên bản hàng .
Có lẽ cần phải có một số ví dụ để làm cho những hành vi khó hiểu này rõ ràng hơn một chút…
Thiết lập thử nghiệm
Tập lệnh sau đảm bảo tất cả chúng ta đều được thiết lập để sử dụng RCSI, tạo một bảng đơn giản và thêm hai hàng mẫu vào đó:
ALTER DATABASE Sandpit SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE; GO SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO CREATE TABLE dbo.Test ( RowID integer PRIMARY KEY, Data integer NOT NULL ); GO INSERT dbo.Test (RowID, Data) VALUES (1, 1234), (2, 2345);
Bước tiếp theo cần phải chạy trong một phiên riêng biệt . Nó bắt đầu một giao dịch và xóa cả hai hàng khỏi bảng thử nghiệm (có vẻ kỳ lạ, nhưng điều này sẽ sớm có ý nghĩa):
BEGIN TRANSACTION; DELETE dbo.Test WHERE RowID IN (1, 2);
Lưu ý rằng giao dịch được cố tình bỏ ngỏ . Điều này duy trì các khóa độc quyền trên cả hai hàng đang bị xóa (cùng với các khóa dành riêng cho mục đích thông thường trên trang chứa và chính bảng) vì truy vấn dưới đây có thể được sử dụng để hiển thị:
SELECT resource_type, resource_description, resource_associated_entity_id, request_mode, request_status FROM sys.dm_tran_locks WHERE request_session_id = @@SPID;
Kiểm tra lựa chọn
Chuyển về phiên ban đầu , điều đầu tiên tôi muốn hiển thị là các câu lệnh chọn thông thường sử dụng RCSI vẫn thấy hai hàng bị xóa. Truy vấn chọn bên dưới sử dụng các phiên bản hàng để trả về dữ liệu được cam kết mới nhất tại thời điểm bắt đầu câu lệnh:
SELECT * FROM dbo.Test;
Trong trường hợp điều đó có vẻ đáng ngạc nhiên, hãy nhớ rằng hiển thị các hàng như đã bị xóa có nghĩa là hiển thị chế độ xem dữ liệu không được cam kết, không được phép ở chế độ cô lập được cam kết đọc.
Kiểm tra Xóa
Bất chấp sự thành công của thử nghiệm chọn lọc, một nỗ lực để xóa những hàng tương tự từ phiên hiện tại sẽ bị chặn. Bạn có thể tưởng tượng việc chặn này xảy ra khi hoạt động cố gắng giành được độc quyền khóa, nhưng đó không phải là trường hợp.
Xóa không sử dụng lập phiên bản hàng để xác định vị trí các hàng cần xóa; nó cố gắng lấy các khóa cập nhật thay thế. Các khóa cập nhật không tương thích với các khóa hàng độc quyền được giữ bởi phiên có giao dịch đang mở, do đó, các khối truy vấn:
DELETE dbo.Test WHERE RowID IN (1, 2);
Kế hoạch truy vấn ước tính cho câu lệnh này cho thấy rằng các hàng sẽ bị xóa được xác định bởi một hoạt động tìm kiếm thông thường trước khi một toán tử riêng biệt thực hiện xóa thực sự:
Chúng ta có thể thấy các khóa được giữ ở giai đoạn này bằng cách chạy cùng một truy vấn khóa như trước (từ một phiên khác) nhớ thay đổi tham chiếu SPID thành tham chiếu được sử dụng bởi truy vấn bị chặn. Kết quả như sau:
Truy vấn xóa của chúng tôi bị chặn tại toán tử Clustered Index Seek, toán tử này đang chờ để có được khóa cập nhật để đọc dữ liệu. Điều này cho thấy rằng việc định vị các hàng cần xóa trong RCSI có được các khóa cập nhật hơn là đọc dữ liệu có phiên bản cũ. Nó cũng cho thấy rằng việc chặn không phải do phần xóa của hoạt động đang chờ để có được một khóa độc quyền.
Kiểm tra cập nhật
Hủy truy vấn bị chặn và thay vào đó hãy thử cập nhật sau:
UPDATE dbo.Test SET Data = Data + 1000 WHERE RowID IN (1, 2);
Kế hoạch thực thi ước tính tương tự như kế hoạch được thấy trong thử nghiệm xóa:
Phạm vi tính toán ở đó để xác định kết quả của việc thêm 1000 vào giá trị hiện tại của cột Dữ liệu trong mỗi hàng, được đọc bởi Tìm kiếm chỉ mục theo cụm. Câu lệnh này cũng sẽ chặn khi được thực thi, do khóa cập nhật được yêu cầu bởi thao tác đọc. Ảnh chụp màn hình bên dưới cho thấy các ổ khóa được giữ khi truy vấn chặn:
Như trước đây, truy vấn bị chặn khi tìm kiếm, chờ khóa độc quyền không tương thích được phát hành để có thể nhận được khóa cập nhật.
Kiểm tra Chèn
Thử nghiệm tiếp theo có một câu lệnh chèn một hàng mới vào bảng thử nghiệm của chúng tôi, sử dụng giá trị cột Dữ liệu từ hàng hiện có với ID 1 trong bảng. Xin lưu ý rằng hàng này vẫn bị khóa riêng theo phiên với giao dịch đang mở:
INSERT dbo.Test (RowID, Data) SELECT 3, Data FROM dbo.Test WHERE RowID = 1;
Kế hoạch thực hiện một lần nữa tương tự như các thử nghiệm trước:
Lần này, truy vấn không bị chặn . Điều này cho thấy rằng khóa cập nhật không được nhận khi đọc dữ liệu cho phần chèn. Thay vào đó, truy vấn này đã sử dụng lập phiên bản hàng để nhận giá trị cột Dữ liệu cho hàng mới được chèn. Không có được các khóa cập nhật vì câu lệnh này không tìm thấy bất kỳ hàng nào để sửa đổi , nó chỉ đọc dữ liệu để sử dụng trong phần chèn.
Chúng ta có thể thấy hàng mới này trong bảng bằng cách sử dụng truy vấn kiểm tra chọn từ trước:
Lưu ý rằng chúng tôi là có thể cập nhật và xóa hàng mới (sẽ yêu cầu khóa cập nhật) vì không có khóa độc quyền xung đột. Phiên có giao dịch đang mở chỉ có các khóa riêng trên hàng 1 và 2:
-- Update the new row UPDATE dbo.Test SET Data = 9999 WHERE RowID = 3; -- Show the data SELECT * FROM dbo.Test; -- Delete the new row DELETE dbo.Test WHERE RowID = 3;
Kiểm tra này xác nhận rằng chèn câu lệnh không nhận được khóa cập nhật khi đọc , bởi vì không giống như cập nhật và xóa, chúng không sửa đổi một hàng hiện có. Phần đọc của chèn câu lệnh sử dụng hành vi lập phiên bản hàng RCSI bình thường.
Kiểm tra nhiều tham chiếu
Tôi đã đề cập trước đó rằng chỉ tham chiếu bảng duy nhất được sử dụng để xác định vị trí các hàng để sửa đổi các khóa cập nhật có được; các bảng khác trong cùng một câu lệnh cập nhật hoặc xóa vẫn đọc các phiên bản hàng. Là một trường hợp đặc biệt của nguyên tắc chung đó, một câu lệnh sửa đổi dữ liệu có nhiều tham chiếu đến cùng một bảng chỉ áp dụng khóa cập nhật trên một phiên bản được sử dụng để xác định vị trí các hàng để sửa đổi. Thử nghiệm cuối cùng này minh họa từng bước hành vi phức tạp hơn này.
Điều đầu tiên chúng ta cần là một hàng thứ ba mới cho bảng thử nghiệm của chúng ta, lần này là số 0 trong cột Dữ liệu:
INSERT dbo.Test (RowID, Data) VALUES (3, 0);
Như mong đợi, quá trình chèn này diễn ra mà không bị chặn, dẫn đến một bảng trông giống như sau:
Hãy nhớ rằng phiên thứ hai vẫn giữ độc quyền khóa trên hàng 1 và 2 tại thời điểm này. Chúng tôi có thể tự do mua các ổ khóa trên hàng 3 nếu chúng tôi cần. Truy vấn sau là truy vấn chúng tôi sẽ sử dụng để hiển thị hành vi với nhiều tham chiếu đến bảng đích:
-- Multi-reference update test UPDATE WriteRef SET Data = ReadRef.Data * 2 OUTPUT ReadRef.RowID, ReadRef.Data, INSERTED.RowID AS UpdatedRowID, INSERTED.Data AS NewDataValue FROM dbo.Test AS ReadRef JOIN dbo.Test AS WriteRef ON WriteRef.RowID = ReadRef.RowID + 2 WHERE ReadRef.RowID = 1;
Đây là một truy vấn phức tạp hơn, nhưng hoạt động của nó tương đối đơn giản. Có hai tham chiếu đến bảng thử nghiệm, một tham chiếu tôi có bí danh là ReadRef và tham chiếu còn lại là WriteRef. Ý tưởng là đọc từ hàng 1 (sử dụng phiên bản hàng) qua ReadRef và đến cập nhật hàng thứ ba (sẽ cần khóa cập nhật) bằng WriteRef.
Truy vấn chỉ định hàng 1 một cách rõ ràng trong mệnh đề where cho tham chiếu bảng đọc. Nó kết hợp với tham chiếu viết tới cùng một bảng bằng cách thêm 2 vào RowID đó (để xác định hàng 3). Câu lệnh cập nhật cũng sử dụng mệnh đề đầu ra để trả về tập kết quả hiển thị các giá trị được đọc từ bảng nguồn và các thay đổi kết quả được thực hiện đối với hàng 3.
Kế hoạch truy vấn ước tính cho câu lệnh này như sau:
Các thuộc tính của tìm kiếm có nhãn (1) cho thấy rằng tìm kiếm này nằm trên ReadRef bí danh, đọc dữ liệu từ hàng với RowID 1:
Thao tác tìm kiếm này không xác định được hàng sẽ được cập nhật, vì vậy, các khóa cập nhật không Lấy; việc đọc được thực hiện bằng cách sử dụng dữ liệu được tạo phiên bản. Việc đọc không bị chặn bởi các khóa độc quyền được giữ bởi phiên khác.
Vô hướng máy tính có nhãn (2) xác định một biểu thức có nhãn 1004 để tính toán giá trị cột Dữ liệu được cập nhật. Biểu thức 1009 tính toán ID hàng được cập nhật (1 + 2 =ID hàng 3):
Tìm kiếm thứ hai là một tham chiếu đến cùng một bảng (3). Tìm kiếm này xác định hàng sẽ được cập nhật (hàng 3) bằng cách sử dụng biểu thức 1009:
Vì tìm kiếm này định vị một hàng cần thay đổi, một khóa cập nhật được lấy thay vì sử dụng các phiên bản hàng. Không có khóa độc quyền xung đột nào trên hàng ID 3, vì vậy yêu cầu khóa được cấp ngay lập tức.
Toán tử được đánh dấu cuối cùng (4) là hoạt động cập nhật chính nó. Khóa cập nhật trên hàng 3 được nâng cấp thành độc quyền khóa tại thời điểm này, ngay trước khi sửa đổi thực sự được thực hiện. Toán tử này cũng trả về dữ liệu được chỉ định trong mệnh đề đầu ra của tuyên bố cập nhật:
Kết quả của câu lệnh cập nhật (được tạo bởi mệnh đề đầu ra) được hiển thị bên dưới:
Trạng thái cuối cùng của bảng như hình dưới đây:
Chúng tôi có thể xác nhận các khóa được thực hiện bằng cách sử dụng theo dõi Hồ sơ:
Điều này cho thấy rằng chỉ có một bản cập nhật duy nhất khóa phím hàng được mua lại. Khi hàng này đến tay nhà điều hành cập nhật, khóa được chuyển đổi thành độc quyền Khóa. Khi kết thúc câu lệnh, khóa sẽ được giải phóng.
Bạn có thể thấy từ đầu ra theo dõi rằng giá trị băm khóa cho hàng bị khóa cập nhật là (98ec012aa510) trong cơ sở dữ liệu thử nghiệm của tôi. Truy vấn sau đây cho thấy rằng hàm băm khóa này thực sự được liên kết với RowID 3 trong chỉ mục được phân nhóm:
SELECT RowID, %%LockRes%% FROM dbo.Test;
Lưu ý rằng các khóa cập nhật được thực hiện trong các ví dụ này có thời gian tồn tại ngắn hơn so với các khóa cập nhật được thực hiện nếu chúng tôi chỉ định một UPDLOCK
gợi ý. Các khóa cập nhật nội bộ này được phát hành ở cuối câu lệnh, trong khi UPDLOCK
khóa được giữ ở cuối giao dịch.
Phần này kết thúc phần trình bày về các trường hợp RCSI có được các khóa cập nhật để đọc dữ liệu đã cam kết hiện tại thay vì sử dụng lập phiên bản hàng.
Khóa chia sẻ và khóa phạm vi khóa theo RCSI
Có một số trường hợp khác mà công cụ cơ sở dữ liệu vẫn có thể nhận được các khóa theo RCSI. Tất cả các tình huống này đều liên quan đến nhu cầu duy trì tính đúng đắn sẽ bị đe dọa khi dựa vào dữ liệu có phiên bản đã lỗi thời.
Các khóa dùng chung được thực hiện để xác thực khóa nước ngoài
Đối với hai bảng trong mối quan hệ khóa ngoài đơn giản, công cụ cơ sở dữ liệu cần thực hiện các bước để đảm bảo các ràng buộc không bị vi phạm bằng cách dựa vào các lần đọc có phiên bản cũ. Việc triển khai hiện tại thực hiện điều này bằng cách chuyển sang khóa đã cam kết đã đọc khi truy cập dữ liệu như một phần của kiểm tra khóa ngoại tự động.
Việc sử dụng các khóa dùng chung đảm bảo việc kiểm tra tính toàn vẹn sẽ đọc dữ liệu được cam kết mới nhất (không phải phiên bản cũ) hoặc các khối do sửa đổi đồng thời trên chuyến bay. Việc chuyển sang khóa đã cam kết đọc chỉ áp dụng cho phương pháp truy cập cụ thể được sử dụng để kiểm tra dữ liệu khóa ngoại; quyền truy cập dữ liệu khác trong cùng một câu lệnh tiếp tục sử dụng các phiên bản hàng.
Hành vi này chỉ áp dụng cho các câu lệnh thay đổi dữ liệu, trong đó thay đổi ảnh hưởng trực tiếp đến mối quan hệ khóa ngoài. Đối với các sửa đổi đối với bảng (cha) được tham chiếu, điều này có nghĩa là các cập nhật ảnh hưởng đến giá trị được tham chiếu (trừ khi nó được đặt thành NULL
) và tất cả các thao tác xóa. Đối với bảng tham chiếu (con), điều này có nghĩa là tất cả các lần chèn và cập nhật (một lần nữa, trừ khi tham chiếu khóa là NULL
). Các cân nhắc tương tự cũng áp dụng cho các hiệu ứng thành phần của MERGE
.
Dưới đây là một ví dụ về kế hoạch thực thi hiển thị tra cứu khóa ngoại có khóa dùng chung:
Có thể tuần tự hóa để xếp tầng các khóa ngoại
Trong trường hợp mối quan hệ khóa ngoài có một hành động xếp tầng, thì tính đúng đắn đòi hỏi một sự leo thang cục bộ đến ngữ nghĩa cô lập có thể tuần tự hóa. Điều này có nghĩa là bạn sẽ thấy các khóa phạm vi khóa được thực hiện cho một hành động tham chiếu theo tầng. Như trường hợp của các khóa cập nhật đã thấy trước đây, các khóa phạm vi khóa này được xác định phạm vi cho bản sao kê, không phải giao dịch. Một kế hoạch thực thi ví dụ cho thấy nơi các khóa có thể nối tiếp hóa bên trong được thực hiện theo RCSI được hiển thị bên dưới:
Các tình huống khác
Có nhiều trường hợp cụ thể khác trong đó động cơ tự động kéo dài tuổi thọ của ổ khóa hoặc nâng cấp cục bộ lên mức cách ly để đảm bảo tính đúng đắn. Chúng bao gồm ngữ nghĩa có thể tuần tự hóa được sử dụng khi duy trì chế độ xem được lập chỉ mục liên quan hoặc khi duy trì chỉ mục có IGNORE_DUP_KEY
bộ tùy chọn.
Thông báo rút ra là RCSI giảm số lượng khóa, nhưng không phải lúc nào cũng loại bỏ hoàn toàn.
Lần tới
Bài tiếp theo trong loạt bài này xem xét mức độ cô lập của ảnh chụp nhanh.
[Xem chỉ mục cho toàn bộ chuỗi]