Tại sao chi phí ước tính của (cùng) 1000 tìm kiếm trên một chỉ mục duy nhất lại khác nhau trong các kế hoạch này?


27

Trong các truy vấn bên dưới, cả hai kế hoạch thực hiện được ước tính để thực hiện 1.000 lần tìm kiếm trên một chỉ mục duy nhất.

Các tìm kiếm được điều khiển bởi một lần quét theo thứ tự trên cùng một bảng nguồn, vì vậy dường như cuối cùng sẽ tìm kiếm các giá trị tương tự theo cùng một thứ tự.

Cả hai vòng lặp lồng nhau có <NestedLoops Optimized="false" WithOrderedPrefetch="true">

Bất cứ ai cũng biết tại sao nhiệm vụ này có giá 0,172434 trong kế hoạch đầu tiên nhưng 3,01702 trong lần thứ hai?

. .)

Thiết lập

CREATE TABLE dbo.Target(KeyCol int PRIMARY KEY, OtherCol char(32) NOT NULL);

CREATE TABLE dbo.Staging(KeyCol int PRIMARY KEY, OtherCol char(32) NOT NULL); 

INSERT INTO dbo.Target
SELECT TOP (1000000) ROW_NUMBER() OVER (ORDER BY @@SPID), LEFT(NEWID(),32)
FROM master..spt_values v1,  
     master..spt_values v2;

INSERT INTO dbo.Staging
SELECT TOP (1000) ROW_NUMBER() OVER (ORDER BY @@SPID), LEFT(NEWID(),32)
FROM master..spt_values v1;

Truy vấn 1 liên kết "Dán kế hoạch"

WITH T
     AS (SELECT *
         FROM   Target AS T
         WHERE  T.KeyCol IN (SELECT S.KeyCol
                             FROM   Staging AS S))
MERGE T
USING Staging S
ON ( T.KeyCol = S.KeyCol )
WHEN NOT MATCHED THEN
  INSERT ( KeyCol, OtherCol )
  VALUES(S.KeyCol, S.OtherCol )
WHEN MATCHED AND T.OtherCol > S.OtherCol THEN
  UPDATE SET T.OtherCol = S.OtherCol;

Truy vấn 2 liên kết "Dán kế hoạch"

MERGE Target T
USING Staging S
ON ( T.KeyCol = S.KeyCol )
WHEN NOT MATCHED THEN
  INSERT ( KeyCol, OtherCol )
  VALUES( S.KeyCol, S.OtherCol )
WHEN MATCHED AND T.OtherCol > S.OtherCol THEN
  UPDATE SET T.OtherCol = S.OtherCol; 

Truy vấn 1

Truy vấn 2

Những điều trên đã được thử nghiệm trên SQL Server 2014 (SP2) (KB3171021) - 12.0.5000.0 (X64)


@Joe Obbish chỉ ra trong các ý kiến ​​rằng một repro đơn giản hơn sẽ là

SELECT *
FROM staging AS S 
  LEFT OUTER JOIN Target AS T 
    ON T.KeyCol = S.KeyCol;

đấu với

SELECT *
FROM staging AS S 
  LEFT OUTER JOIN (SELECT * FROM Target) AS T 
    ON T.KeyCol = S.KeyCol;

Đối với bảng phân tầng 1.000 hàng, cả hai bảng trên vẫn có hình dạng kế hoạch giống nhau với các vòng lặp lồng nhau và kế hoạch không có bảng dẫn xuất hiện rẻ hơn, nhưng đối với bảng phân tầng 10.000 và cùng bảng mục tiêu như trên, sự khác biệt về chi phí sẽ thay đổi kế hoạch hình dạng (với một lần quét toàn bộ và kết hợp tham gia có vẻ tương đối hấp dẫn hơn so với các tìm kiếm tốn kém chi phí) cho thấy sự khác biệt về chi phí này có thể có ý nghĩa khác ngoài việc làm cho việc so sánh các kế hoạch trở nên khó khăn hơn.

nhập mô tả hình ảnh ở đây

Câu trả lời:


20

Bất cứ ai cũng biết tại sao nhiệm vụ này có giá 0,172434 trong kế hoạch đầu tiên nhưng 3,01702 trong lần thứ hai?

Nói chung, một tìm kiếm bên trong bên dưới tham gia các vòng lặp lồng nhau được tính chi phí giả định một mẫu I / O ngẫu nhiên. Có một cách giảm dựa trên thay thế đơn giản cho các lần truy cập tiếp theo, tính đến khả năng trang bắt buộc đã được đưa vào bộ nhớ bằng lần lặp trước. Đánh giá cơ bản này tạo ra chi phí tiêu chuẩn (cao hơn).

