Lưu ý:Bài đăng này ban đầu chỉ được xuất bản trong sách điện tử của chúng tôi, Kỹ thuật hiệu suất cao cho SQL Server, Tập 2. Bạn có thể tìm hiểu về sách điện tử của chúng tôi tại đây.
Tóm tắt:Bài viết này kiểm tra một số hành vi đáng ngạc nhiên của trình kích hoạt INSTEAD OF và tiết lộ một lỗi ước tính bản số nghiêm trọng trong SQL Server 2014.
Trình kích hoạt và Phiên bản hàng
Chỉ các trình kích hoạt DML SAU KHI sử dụng lập phiên bản hàng (trong SQL Server 2005 trở đi) để cung cấp được chèn và đã xóa bảng giả bên trong một thủ tục kích hoạt. Điểm này không được trình bày rõ ràng trong nhiều tài liệu chính thức. Ở hầu hết các nơi, tài liệu chỉ nói rằng lập phiên bản hàng được sử dụng để tạo được chèn và đã xóa bảng trong trình kích hoạt không có đủ điều kiện (ví dụ bên dưới):
Sử dụng tài nguyên tạo phiên bản hàng
Tìm hiểu mức độ cách ly dựa trên tạo phiên bản hàng
Kiểm soát việc thực thi trình kích hoạt khi nhập dữ liệu hàng loạt
Có lẽ, phiên bản gốc của những mục này được viết trước khi INSTEAD OF kích hoạt được thêm vào sản phẩm và không bao giờ được cập nhật. Hoặc đó, hoặc đó là một sự giám sát đơn giản (nhưng lặp đi lặp lại).
Dù sao, cách lập phiên bản hàng hoạt động với các trình kích hoạt SAU KHI khá trực quan. Những tác nhân này kích hoạt sau các sửa đổi được đề cập đã được thực hiện, vì vậy, thật dễ dàng để xem cách duy trì các phiên bản của các hàng đã sửa đổi cho phép công cụ cơ sở dữ liệu cung cấp được chèn và đã xóa bảng giả. Đã xóa bảng giả được xây dựng từ các phiên bản của các hàng bị ảnh hưởng trước khi các sửa đổi diễn ra; đã chèn bảng giả được tạo từ các phiên bản của các hàng bị ảnh hưởng tại thời điểm bắt đầu quy trình kích hoạt.
Thay vì Kích hoạt
Trình kích hoạt INSTEAD OF khác vì loại trình kích hoạt DML này hoàn toàn thay thế hành động được kích hoạt. Đã chèn và đã xóa bảng giả hiện đại diện cho những thay đổi mà sẽ có đã được thực hiện, đã thực thi câu lệnh kích hoạt. Không thể sử dụng lập phiên bản hàng cho các trình kích hoạt này vì không có sửa đổi nào xảy ra, theo định nghĩa. Vì vậy, nếu không sử dụng các phiên bản hàng, SQL Server sẽ làm điều đó như thế nào?
Câu trả lời là SQL Server sửa đổi kế hoạch thực thi cho câu lệnh DML kích hoạt khi tồn tại một trình kích hoạt INSTEAD OF. Thay vì sửa đổi trực tiếp các bảng bị ảnh hưởng, kế hoạch thực thi ghi thông tin về những thay đổi đối với một bảng làm việc ẩn. Bảng làm việc này chứa tất cả dữ liệu cần thiết để thực hiện các thay đổi ban đầu, loại sửa đổi cần thực hiện trên mỗi hàng (xóa hoặc chèn), cũng như bất kỳ thông tin nào cần thiết trong trình kích hoạt cho mệnh đề OUTPUT.
Kế hoạch thực thi không có trình kích hoạt
Để xem tất cả điều này đang hoạt động, trước tiên chúng tôi sẽ chạy một thử nghiệm đơn giản mà không có trình kích hoạt INSTEAD OF:
CREATE TABLE Test ( RowID integer NOT NULL, Data integer NOT NULL, CONSTRAINT PK_Test_RowID PRIMARY KEY CLUSTERED (RowID) ); GO INSERT dbo.Test (RowID, Data) VALUES (1, 100), (2, 200), (3, 300); GO DELETE dbo.Test; GO DROP TABLE dbo.Test;
Kế hoạch thực hiện cho việc xóa rất đơn giản:
Mỗi hàng đủ điều kiện được chuyển trực tiếp đến toán tử Xóa chỉ mục theo cụm, toán tử này sẽ xóa hàng đó. Dễ dàng.
Kế hoạch thực thi với trình kích hoạt INSTEAD OF
Bây giờ, hãy sửa đổi thử nghiệm để bao gồm một trình kích hoạt INSTEAD OF DELETE (một trình kích hoạt chỉ thực hiện cùng một hành động xóa để đơn giản hóa):
CREATE TABLE Test ( RowID integer NOT NULL, Data integer NOT NULL, CONSTRAINT PK_Test_RowID PRIMARY KEY CLUSTERED (RowID) ); GO INSERT dbo.Test (RowID, Data) VALUES (1, 100), (2, 200), (3, 300); GO CREATE TRIGGER dbo_Test_IOD ON dbo.Test INSTEAD OF DELETE AS BEGIN SET NOCOUNT ON; DELETE FROM dbo.Test WHERE EXISTS ( SELECT * FROM Deleted WHERE Deleted.RowID = dbo.Test.RowID ); END; GO DELETE dbo.Test; GO DROP TABLE dbo.Test;
Kế hoạch thực thi cho DELETE hiện khá khác:
Toán tử Xóa chỉ mục theo cụm đã được thay thế bằng chỉ mục theo cụm Chèn . Đây là phần chèn vào bảng làm việc ẩn, được đổi tên (trong biểu diễn kế hoạch thực thi công khai) thành tên của bảng cơ sở bị ảnh hưởng bởi việc xóa. Việc đổi tên xảy ra khi kế hoạch hiển thị XML được tạo từ biểu diễn kế hoạch thực thi nội bộ, vì vậy không có cách nào được lập thành văn bản để xem bảng làm việc ẩn.
Do thay đổi này, do đó, kế hoạch dường như thực hiện một chèn vào bảng cơ sở để xóa hàng từ nó. Điều này thật khó hiểu, nhưng ít nhất nó cũng tiết lộ sự hiện diện của một trình kích hoạt INSTEAD OF. Thay thế toán tử Chèn bằng một Xóa có thể còn khó hiểu hơn. Có lẽ lý tưởng nhất sẽ là một biểu tượng đồ họa mới cho một bảng làm việc kích hoạt INSTEAD OF? Dù sao thì nó vẫn là như vậy.
Toán tử Scalar tính toán mới xác định loại hành động được thực hiện trên mỗi hàng. Mã hành động này là một số nguyên, với các ý nghĩa sau:
- 3 =XÓA
- 4 =CHÈN
- 259 =XÓA trong gói MERGE
- 260 =CHÈN trong gói MERGE
Đối với truy vấn này, hành động là một hằng số 3, có nghĩa là mọi hàng sẽ bị xóa :
Cập nhật hành động
Ngoài ra, một kế hoạch thực thi INSTEAD OF UPDATE thay thế một toán tử Cập nhật duy nhất bằng hai Chỉ mục theo cụm Chèn vào cùng một bảng làm việc ẩn - một bảng cho được chèn các hàng trong bảng giả và một hàng cho đã xóa hàng giả bảng. Một kế hoạch thực hiện ví dụ:
MERGE thực hiện CẬP NHẬT cũng tạo ra một kế hoạch thực thi với hai lần chèn vào cùng một bảng cơ sở vì những lý do tương tự:
Kế hoạch thực thi trình kích hoạt
Kế hoạch thực thi cho phần thân trình kích hoạt cũng có một số tính năng thú vị:
Điều đầu tiên cần chú ý là biểu tượng đồ họa được sử dụng cho bảng đã xóa không giống với biểu tượng được sử dụng trong các kế hoạch kích hoạt SAU:
Biểu diễn trong kế hoạch kích hoạt INSTEAD OF là một Tìm kiếm chỉ mục theo cụm. Đối tượng cơ bản là cùng một bảng làm việc nội bộ mà chúng ta đã thấy trước đó, mặc dù ở đây nó được đặt tên là đã xóa thay vì được cung cấp tên bảng cơ sở, có lẽ vì một số loại nhất quán với các trình kích hoạt SAU KHI.
Hoạt động tìm kiếm trên đã xóa bảng có thể không phải là những gì bạn mong đợi (nếu bạn đang mong đợi một tìm kiếm trên RowID):
'Tìm kiếm' này trả về tất cả các hàng từ bảng làm việc có mã hành động là 3 (xóa), làm cho nó chính xác tương đương với Quét đã xóa toán tử được thấy trong các kế hoạch kích hoạt SAU. Cùng một bảng làm việc nội bộ được sử dụng để giữ các hàng cho cả được chèn và đã xóa bảng giả trong INSTEAD OF kích hoạt. Tương đương với Quét đã chèn là mã tìm kiếm hành động 4 (có thể thực hiện được trong xóa kích hoạt, nhưng kết quả sẽ luôn trống). Không có chỉ mục nào trên bảng làm việc nội bộ ngoài chỉ mục được phân nhóm không phải duy nhất trên action cột một mình. Ngoài ra, không có thống kê nào liên quan đến chỉ mục nội bộ này.
Phân tích cho đến nay có thể khiến bạn tự hỏi nơi nối giữa các cột RowID được thực hiện. Sự so sánh này xảy ra ở toán tử Nối vòng lặp lồng nhau bên trái Bán kết nối dưới dạng một vị từ còn lại:
Bây giờ chúng tôi biết 'tìm kiếm' thực sự là một bản quét đầy đủ của đã xóa , kế hoạch thực thi được chọn bởi trình tối ưu hóa truy vấn có vẻ khá kém hiệu quả. Quy trình tổng thể của kế hoạch thực thi là mỗi hàng từ bảng Kiểm tra có khả năng được so sánh với toàn bộ tập hợp đã xóa hàng, nghe có vẻ giống như một sản phẩm của cartesian.
Cơ hội lưu là phép nối là phép nối bán phần, có nghĩa là quá trình so sánh sẽ dừng đối với một hàng Thử nghiệm nhất định ngay sau khi hàng đầu tiên bị xóa hàng thỏa mãn vị ngữ dư. Tuy nhiên, chiến lược này có vẻ là một chiến lược gây tò mò. Có lẽ kế hoạch thực thi sẽ tốt hơn nếu bảng Kiểm tra chứa nhiều hàng hơn?
Kiểm tra trình kích hoạt với 1.000 hàng
Tập lệnh sau có thể được sử dụng để kiểm tra trình kích hoạt với số lượng hàng lớn hơn. Chúng tôi sẽ bắt đầu với 1.000:
CREATE TABLE Test ( RowID integer NOT NULL, Data integer NOT NULL, CONSTRAINT PK_Test_RowID PRIMARY KEY CLUSTERED (RowID) ); GO SET STATISTICS XML OFF; SET NOCOUNT ON; GO DECLARE @i integer = 1; WHILE @i <= 1000 BEGIN INSERT dbo.Test (RowID, Data) VALUES (@i, @i * 100); SET @i += 1; END; GO CREATE TRIGGER dbo_Test_IOD ON dbo.Test INSTEAD OF DELETE AS BEGIN SET NOCOUNT ON; DELETE FROM dbo.Test WHERE EXISTS ( SELECT * FROM Deleted WHERE Deleted.RowID = dbo.Test.RowID ); END; GO SET STATISTICS XML ON; GO DELETE dbo.Test; GO DROP TABLE dbo.Test;
Kế hoạch thực thi cho phần thân trình kích hoạt bây giờ là:
Về mặt tinh thần, thay thế Tìm kiếm chỉ mục theo cụm (gây hiểu lầm) bằng Tìm kiếm đã xóa, kế hoạch này nhìn chung khá tốt. Trình tối ưu hóa đã chọn Liên kết hợp nhất một đến nhiều thay vì Tham gia bán vòng lặp lồng nhau, điều này có vẻ hợp lý. Tuy nhiên, Phân loại Phân biệt là một bổ sung gây tò mò:
Sắp xếp này đang thực hiện hai chức năng. Đầu tiên, nó đang cung cấp phép nối hợp nhất với đầu vào được sắp xếp mà nó cần, điều này đủ công bằng vì không có chỉ mục nào trên bảng làm việc nội bộ để cung cấp thứ tự cần thiết. Điều thứ hai mà phân loại đang làm là phân biệt trên RowID. Điều này có vẻ kỳ lạ, vì RowID là khóa chính của bảng cơ sở.
Vấn đề là các hàng trong đã xóa bảng chỉ đơn giản là các hàng ứng viên mà truy vấn DELETE ban đầu đã xác định. Không giống như trình kích hoạt SAU KHI, các hàng này chưa được kiểm tra các vi phạm ràng buộc hoặc khóa, vì vậy bộ xử lý truy vấn không đảm bảo rằng chúng là duy nhất trên thực tế.
Nói chung, đây là một điểm rất quan trọng cần lưu ý với các trình kích hoạt INSTEAD OF:không có gì đảm bảo rằng các hàng được cung cấp đáp ứng bất kỳ ràng buộc nào trên bảng cơ sở (bao gồm cả NOT NULL). Điều này không chỉ quan trọng đối với tác giả kích hoạt cần nhớ; nó cũng giới hạn sự đơn giản hóa và chuyển đổi mà trình tối ưu hóa truy vấn có thể thực hiện.
Vấn đề thứ hai được hiển thị trong thuộc tính Sắp xếp ở trên, nhưng không được đánh dấu, là ước tính đầu ra chỉ là 32 hàng. Bảng làm việc nội bộ không có thống kê nào được liên kết với nó, vì vậy trình tối ưu hóa đoán khi có hiệu lực của phép toán Khác biệt. Chúng tôi 'biết' các giá trị RowID là duy nhất, nhưng nếu không có bất kỳ thông tin khó tiếp tục nào, trình tối ưu hóa sẽ đoán sai. Vấn đề này sẽ trở lại ám ảnh chúng tôi trong thử nghiệm tiếp theo.
Kiểm tra trình kích hoạt với 5.000 hàng
Bây giờ, hãy sửa đổi tập lệnh thử nghiệm để tạo ra 5.000 hàng:
CREATE TABLE Test ( RowID integer NOT NULL, Data integer NOT NULL, CONSTRAINT PK_Test_RowID PRIMARY KEY CLUSTERED (RowID) ); GO SET STATISTICS XML OFF; SET NOCOUNT ON; GO DECLARE @i integer = 1; WHILE @i <= 5000 BEGIN INSERT dbo.Test (RowID, Data) VALUES (@i, @i * 100); SET @i += 1; END; GO CREATE TRIGGER dbo_Test_IOD ON dbo.Test INSTEAD OF DELETE AS BEGIN SET NOCOUNT ON; DELETE FROM dbo.Test WHERE EXISTS ( SELECT * FROM Deleted WHERE Deleted.RowID = dbo.Test.RowID ); END; GO SET STATISTICS XML ON; GO DELETE dbo.Test; GO DROP TABLE dbo.Test;
Kế hoạch thực thi trình kích hoạt là:
Lần này trình tối ưu hóa đã quyết định tách các hoạt động riêng biệt và sắp xếp. Sự khác biệt trên RowID được thực hiện bởi toán tử Hash Match (Aggregate):
Lưu ý rằng ước tính của trình tối ưu hóa cho đầu ra là 71 hàng. Trên thực tế, tất cả 5.000 hàng tồn tại sự khác biệt bởi vì RowID là duy nhất. Ước tính không chính xác có nghĩa là một phần không đủ của quyền bộ nhớ truy vấn được phân bổ cho Sắp xếp, cuối cùng sẽ tràn sang tempdb :
Kiểm tra này phải được thực hiện trên SQL Server 2012 trở lên để xem cảnh báo sắp xếp trong kế hoạch thực thi. Trong các phiên bản trước, kế hoạch không chứa thông tin về sự cố tràn - cần có dấu vết Hồ sơ về sự kiện Cảnh báo sắp xếp để tiết lộ nó (và bằng cách nào đó, bạn sẽ cần liên hệ điều đó trở lại với truy vấn nguồn).
Kiểm tra trình kích hoạt với 5.000 hàng trên SQL Server 2014
Nếu thử nghiệm trước đó được lặp lại trên SQL Server 2014, trong cơ sở dữ liệu được đặt thành mức tương thích 120 để sử dụng công cụ ước tính bản số (CE) mới, thì kế hoạch thực thi trình kích hoạt lại khác:
Theo một số cách, kế hoạch thực thi này có vẻ như là một sự cải tiến. Phân loại Riêng biệt (không cần thiết) vẫn ở đó, nhưng chiến lược tổng thể có vẻ tự nhiên hơn:đối với mỗi ứng cử viên riêng biệt RowID trong đã xóa bảng, tham gia vào bảng cơ sở (để xác minh rằng hàng ứng cử viên thực sự tồn tại) và sau đó xóa nó.
Thật không may, kế hoạch năm 2014 dựa trên ước tính bản số kém hơn chúng ta đã thấy trong SQL Server 2012. Chuyển SQL Sentry Plan Explorer để hiển thị ước tính số lượng hàng cho thấy rõ vấn đề:
Trình tối ưu hóa đã chọn chiến lược Vòng lặp lồng nhau cho phép kết hợp vì nó mong đợi một số lượng hàng rất nhỏ ở đầu vào trên cùng của nó. Sự cố đầu tiên xảy ra tại Tìm kiếm chỉ mục theo cụm. Trình tối ưu hóa biết bảng đã xóa chứa 5.000 hàng tại thời điểm này, như chúng ta có thể thấy bằng cách chuyển sang chế độ xem Cây kế hoạch và thêm cột Số lượng bảng tùy chọn (mà tôi muốn được đưa vào theo mặc định):
Công cụ ước tính số lượng thẻ 'cũ' trong SQL Server 2012 trở về trước đủ thông minh để biết rằng lệnh 'tìm kiếm' trên bảng làm việc nội bộ sẽ trả về tất cả 5.000 hàng (vì vậy nó đã chọn một phép nối hợp nhất). CE mới không quá thông minh. Nó coi bảng làm việc như một 'hộp đen' và đoán tác động của lệnh tìm kiếm trên mã hành động =3:
Việc phỏng đoán 71 hàng (làm tròn lên) là một kết quả khá tồi tệ, nhưng sai số càng tăng khi CE mới ước tính các hàng cho hoạt động riêng biệt trên 71 hàng đó:
Dựa trên 8 hàng dự kiến, trình tối ưu hóa chọn chiến lược Vòng lặp lồng nhau. Một cách khác để xem các lỗi ước tính này là thêm câu lệnh sau vào phần thân trình kích hoạt (chỉ dành cho mục đích kiểm tra):
SELECT COUNT_BIG(DISTINCT RowID) FROM Deleted;
Kế hoạch ước tính chỉ ra các sai sót ước tính một cách rõ ràng:
Tất nhiên, kế hoạch thực tế vẫn hiển thị 5.000 hàng:
Hoặc bạn có thể so sánh ước tính với thực tế tại cùng một thời điểm trong chế độ xem Cây kế hoạch:
Một triệu hàng…
Các ước tính phỏng đoán kém khi sử dụng công cụ ước tính bản số 2014 khiến trình tối ưu hóa chọn chiến lược Vòng lặp lồng nhau ngay cả khi bảng Kiểm tra chứa một triệu hàng. CE mới năm 2014 ước tính kế hoạch cho bài kiểm tra đó là:
'Tìm kiếm' ước tính 1.000 hàng từ tổng số 1.000.000 đã biết và ước tính khác biệt là 32 hàng. Kế hoạch sau khi thực hiện cho thấy tác động lên bộ nhớ dành cho Hash Match:
Chỉ mong đợi 32 hàng, Hash Match thực sự gặp rắc rối, làm đổ đệ quy bảng băm của nó trước khi hoàn thành.
Lời kết
Mặc dù đúng là không bao giờ nên viết trình kích hoạt để làm điều gì đó có thể đạt được với tính toàn vẹn tham chiếu có khai báo, nhưng được viết tốt cũng đúng trình kích hoạt sử dụng một hiệu quả kế hoạch thực hiện có thể được so sánh về hiệu suất với chi phí duy trì thêm một chỉ mục không phân biệt.
Có hai vấn đề thực tế với câu nói trên. Đầu tiên (và với ý chí tốt nhất trên thế giới) mọi người không phải lúc nào cũng viết mã kích hoạt tốt. Thứ hai, việc có được một kế hoạch thực thi tốt từ trình tối ưu hóa truy vấn trong mọi trường hợp có thể khó khăn. Bản chất của các trình kích hoạt là chúng được gọi với một loạt các bản số đầu vào và phân phối dữ liệu.
Ngay cả đối với các trình kích hoạt SAU KHI, việc thiếu chỉ mục và thống kê về đã xóa và đã chèn bảng giả có nghĩa là lựa chọn kế hoạch thường dựa trên phỏng đoán hoặc thông tin sai lệch. Ngay cả khi một kế hoạch tốt được chọn ban đầu, các lần thực hiện sau này có thể sử dụng lại cùng một kế hoạch khi biên dịch lại sẽ là lựa chọn tốt hơn. Có nhiều cách để khắc phục những hạn chế, chủ yếu thông qua việc sử dụng các bảng tạm thời và chỉ mục / thống kê rõ ràng nhưng thậm chí cần phải hết sức cẩn thận (vì trình kích hoạt là một dạng thủ tục được lưu trữ).
Với trình kích hoạt INSTEAD OF, rủi ro có thể còn lớn hơn vì nội dung của được chèn và đã xóa bảng là các ứng cử viên chưa được xác minh - trình tối ưu hóa truy vấn không thể sử dụng các ràng buộc trên bảng cơ sở để đơn giản hóa và tinh chỉnh kế hoạch thực thi của nó. Công cụ ước tính cardinality mới trong SQL Server 2014 cũng thể hiện một bước lùi thực sự khi nói đến INSTEAD OF các kế hoạch kích hoạt. Đoán hiệu quả của một hoạt động tìm kiếm mà động cơ tự giới thiệu là một sự giám sát đáng ngạc nhiên và không được hoan nghênh.