Bạn có thể giải thích kế hoạch thực hiện này?


20

Tôi đã nghiên cứu một cái gì đó khác khi tôi gặp điều này. Tôi đã tạo các bảng thử nghiệm với một số dữ liệu trong đó và chạy các truy vấn khác nhau để tìm hiểu các cách khác nhau để viết các truy vấn ảnh hưởng đến kế hoạch thực hiện. Đây là kịch bản mà tôi đã sử dụng để tạo dữ liệu thử nghiệm ngẫu nhiên:

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID('t') AND type in (N'U'))
DROP TABLE t
GO

CREATE TABLE t 
(
 c1 int IDENTITY(1,1) NOT NULL 
,c2 int NULL
) 
GO

insert into t
select top 1000000 a from
(select t1.number*2048 + t2.number a, newid() b
from [master]..spt_values t1 
cross join  [master]..spt_values t2
where t1.[type] = 'P' and t2.[type] = 'P') a
order by b
GO

update t set c2 = null
where c2 < 2048 * 2048 / 10
GO


CREATE CLUSTERED INDEX pk ON [t] (c1)
GO

CREATE NONCLUSTERED INDEX i ON t (c2)
GO

Bây giờ, với dữ liệu này, tôi đã gọi truy vấn sau:

select * 
from t 
where 
      c2 < 1048576 
   or c2 is null
;

Thật ngạc nhiên, kế hoạch thực hiện được tạo ra cho truy vấn này, là cái này . (Xin lỗi vì liên kết ngoài, nó quá lớn để phù hợp ở đây).

Ai đó có thể giải thích cho tôi những gì xảy ra với tất cả các " Quét liên tục " và " Tính toán vô hướng " không? Chuyện gì đang xảy ra vậy?

Kế hoạch

  |--Nested Loops(Inner Join, OUTER REFERENCES:([Expr1010], [Expr1011], [Expr1012]))
       |--Merge Interval
       |    |--Sort(TOP 2, ORDER BY:([Expr1013] DESC, [Expr1014] ASC, [Expr1010] ASC, [Expr1015] DESC))
       |         |--Compute Scalar(DEFINE:([Expr1013]=((4)&[Expr1012]) = (4) AND NULL = [Expr1010], [Expr1014]=(4)&[Expr1012], [Expr1015]=(16)&[Expr1012]))
       |              |--Concatenation
       |                   |--Compute Scalar(DEFINE:([Expr1005]=NULL, [Expr1006]=NULL, [Expr1004]=(60)))
       |                   |    |--Constant Scan
       |                   |--Compute Scalar(DEFINE:([Expr1008]=NULL, [Expr1009]=(1048576), [Expr1007]=(10)))
       |                        |--Constant Scan
       |--Index Seek(OBJECT:([t].[i]), SEEK:([t].[c2] > [Expr1010] AND [t].[c2] < [Expr1011]) ORDERED FORWARD)

Câu trả lời:


29

Hằng số quét từng tạo ra một hàng trong bộ nhớ duy nhất không có cột. Số vô hướng tính toán trên cùng xuất ra một hàng đơn có 3 cột

Expr1005    Expr1006    Expr1004
----------- ----------- -----------
NULL        NULL        60

Tính toán vô hướng dưới cùng xuất ra một hàng đơn có 3 cột

Expr1008    Expr1009    Expr1007
----------- ----------- -----------
NULL        1048576        10

Toán tử ghép nối Liên kết 2 hàng này lại với nhau và xuất ra 3 cột nhưng giờ chúng được đổi tên

Expr1010    Expr1011    Expr1012
----------- ----------- -----------
NULL        NULL        60
NULL        1048576     10

Các Expr1012cột là một tập hợp của những lá cờ sử dụng trong nội bộ để xác định một số tìm kiếm bất động sản cho Công cụ lưu trữ .

Tính toán vô hướng tiếp theo dọc theo đầu ra 2 hàng

Expr1010    Expr1011    Expr1012    Expr1013    Expr1014    Expr1015
----------- ----------- ----------- ----------- ----------- -----------
NULL        NULL        60          True        4           16            
NULL        1048576     10          False       0           0      

Ba cột cuối cùng được định nghĩa như sau và chỉ được sử dụng để sắp xếp các mục đích trước khi trình bày cho Toán tử Khoảng thời gian Hợp nhất

