Tại sao CTE đệ quy này với tham số sử dụng một chỉ mục khi nó thực hiện với một chữ?


8

Tôi đang sử dụng CTE đệ quy trên cấu trúc cây để liệt kê tất cả các hậu duệ của một nút cụ thể trong cây. Nếu tôi viết một giá trị nút bằng chữ trong WHEREmệnh đề của mình , SQL Server dường như thực sự áp dụng CTE chỉ cho giá trị đó, đưa ra một kế hoạch truy vấn với số lượng hàng thực tế thấp, et cetera :

kế hoạch truy vấn với giá trị bằng chữ

Tuy nhiên, nếu tôi truyền giá trị dưới dạng tham số, có vẻ như nhận ra (spool) CTE và sau đó lọc nó sau khi thực tế :

kế hoạch truy vấn với giá trị tham số

Tôi có thể đọc sai kế hoạch. Tôi chưa nhận thấy vấn đề về hiệu năng, nhưng tôi lo lắng rằng việc thực hiện CTE có thể gây ra sự cố với các tập dữ liệu lớn hơn, đặc biệt là trong một hệ thống bận rộn hơn. Ngoài ra, tôi thường kết hợp điều này qua chính nó: Tôi đi ngược lại tổ tiên và quay trở lại con cháu (để đảm bảo rằng tôi thu thập tất cả các nút liên quan). Do dữ liệu của tôi như thế nào, mỗi tập hợp các nút có liên quan đến các mối quan hệ khác nhau khá nhỏ, do đó việc thực hiện CTE không có ý nghĩa gì. Và khi SQL Server dường như nhận ra CTE, nó sẽ cho tôi một số lượng khá lớn trong số đếm thực tế của nó.

Có cách nào để có được phiên bản tham số của truy vấn để hoạt động như phiên bản theo nghĩa đen không? Tôi muốn đặt CTE trong một cái nhìn có thể tái sử dụng.

Truy vấn bằng chữ:

CREATE PROCEDURE #c AS BEGIN;
    WITH descendants AS (SELECT
         t.ParentId Id
        ,t.Id DescendantId
    FROM #tree t
    WHERE t.ParentId IS NOT NULL
    UNION ALL SELECT
         d.Id
        ,t.Id DescendantId
    FROM descendants d
    JOIN #tree t ON d.DescendantId = t.ParentId)
    SELECT d.*
    FROM descendants d
    WHERE d.Id = 24
    ORDER BY d.Id, d.DescendantId;
END;
GO
EXEC #c;

Truy vấn với tham số:

CREATE PROCEDURE #c (@Id BIGINT) AS BEGIN;
    WITH descendants AS (SELECT
         t.ParentId Id
        ,t.Id DescendantId
    FROM #tree t
    WHERE t.ParentId IS NOT NULL
    UNION ALL SELECT
         d.Id
        ,t.Id DescendantId
    FROM descendants d
    JOIN #tree t ON d.DescendantId = t.ParentId)
    SELECT d.*
    FROM descendants d
    WHERE d.Id = @Id
    ORDER BY d.Id, d.DescendantId;
END;
GO
EXEC #c 24;

Mã cài đặt:

DECLARE @count BIGINT = 100000;
CREATE TABLE #tree (
     Id BIGINT NOT NULL PRIMARY KEY
    ,ParentId BIGINT
);
CREATE INDEX tree_23lk4j23lk4j ON #tree (ParentId);
WITH number AS (SELECT
         CAST(1 AS BIGINT) Value
    UNION ALL SELECT
         n.Value * 2 + 1
    FROM number n
    WHERE n.Value * 2 + 1 <= @count
    UNION ALL SELECT
         n.Value * 2
    FROM number n
    WHERE n.Value * 2 <= @count)
INSERT #tree (Id, ParentId)
SELECT n.Value, CASE WHEN n.Value % 3 = 0 THEN n.Value / 4 END
FROM number n;

Câu trả lời:


12

Câu trả lời của Randi Vertongen giải quyết chính xác cách bạn có thể nhận gói bạn muốn với phiên bản truy vấn được tham số hóa. Câu trả lời này bổ sung rằng bằng cách giải quyết tiêu đề của câu hỏi trong trường hợp bạn quan tâm đến các chi tiết.

SQL Server viết lại các biểu thức bảng chung đệ quy đuôi (CTE) dưới dạng lặp. Tất cả mọi thứ từ Lazy Index Spool trở xuống là việc thực hiện thời gian chạy của bản dịch lặp. Tôi đã viết một tài khoản chi tiết về cách phần này của một kế hoạch thực hiện hoạt động để trả lời cho việc sử dụng EXCEPT trong biểu thức bảng chung đệ quy .

Bạn muốn chỉ định một vị từ (bộ lọc) bên ngoài CTE và có trình tối ưu hóa truy vấn đẩy bộ lọc này xuống bên trong đệ quy (viết lại dưới dạng lặp) và áp dụng nó cho thành viên neo. Điều này có nghĩa là đệ quy bắt đầu chỉ với những hồ sơ phù hợp ParentId = @Id.

Đây là một kỳ vọng khá hợp lý, cho dù giá trị bằng chữ, biến hoặc tham số được sử dụng; tuy nhiên, trình tối ưu hóa chỉ có thể thực hiện những điều mà các quy tắc đã được viết. Các quy tắc xác định cách một cây truy vấn logic được sửa đổi để đạt được một chuyển đổi cụ thể. Chúng bao gồm logic để đảm bảo kết quả cuối cùng là an toàn - tức là nó trả về chính xác dữ liệu giống như đặc tả truy vấn ban đầu trong tất cả các trường hợp có thể.

Quy tắc chịu trách nhiệm đẩy các biến vị ngữ trên CTE đệ quy được gọi SelOnIterator- một lựa chọn quan hệ (= vị ngữ) trên một trình lặp thực hiện đệ quy. Chính xác hơn, quy tắc này có thể sao chép một lựa chọn xuống phần neo của phép lặp đệ quy:

Sel(Iter(A,R)) -> Sel(Iter(Sel(A),R))

Quy tắc này có thể được vô hiệu hóa với gợi ý không có giấy tờ OPTION(QUERYRULEOFF SelOnIterator). Khi điều này được sử dụng, trình tối ưu hóa không còn có thể đẩy các biến vị ngữ có giá trị bằng chữ xuống mỏ neo của CTE đệ quy. Bạn không muốn điều đó, nhưng nó minh họa điểm.

Ban đầu, quy tắc này được giới hạn để làm việc trên các vị từ chỉ với các giá trị theo nghĩa đen. Nó cũng có thể được thực hiện để làm việc với các biến hoặc tham số bằng cách chỉ định OPTION (RECOMPILE), vì gợi ý đó cho phép Tối ưu hóa nhúng tham số , theo đó giá trị bằng chữ của thời gian chạy của biến (hoặc tham số) được sử dụng khi biên dịch kế hoạch. Kế hoạch không được lưu trữ, vì vậy nhược điểm của việc này là một phần tổng hợp mới trên mỗi lần thực hiện.

Tại một số điểm, SelOnIteratorquy tắc đã được cải thiện để cũng hoạt động với các biến và tham số. Để tránh những thay đổi kế hoạch không mong muốn, điều này đã được bảo vệ dưới cờ theo dõi 4199, mức độ tương thích cơ sở dữ liệu và mức độ tương thích hotfix tối ưu hóa truy vấn. Đây là một mô hình khá bình thường để cải thiện trình tối ưu hóa, không phải lúc nào cũng được ghi lại. Những cải tiến thường tốt cho hầu hết mọi người, nhưng luôn có khả năng bất kỳ thay đổi nào cũng sẽ đưa ra hồi quy cho ai đó.

Tôi muốn đặt CTE ở chế độ có thể sử dụng lại

Bạn có thể sử dụng hàm có giá trị bảng nội tuyến thay vì dạng xem. Cung cấp giá trị bạn muốn đẩy xuống làm tham số và đặt vị từ trong thành viên neo đệ quy.

Nếu bạn thích, bật cờ theo dõi 4199 trên toàn cầu cũng là một tùy chọn. Có nhiều thay đổi tối ưu hóa được bao phủ bởi cờ này, vì vậy bạn sẽ cần kiểm tra cẩn thận khối lượng công việc của mình khi được bật và sẵn sàng xử lý hồi quy.


10

Mặc dù hiện tại tôi không có tiêu đề của hotfix thực tế, kế hoạch truy vấn tốt hơn sẽ được sử dụng khi bật hotfix tối ưu hóa truy vấn trên phiên bản của bạn (SQL Server 2012).

Một số phương pháp khác là:

  • Sử dụng OPTION(RECOMPILE)để lọc xảy ra sớm hơn, trên giá trị bằng chữ.
  • Trên SQL Server 2016 hoặc cao hơn các hotfix trước khi phiên bản này được áp dụng tự động và truy vấn cũng sẽ chạy tương đương với kế hoạch thực hiện tốt hơn.

Tối ưu hóa truy vấn Hotfixes

Bạn có thể kích hoạt các bản sửa lỗi này với

  • Traceflag 4199 trước SQL Server 2016
  • ALTER DATABASE SCOPED CONFIGURATION SET QUERY_OPTIMIZER_HOTFIXES=ON; bắt đầu từ SQL Server 2016. (không cần thiết cho sửa lỗi của bạn)

Việc lọc trên @idđược áp dụng sớm hơn cho cả các thành viên đệ quy và neo trong kế hoạch thực hiện với hotfix được kích hoạt.

Dấu vết có thể được thêm vào ở cấp truy vấn:

OPTION(QUERYTRACEON 4199)

Khi chạy truy vấn trên SQL Server 2012 SP4 GDR hoặc SQL Server 2014 SP3 với Traceflag 4199, kế hoạch truy vấn tốt hơn được chọn:

ALTER PROCEDURE #c (@Id BIGINT) AS BEGIN;
    WITH descendants AS (SELECT
         t.ParentId Id
        ,t.Id DescendantId
    FROM #tree t 
    WHERE t.ParentId IS NOT NULL
    UNION ALL 
    SELECT
         d.Id
        ,t.Id DescendantId
    FROM descendants d
    JOIN #tree t ON d.DescendantId = t.ParentId)
    SELECT d.*
    FROM descendants d
    WHERE d.Id = @Id
    ORDER BY d.Id, d.DescendantId
    OPTION( QUERYTRACEON 4199 );

END;
GO
EXEC #c 24;

Kế hoạch truy vấn trên SQL Server 2014 SP3 Với trackflag 4199

Kế hoạch truy vấn trên SQL Server 2012 SP4 GDR với trackflag 4199

Kế hoạch truy vấn trên SQL Server 2012 SP4 GDR mà không theo dõi 4199

Sự đồng thuận chính là để kích hoạt trackflag 4199 trên toàn cầu khi sử dụng phiên bản trước SQL Server 2016. Sau đó, nó được mở để thảo luận về việc có nên kích hoạt nó hay không. AQ / A về điều đó ở đây .


Mức độ tương thích 130 hoặc 140

Khi kiểm tra truy vấn được tham số hóa trên cơ sở dữ liệu với compatibility_level= 130 hoặc 140, quá trình lọc xảy ra trước đó:

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

Do thực tế là các bản sửa lỗi 'cũ' từ trackflag 4199 được bật trên SQL Server 2016 trở lên.


TÙY CHỌN (RECOMPILE)

Mặc dù một thủ tục được sử dụng, SQL Server sẽ có thể lọc theo giá trị bằng chữ khi thêm OPTION(RECOMPILE);.

ALTER PROCEDURE #c (@Id BIGINT) AS BEGIN;
    WITH descendants AS (SELECT
         t.ParentId Id
        ,t.Id DescendantId
    FROM #tree t 
    WHERE t.ParentId IS NOT NULL
    UNION ALL 
    SELECT
         d.Id
        ,t.Id DescendantId
    FROM descendants d
    JOIN #tree t ON d.DescendantId = t.ParentId)
    SELECT d.*
    FROM descendants d
    WHERE d.Id = @Id
    ORDER BY d.Id, d.DescendantId
OPTION(
RECOMPILE )

END;
GO

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

Gói truy vấn trên SQL Server 2012 SP4 GDR với TÙY CHỌN (RECOMPILE)

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.