Có một đầu vào chi phí khác, Chi phí tìm kiếm thông minh , về chi tiết nhỏ được biết đến. Tôi đoán (và đó là tất cả những gì ở giai đoạn này) là SSC cố gắng đánh giá chi phí I / O bên trong chi tiết hơn, có lẽ bằng cách xem xét thứ tự địa phương và / hoặc phạm vi giá trị để tìm nạp. Ai biết.

Ví dụ: thao tác tìm kiếm đầu tiên mang lại không chỉ hàng được yêu cầu, mà tất cả các hàng trên trang đó (theo thứ tự chỉ mục). Với mẫu truy cập tổng thể, việc tìm nạp 1000 hàng trong 1000 lượt tìm kiếm chỉ cần 2 lần đọc vật lý, ngay cả khi đã đọc trước và tắt tìm nạp trước. Từ quan điểm đó, chi phí I / O mặc định đại diện cho sự đánh giá quá cao đáng kể và chi phí được điều chỉnh theo SSC gần với thực tế hơn.

Có vẻ hợp lý khi hy vọng rằng SSC sẽ có hiệu quả nhất khi vòng lặp điều khiển một chỉ mục tìm kiếm trực tiếp ít nhiều và tham chiếu bên ngoài tham gia là cơ sở của hoạt động tìm kiếm. Từ những gì tôi có thể nói, SSC luôn cố gắng cho các hoạt động vật lý phù hợp, nhưng hầu hết thường không tạo ra sự điều chỉnh đi xuống khi tìm kiếm được tách ra khỏi liên kết bởi các hoạt động khác. Các bộ lọc đơn giản là một ngoại lệ cho điều này, có lẽ vì SQL Server thường có thể đẩy chúng vào toán tử truy cập dữ liệu. Trong mọi trường hợp, trình tối ưu hóa có hỗ trợ khá sâu cho các lựa chọn.

Thật không may là tính toán vô hướng cho các phép chiếu bên ngoài truy vấn phụ dường như can thiệp vào SSC ở đây. Tính toán vô hướng thường được di chuyển trên tham gia, nhưng những người này phải ở nơi họ đang ở. Mặc dù vậy, hầu hết các Compute Scalars bình thường đều khá minh bạch để tối ưu hóa, vì vậy điều này hơi bất ngờ.

Bất kể, khi hoạt động vật lý PhyOp_Rangeđược tạo ra từ một lựa chọn đơn giản trên một chỉ mục SelIdxToRng, SSC có hiệu quả. Khi phức tạp hơn SelToIdxStrategy(lựa chọn trên một bảng cho một chiến lược chỉ mục) được sử dụng, kết quả sẽ PhyOp_Rangechạy SSC nhưng kết quả không giảm. Một lần nữa, có vẻ như các hoạt động trực tiếp hơn, đơn giản hơn hoạt động tốt nhất với SSC.

Tôi ước tôi có thể cho bạn biết chính xác những gì SSC làm và hiển thị các tính toán chính xác, nhưng tôi không biết những chi tiết đó. Nếu bạn muốn khám phá đầu ra theo dõi hạn chế có sẵn cho mình, bạn có thể sử dụng cờ theo dõi không có giấy tờ 2398. Một đầu ra ví dụ là:

Chi phí tìm kiếm thông minh (7.1) :: 1.34078e + 154, 0,001

Ví dụ đó liên quan đến nhóm ghi nhớ 7, phương án 1, cho thấy giới hạn trên của chi phí và hệ số 0,001. Để xem các yếu tố sạch hơn, hãy chắc chắn xây dựng lại các bảng mà không có sự song song để các trang càng dày đặc càng tốt. Không làm điều đó, hệ số giống như 0,000821 cho bảng Target mẫu của bạn. Có một số mối quan hệ khá rõ ràng ở đó, tất nhiên.

SSC cũng có thể bị vô hiệu hóa với cờ theo dõi không có giấy tờ 2399. Với cờ đó hoạt động, cả hai chi phí đều có giá trị cao hơn.


8

Không chắc đây là một câu trả lời nhưng nó hơi dài cho một bình luận. Nguyên nhân của sự khác biệt là do suy đoán thuần túy từ phía tôi và có lẽ có thể là thức ăn cho người khác nghĩ.

Truy vấn đơn giản hóa với các kế hoạch thực hiện.

SELECT S.KeyCol, 
       S.OtherCol,
       T.*
FROM staging AS S 
  LEFT OUTER JOIN Target AS T 
    ON T.KeyCol = S.KeyCol;

SELECT S.KeyCol, 
       S.OtherCol,
       T.*
FROM staging AS S 
  LEFT OUTER JOIN (
                  SELECT *
                  FROM Target
                  ) AS T 
    ON T.KeyCol = S.KeyCol;

nhập mô tả hình ảnh ở đây