[Expr1013] = Scalar Operator(((4)&[Expr1012]) = (4) AND NULL = [Expr1010]), 
[Expr1014] = Scalar Operator((4)&[Expr1012]), 
[Expr1015] = Scalar Operator((16)&[Expr1012])

Expr1014Expr1015chỉ kiểm tra xem một số bit nhất định có trong cờ không. Expr1013xuất hiện để trả về một cột boolean đúng nếu cả hai bit 4được bật và Expr1010NULL.

Từ việc thử các toán tử so sánh khác trong truy vấn, tôi nhận được các kết quả này

+----------+----------+----------+-------------+----+----+---+---+---+---+
| Operator | Expr1010 | Expr1011 | Flags (Dec) |       Flags (Bin)       |
|          |          |          |             | 32 | 16 | 8 | 4 | 2 | 1 |
+----------+----------+----------+-------------+----+----+---+---+---+---+
| >        | 1048576  | NULL     |           6 |  0 |  0 | 0 | 1 | 1 | 0 |
| >=       | 1048576  | NULL     |          22 |  0 |  1 | 0 | 1 | 1 | 0 |
| <=       | NULL     | 1048576  |          42 |  1 |  0 | 1 | 0 | 1 | 0 |
| <        | NULL     | 1048576  |          10 |  0 |  0 | 1 | 0 | 1 | 0 |
| =        | 1048576  | 1048576  |          62 |  1 |  1 | 1 | 1 | 1 | 0 |
| IS NULL  | NULL     | NULL     |          60 |  1 |  1 | 1 | 1 | 0 | 0 |
+----------+----------+----------+-------------+----+----+---+---+---+---+

Từ đó tôi suy ra rằng Bit 4 có nghĩa là "Đã bắt đầu phạm vi" (trái ngược với việc không bị ràng buộc) và Bit 16 có nghĩa là bắt đầu phạm vi được bao gồm.

Tập kết quả 6 cột này được phát ra từ SORTtoán tử được sắp xếp theo Expr1013 DESC, Expr1014 ASC, Expr1010 ASC, Expr1015 DESC. Giả sử Trueđược đại diện bởi 1Falsebởi 0các kết quả được đại diện trước đó đã theo thứ tự đó.

Dựa trên các giả định trước đây của tôi, hiệu ứng ròng của loại này là trình bày các phạm vi cho khoảng thời gian hợp nhất theo thứ tự sau

 ORDER BY 
          HasStartOfRangeAndItIsNullFirst,
          HasUnboundedStartOfRangeFirst,
          StartOfRange,
          StartOfRangeIsInclusiveFirst

Toán tử khoảng thời gian hợp nhất xuất ra 2 hàng

Expr1010    Expr1011    Expr1012
----------- ----------- -----------
NULL        NULL        60
NULL        1048576     10

Đối với mỗi hàng phát ra một phạm vi tìm kiếm được thực hiện

Seek Keys[1]: Start:[dbo].[t].c2 > Scalar Operator([Expr1010]), 
               End: [dbo].[t].c2 < Scalar Operator([Expr1011])

Vì vậy, nó sẽ xuất hiện như thể hai tìm kiếm được thực hiện. Một rõ ràng > NULL AND < NULLvà một > NULL AND < 1048576. Tuy nhiên, các cờ được truyền vào xuất hiện để sửa đổi điều này thành IS NULL< 1048576tương ứng. Hy vọng @sqlkiwi có thể làm rõ điều này và sửa chữa mọi điểm không chính xác!

Nếu bạn thay đổi truy vấn một chút thành

select *
from t 
where 
      c2 > 1048576 
   or c2 = 0
;

Sau đó, kế hoạch trông đơn giản hơn nhiều với một tìm kiếm chỉ mục với nhiều biến vị ngữ tìm kiếm.

Kế hoạch cho thấy Seek Keys

Start: c2 >= 0, End: c2 <= 0, 
Start: c2 > 1048576

Giải thích cho lý do tại sao kế hoạch đơn giản hơn này không thể được sử dụng cho trường hợp trong OP được SQLKiwi đưa ra trong các nhận xét cho bài đăng trên blog được liên kết trước đó .

