[Phần 1 | Phần 2 | Phần 3 | Phần 4]
Trong phần đầu tiên của loạt bài này, chúng ta đã biết cách Sự cố Halloween áp dụng cho UPDATE
truy vấn. Tóm lại ngắn gọn, vấn đề là một chỉ mục được sử dụng để định vị các bản ghi để cập nhật đã có các khóa của nó được sửa đổi bởi chính thao tác cập nhật (một lý do chính đáng khác để sử dụng các cột được bao gồm trong một chỉ mục thay vì mở rộng các khóa). Trình tối ưu hóa truy vấn đã giới thiệu một toán tử Eager Table Spool để tách các bên đọc và ghi của kế hoạch thực thi để tránh sự cố. Trong bài đăng này, chúng ta sẽ xem cùng một vấn đề cơ bản có thể ảnh hưởng đến INSERT
như thế nào và DELETE
tuyên bố.
Chèn câu lệnh
Bây giờ chúng ta đã biết một chút về các điều kiện yêu cầu Bảo vệ Halloween, khá dễ dàng để tạo INSERT
ví dụ liên quan đến việc đọc và ghi vào các khóa của cùng một cấu trúc chỉ mục. Ví dụ đơn giản nhất là sao chép các hàng trong bảng (trong đó việc thêm các hàng mới chắc chắn sẽ sửa đổi các khóa của chỉ mục được nhóm):
CREATE TABLE dbo.Demo ( SomeKey integer NOT NULL, CONSTRAINT PK_Demo PRIMARY KEY (SomeKey) ); INSERT dbo.Demo SELECT SomeKey FROM dbo.Demo;
Vấn đề là các hàng mới được chèn có thể gặp phải bởi phía đọc của kế hoạch thực thi, có khả năng dẫn đến một vòng lặp thêm các hàng mãi mãi (hoặc ít nhất là cho đến khi đạt đến một số giới hạn tài nguyên). Trình tối ưu hóa truy vấn nhận ra rủi ro này và thêm Bộ đệm bảng Eager để cung cấp phân tách giai đoạn cần thiết :
Một ví dụ thực tế hơn
Có thể bạn không thường xuyên viết các truy vấn để sao chép mọi hàng trong bảng, nhưng bạn có thể viết các truy vấn trong đó bảng đích cho một INSERT
cũng xuất hiện ở đâu đó trong SELECT
mệnh đề. Một ví dụ là thêm hàng từ bảng dàn chưa tồn tại trong đích:
CREATE TABLE dbo.Staging ( SomeKey integer NOT NULL ); -- Sample data INSERT dbo.Staging (SomeKey) VALUES (1234), (1234); -- Test query INSERT dbo.Demo SELECT s.SomeKey FROM dbo.Staging AS s WHERE NOT EXISTS ( SELECT 1 FROM dbo.Demo AS d WHERE d.SomeKey = s.SomeKey );
Kế hoạch thực hiện là:
Vấn đề trong trường hợp này khác biệt một cách tinh tế, mặc dù vẫn là một ví dụ về cùng một vấn đề cốt lõi. Không có giá trị ‘1234’ trong bảng Demo mục tiêu, nhưng bảng Staging chứa hai mục nhập như vậy. Nếu không có phân tách pha, giá trị ‘1234’ đầu tiên gặp phải sẽ được chèn thành công, nhưng lần kiểm tra thứ hai sẽ thấy rằng giá trị ‘1234’ hiện đã tồn tại và sẽ không cố gắng chèn lại. Toàn bộ câu lệnh sẽ hoàn tất thành công.
Điều này có thể tạo ra một kết quả mong muốn trong trường hợp cụ thể này (và thậm chí có thể có vẻ đúng về mặt trực giác) nhưng nó không phải là cách triển khai chính xác. Tiêu chuẩn SQL yêu cầu rằng các truy vấn sửa đổi dữ liệu phải thực thi như thể ba giai đoạn đọc, ghi và kiểm tra các ràng buộc xảy ra hoàn toàn riêng biệt (xem phần một).
Tìm kiếm tất cả các hàng để chèn dưới dạng một thao tác, chúng ta nên chọn cả hai hàng ‘1234’ từ bảng Staging, vì giá trị này chưa tồn tại trong mục tiêu. Do đó, kế hoạch thực thi nên cố gắng chèn cả hai '1234' hàng từ bảng Staging, dẫn đến vi phạm khóa chính:
Msg 2627, Cấp độ 14, Trạng thái 1, Dòng 1Vi phạm ràng buộc KHÓA CHÍNH 'PK_Demo'.
Không thể chèn khóa trùng lặp vào đối tượng 'dbo.Demo'.
Giá trị khóa trùng lặp là ( 1234).
Tuyên bố đã bị chấm dứt.
Sự phân chia giai đoạn được cung cấp bởi Table Spool đảm bảo rằng tất cả các kiểm tra về sự tồn tại được hoàn thành trước khi bất kỳ thay đổi nào được thực hiện đối với bảng mục tiêu. Nếu bạn chạy truy vấn trong SQL Server với dữ liệu mẫu ở trên, bạn sẽ nhận được thông báo lỗi (đúng).
Bảo vệ Halloween là bắt buộc đối với các câu lệnh INSERT trong đó bảng đích cũng được tham chiếu trong mệnh đề SELECT.
Xóa các câu lệnh
Chúng tôi có thể mong đợi Sự cố Halloween không áp dụng cho DELETE
vì nó sẽ không thực sự quan trọng nếu chúng ta cố gắng xóa một hàng nhiều lần. Chúng tôi có thể sửa đổi ví dụ về bảng dàn của mình để loại bỏ hàng từ bảng Demo không tồn tại trong Staging:
TRUNCATE TABLE dbo.Demo; TRUNCATE TABLE dbo.Staging; INSERT dbo.Demo (SomeKey) VALUES (1234); DELETE dbo.Demo WHERE NOT EXISTS ( SELECT 1 FROM dbo.Staging AS s WHERE s.SomeKey = dbo.Demo.SomeKey );
Thử nghiệm này dường như xác thực trực giác của chúng tôi vì không có Table Spool trong kế hoạch thực thi:
Loại DELETE
này không yêu cầu phân tách theo pha vì mỗi hàng có một mã định danh duy nhất (một RID nếu bảng là một đống, (các) khóa chỉ mục được phân cụm và có thể là một mã thống nhất nếu không). Công cụ định vị hàng duy nhất này là một khóa ổn định - không có cơ chế nào mà nó có thể thay đổi trong quá trình thực hiện kế hoạch này, vì vậy Vấn đề Halloween không phát sinh.
XÓA Bảo vệ Halloween
Tuy nhiên, có ít nhất một trường hợp DELETE
yêu cầu bảo vệ Halloween:khi kế hoạch tham chiếu đến một hàng trong bảng khác với hàng đang bị xóa. Điều này yêu cầu tự tham gia, thường thấy khi các mối quan hệ phân cấp được mô hình hóa. Một ví dụ đơn giản được hiển thị bên dưới:
CREATE TABLE dbo.Test ( pk char(1) NOT NULL, ref char(1) NULL, CONSTRAINT PK_Test PRIMARY KEY (pk) ); INSERT dbo.Test (pk, ref) VALUES ('B', 'A'), ('C', 'B'), ('D', 'C');
Thực sự cần phải có một tham chiếu khóa ngoại cùng bảng được xác định ở đây, nhưng chúng ta hãy bỏ qua việc thiết kế không thành công trong giây lát - cấu trúc và dữ liệu dù sao cũng hợp lệ (và điều đáng buồn là khá phổ biến khi tìm thấy khóa ngoại bị bỏ qua trong thế giới thực). Dù sao, nhiệm vụ trước mắt là xóa bất kỳ hàng nào có ref cột trỏ đến một pk không tồn tại giá trị. DELETE
tự nhiên truy vấn phù hợp với yêu cầu này là:
DELETE dbo.Test WHERE NOT EXISTS ( SELECT 1 FROM dbo.Test AS t2 WHERE t2.pk = dbo.Test.ref );
Kế hoạch truy vấn là:
Lưu ý rằng kế hoạch này hiện có tính năng Eager Table Spool đắt tiền. Ở đây cần phải tách giai đoạn vì nếu không, kết quả có thể phụ thuộc vào thứ tự các hàng được xử lý:
Nếu công cụ thực thi bắt đầu với hàng mà pk =B, nó sẽ không tìm thấy hàng nào phù hợp ( ref =A và không có hàng nào ở đó pk =A). Nếu thực thi thì chuyển sang hàng có pk =C, nó cũng sẽ bị xóa vì chúng tôi vừa xóa hàng B được trỏ đến bởi ref của nó cột. Kết quả cuối cùng sẽ là xử lý lặp đi lặp lại theo thứ tự này sẽ xóa tất cả các hàng khỏi bảng, điều này rõ ràng là không chính xác.
Mặt khác, nếu công cụ thực thi xử lý hàng bằng pk =D trước tiên, nó sẽ tìm một hàng phù hợp ( ref =C). Giả sử quá trình thực thi tiếp tục theo chiều ngược lại pk đặt hàng, hàng duy nhất bị xóa khỏi bảng sẽ là hàng mà pk =B. Đây là kết quả chính xác (hãy nhớ truy vấn phải thực thi như thể các giai đoạn đọc, ghi và xác thực đã diễn ra tuần tự và không có sự chồng chéo).
Tách giai đoạn để xác nhận ràng buộc
Ngoài ra, chúng ta có thể xem một ví dụ khác về tách pha nếu chúng ta thêm ràng buộc khóa ngoại cùng bảng vào ví dụ trước:
DROP TABLE dbo.Test; CREATE TABLE dbo.Test ( pk char(1) NOT NULL, ref char(1) NULL, CONSTRAINT PK_Test PRIMARY KEY (pk), CONSTRAINT FK_ref_pk FOREIGN KEY (ref) REFERENCES dbo.Test (pk) ); INSERT dbo.Test (pk, ref) VALUES ('B', NULL), ('C', 'B'), ('D', 'C');
Kế hoạch thực thi cho INSERT là:
Bản thân phần chèn không yêu cầu bảo vệ Halloween vì gói không đọc từ cùng một bảng (nguồn dữ liệu là bảng ảo trong bộ nhớ được đại diện bởi toán tử Quét liên tục). Tuy nhiên, tiêu chuẩn SQL yêu cầu giai đoạn 3 (kiểm tra ràng buộc) xảy ra sau khi giai đoạn viết hoàn tất. Vì lý do này, một Bộ đệm Bảng Eager phân tách giai đoạn được thêm vào kế hoạch sau Chỉ mục chỉ mục theo cụm và ngay trước khi mỗi hàng được kiểm tra để đảm bảo ràng buộc khóa ngoại vẫn hợp lệ.
Nếu bạn bắt đầu nghĩ rằng việc dịch một truy vấn sửa đổi SQL khai báo dựa trên tập hợp thành một kế hoạch thực thi vật lý lặp đi lặp lại mạnh mẽ là một công việc khó phần phức tạp nhất của Bộ xử lý truy vấn.
Các câu lệnh DELETE yêu cầu Bảo vệ Halloween khi có sự tự tham gia của bảng đích.
Tóm tắt
Halloween Protection có thể là một tính năng đắt tiền (nhưng cần thiết) trong các kế hoạch thực thi thay đổi dữ liệu (trong đó ‘thay đổi’ bao gồm tất cả các cú pháp SQL thêm, thay đổi hoặc xóa hàng). Bảo vệ Halloween là bắt buộc đối với UPDATE
kế hoạch trong đó các khóa của cấu trúc chỉ mục chung đều được đọc và sửa đổi, cho INSERT
kế hoạch trong đó bảng mục tiêu được tham chiếu ở phía đọc của kế hoạch và cho DELETE
lập kế hoạch nơi tự tham gia trên bảng đích được thực hiện.
Phần tiếp theo của loạt bài này sẽ đề cập đến một số tối ưu hóa Vấn đề Halloween đặc biệt chỉ áp dụng cho MERGE
tuyên bố.
[Phần 1 | Phần 2 | Phần 3 | Phần 4]