Sự khác biệt chính giữa các truy vấn tương đương thực sự có thể dẫn đến các kế hoạch thực hiện giống hệt nhau là toán tử vô hướng tính toán. Tôi không biết tại sao nó phải ở đó nhưng tôi đoán đó là cách tối ưu hóa có thể đi để tối ưu hóa bảng dẫn xuất.

Tôi đoán là sự hiện diện của vô hướng tính toán là những gì đang làm giảm chi phí IO cho truy vấn thứ hai.

Từ bên trong Trình tối ưu hóa: Chi phí kế hoạch

Chi phí CPU được tính là 0,0001581 cho hàng đầu tiên và 0,000011 cho các hàng tiếp theo.
...
Chi phí I / O 0,003125 chính xác là 1/320 - phản ánh giả định của mô hình rằng hệ thống con đĩa có thể thực hiện 320 thao tác I / O ngẫu nhiên mỗi giây
...
thành phần chi phí đủ thông minh để nhận ra rằng tổng số các trang cần được đưa vào từ đĩa không bao giờ có thể vượt quá số lượng trang cần thiết để lưu trữ toàn bộ bảng.

Trong trường hợp của tôi, bảng mất 5618 trang và để có 1000 hàng từ 1000000 hàng, số lượng trang cần thiết là 5.618 với Chi phí IO là 0,015625.

Chi phí CPU cho cả hai đường nối truy vấn là như nhau , 0.0001581 * 1000 executions = 0.1581.

Vì vậy, theo bài viết được liên kết ở trên, chúng ta có thể tính chi phí cho truy vấn đầu tiên là 0,173725.

Và giả sử tôi đúng về cách tính toán vô hướng tạo ra một mớ hỗn độn của IO Cost, nó có thể được tính đến 3.2831.

Không chính xác những gì được hiển thị trong các kế hoạch nhưng nó ở ngay trong khu phố.


6

(Điều này sẽ tốt hơn khi bình luận câu trả lời của Paul, nhưng tôi chưa có đủ đại diện.)

Tôi muốn cung cấp danh sách các cờ theo dõi (và một vài DBCCcâu lệnh) mà tôi đã sử dụng để đi đến kết luận gần, trong trường hợp sẽ rất hữu ích khi điều tra những khác biệt tương tự trong tương lai. Tất cả những thứ này không nên được sử dụng trong sản xuất .

Đầu tiên, tôi đã xem qua Bản ghi nhớ cuối cùng để xem những toán tử vật lý nào đang được sử dụng. Chúng chắc chắn trông giống nhau theo các kế hoạch thực hiện đồ họa. Vì vậy, tôi đã sử dụng cờ theo dõi 36048615, đầu ra chỉ đạo đầu tiên cho máy khách và lần thứ hai tiết lộ Bản ghi nhớ cuối cùng:

SELECT S.*, T.KeyCol
FROM Staging AS S
      LEFT OUTER JOIN Target AS T
       ON T.KeyCol = S.KeyCol
OPTION(QUERYTRACEON 3604, -- Output client info
       QUERYTRACEON 8615, -- Shows Final Memo structure
       RECOMPILE);

Truy tìm lại từ Root Group, tôi tìm thấy các PhyOp_Rangetoán tử gần như giống hệt nhau :

  1. PhyOp_Range 1 ASC 2.0 Cost(RowGoal 0,ReW 0,ReB 999,Dist 1000,Total 1000)= 0.175559(Distance = 2)
  2. PhyOp_Range 1 ASC 3.0 Cost(RowGoal 0,ReW 0,ReB 999,Dist 1000,Total 1000)= 3.01702(Distance = 2)

Sự khác biệt rõ ràng duy nhất với tôi là 2.03.0, trong đó đề cập đến "nhóm ghi nhớ 2, bản gốc" và "bản ghi nhớ nhóm 3, bản gốc" tương ứng của họ. Kiểm tra bản ghi nhớ, những điều này đề cập đến cùng một điều - vì vậy chưa có sự khác biệt nào được tiết lộ.

Thứ hai, tôi đã nhìn vào một mớ hỗn độn các cờ dấu vết chứng tỏ không có kết quả với tôi - nhưng có một số nội dung thú vị. Tôi nhấc hầu hết từ Benjamin Nevarez . Tôi đã tìm kiếm manh mối như các quy tắc tối ưu hóa được áp dụng trong một trường hợp chứ không phải các trường hợp khác.

 SELECT S.*, T.KeyCol
 FROM Staging AS S
      LEFT OUTER JOIN Target AS T
        ON T.KeyCol = S.KeyCol
 OPTION (QUERYTRACEON 3604, -- Output info to client
         QUERYTRACEON 2363, -- Show stats and cardinality info
         QUERYTRACEON 8675, -- Show optimization process info
         QUERYTRACEON 8606, -- Show logical query trees
         QUERYTRACEON 8607, -- Show physical query tree
         QUERYTRACEON 2372, -- Show memory utilization info for optimization stages 
         QUERYTRACEON 2373, -- Show memory utilization info for applying rules
         RECOMPILE );