Một chỉ mục tìm kiếm với nhiều biến vị ngữ không thể trộn lẫn các loại vị từ so sánh khác nhau (nghĩa là IsEqtrong trường hợp trong OP). Đây chỉ là một giới hạn hiện tại của sản phẩm (và có lẽ là lý do tại sao kiểm tra tính bằng trong truy vấn cuối cùng c2 = 0được thực hiện bằng cách sử dụng >=<=thay vì chỉ là sự bình đẳng đơn giản mà bạn tìm kiếm cho truy vấn c2 = 0 OR c2 = 1048576.


Tôi không thể phát hiện bất cứ điều gì trong bài viết của Paul giải thích sự khác biệt trong các cờ cho [Expr1012]. Bạn có thể suy ra những gì 60/10 biểu thị ở đây?
Mark Storey-Smith

@ MarkStorey-Smith - ông nói 62là để so sánh bình đẳng. Tôi đoán 60phải có nghĩa là thay vì > AND < như trong kế hoạch mà bạn thực tế có được >= AND <=trừ khi đó là một IS NULLcờ rõ ràng có thể (?) Hoặc có thể bit 2chỉ ra một cái gì đó không liên quan và 60vẫn bình đẳng như khi tôi làm set ansi_nulls offvà thay đổi nó thành c2 = nullnó vẫn ở60
Martin Smith

2
@MartinSmith 60 thực sự là một so sánh với NULL. Các biểu thức ranh giới phạm vi sử dụng NULL để biểu thị 'không giới hạn' ở hai đầu. Tìm kiếm luôn độc quyền tức là tìm kiếm Bắt đầu:> Expr & End: <Expr thay vì bao gồm sử dụng> = và <=. Cảm ơn vì bình luận trên blog, tôi sẽ đăng câu trả lời hoặc bình luận dài hơn để trả lời vào buổi sáng (quá muộn để thực hiện công lý ngay bây giờ).
Paul White nói GoFundMonica

@QueryKiwi - Cảm ơn. Điều đó có ý nghĩa. Hy vọng rằng tôi sẽ tìm ra một số bit bị thiếu trước đó.
Martin Smith

Cảm ơn bạn rất nhiều, tôi vẫn tiếp thu điều này, nhưng dường như nó giải thích mọi thứ rất hay, câu hỏi chính còn lại là câu hỏi mà bạn hỏi @QueryKiwi trên blog của anh ấy. Tôi sẽ thiền thêm vài ngày nữa cho câu trả lời của bạn để đảm bảo tôi không có bất kỳ câu hỏi tiếp theo nào và tôi sẽ chấp nhận câu trả lời của bạn. Cảm ơn bạn một lần nữa, nó là một trợ giúp rất lớn.
Andrew Savinykh

13

Quét liên tục là một cách để SQL Server tạo ra một nhóm mà sau đó nó sẽ đặt một cái gì đó trong kế hoạch thực hiện. Tôi đã đăng một lời giải thích kỹ lưỡng hơn về nó ở đây . Để hiểu quét liên tục để làm gì, bạn phải xem xét thêm về kế hoạch. Trong trường hợp này, đó là toán tử vô hướng tính toán đang được sử dụng để điền vào không gian được tạo bởi quá trình quét liên tục.

Các toán tử Compute Scalar đang được tải lên với NULL và giá trị 1045876, vì vậy chúng rõ ràng sẽ được sử dụng với Loop Join trong nỗ lực lọc dữ liệu.

Phần thực sự thú vị là kế hoạch này là Trivial. Nó có nghĩa là nó đã trải qua một quá trình tối ưu hóa tối thiểu. Tất cả các hoạt động đang dẫn đến Khoảng thời gian hợp nhất. Điều này được sử dụng để tạo ra một tập hợp các toán tử so sánh tối thiểu cho tìm kiếm chỉ mục ( chi tiết về điều đó ở đây ).

Toàn bộ ý tưởng là loại bỏ các giá trị chồng chéo để sau đó nó có thể kéo dữ liệu ra với các đường truyền tối thiểu. Mặc dù nó vẫn đang sử dụng thao tác vòng lặp, nhưng bạn sẽ lưu ý rằng vòng lặp thực thi chính xác một lần, nghĩa là, đó thực sự là một lần quét.

ĐỊA CHỈ: Câu cuối cùng đã tắt. Có hai lần tìm kiếm. Tôi đọc sai kế hoạch. Phần còn lại của các khái niệm là như nhau và mục tiêu, vượt qua tối thiểu, là như nhau.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.