Thứ ba, tôi đã xem xét các quy tắc được áp dụng cho các quy tắc của chúng tôi PhyOp_Rangetrông rất giống nhau. Tôi đã sử dụng một vài lá cờ dấu vết được đề cập bởi Paul trong một bài đăng trên blog .

SELECT S.*, T.KeyCol
FROM Staging AS S
      LEFT OUTER JOIN (SELECT KeyCol
                      FROM Target) AS T
       ON T.KeyCol = S.KeyCol
OPTION (QUERYTRACEON 3604, -- Output info to client
        QUERYTRACEON 8619, -- Show applied optimization rules
        QUERYTRACEON 8620, -- Show rule-to-memo info
        QUERYTRACEON 8621, -- Show resulting tree
        QUERYTRACEON 2398, -- Show "smart seek costing"
        RECOMPILE );

Từ đầu ra, chúng tôi thấy rằng JOINquy tắc này được áp dụng trực tiếp để có được PhyOp_Rangetoán tử của chúng tôi : Rule Result: group=7 2 <SelIdxToRng>PhyOp_Range 1 ASC 2 (Distance = 2). Subelect áp dụng quy tắc này thay thế : Rule Result: group=9 2 <SelToIdxStrategy>PhyOp_Range 1 ASC 3 (Distance = 2). Đây cũng là nơi bạn thấy thông tin "chi phí tìm kiếm thông minh" được liên kết với mỗi quy tắc. Đối với trực tiếp- JOINđây là đầu ra (đối với tôi) : Smart seek costing (7.2) :: 1.34078e+154 , 0.001. Đối với subselect, đây là đầu ra : Smart seek costing (9.2) :: 1.34078e+154 , 1.

Cuối cùng, tôi không thể kết luận nhiều - nhưng câu trả lời của Paul đã thu hẹp hầu hết khoảng cách. Tôi muốn xem thêm một số thông tin về chi phí tìm kiếm thông minh.


4

Đây thực sự không phải là một câu trả lời - như Mikael lưu ý, thật khó để thảo luận vấn đề này trong các bình luận ...

Thật thú vị, nếu bạn chuyển đổi truy vấn con (select KeyCol FROM Target)thành TVF nội tuyến, bạn sẽ thấy kế hoạch và chi phí của nó, giống như truy vấn ban đầu đơn giản:

CREATE FUNCTION dbo.cs_test()
RETURNS TABLE
WITH SCHEMABINDING
AS 
RETURN (
    SELECT KeyCol FROM dbo.Target
    );

/* "normal" variant */
SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN Target AS T ON T.KeyCol = S.KeyCol;

/* "subquery" variant */
SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN (SELECT KeyCol FROM Target) AS T ON T.KeyCol = S.KeyCol;

/* "inline-TVF" variant */
SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN dbo.cs_test() t ON s.KeyCol = t.Keycol

Các kế hoạch truy vấn ( liên kết pastetheplan ):

nhập mô tả hình ảnh ở đây

Khấu trừ khiến tôi tin rằng công cụ chi phí bị nhầm lẫn về tác động tiềm năng mà loại truy vấn phụ này có thể gây ra .

Lấy ví dụ, như sau:

SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN (
        SELECT KeyCol = CHECKSUM(NEWID()) 
        FROM Target
        ) AS T ON T.KeyCol = S.KeyCol;

Làm thế nào bạn sẽ chi phí đó? Trình tối ưu hóa truy vấn chọn một kế hoạch rất giống với biến thể "truy vấn con" ở trên, có chứa một vô hướng tính toán ( liên kết pastetheplan.com ):

nhập mô tả hình ảnh ở đây

Vô hướng tính toán có chi phí khá khác so với biến thể "truy vấn con" được hiển thị ở trên, tuy nhiên nó vẫn chỉ là phỏng đoán vì trình tối ưu hóa truy vấn không có cách nào để biết, một tiên nghiệm, số lượng hàng được trả về có thể là bao nhiêu. Kế hoạch sử dụng kết quả băm cho phép nối ngoài bên trái do các ước tính hàng không thể biết được và do đó được đặt thành số lượng hàng trong bảng Target.

nhập mô tả hình ảnh ở đây

Tôi không có một kết luận tuyệt vời nào từ điều này ngoại trừ việc tôi đồng ý với công việc mà Mikael đã làm trong câu trả lời của anh ấy và hy vọng ai đó có thể đưa ra câu trả lời tốt hơn.

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.