Kế hoạch thực hiện không tốt sau khi cập nhật số liệu do bảng tạm thời


8

Một truy vấn thủ tục được lưu trữ đôi khi nhận được một kế hoạch xấu sau khi cập nhật thống kê trên một trong các bảng, nhưng có thể được biên dịch lại thành kế hoạch tốt ngay sau đó. Cùng tham số biên dịch.

Vấn đề dường như đến từ một bảng tạm thời nhỏ được tạo trong SP và sau đó tham gia. Kế hoạch xấu có một cảnh báo trên bảng tạm thời rằng cột tham gia không có số liệu thống kê. Đưa cái gì?

SQL Server 2016 SP1 CU4, với mức độ tương thích 2014

Kế hoạch xấu:

Ảnh chụp màn hình kế hoạch xấu

Kế hoạch tốt:

Ảnh chụp màn hình kế hoạch tốt

Thủ tục lưu trữ

USE AppDB
GO
SET QUOTED_IDENTIFIER ON
SET ANSI_NULLS ON
GO
CREATE PROCEDURE [MySchema].[MySP]
    @MyId VARCHAR(50),
    @Months INT
AS
BEGIN

    SET NOCOUNT ON

    SELECT * 
    INTO #MyTemp
    FROM AppDB.MySchema.View_Feeder vf WITH (NOLOCK)
    WHERE vf.MyId = @MyId AND vf.Status IS NOT NULL

    SELECT wd.Col1
         , vp.Col2
         , vp.Col3 
    FROM AppDB.MySchema.View_VP vp WITH (FORCESEEK)
    INNER JOIN #MyTemp wd ON wd.Col1 = vp.Col1
    WHERE vp.Col3 > DATEADD(MONTH, @Months * -1, GETDATE())

END

Góc nhìn bên trong

USE AppDB
GO
SET QUOTED_IDENTIFIER ON
SET ANSI_NULLS ON
GO
CREATE VIEW [MySchema].[View_VP]
AS

    SELECT pp.Col1,
           pd.Col2 AS Col2, 
           MAX(pp.Col4) AS Col3
    FROM P_DB..LargeTable pp WITH (NOLOCK)
    INNER JOIN P_DB..SmallTable pd WITH (NOLOCK) ON pp.P_Id = pd.P_Id
    WHERE pp.[Status] IN (3, 4)
    GROUP BY pp.Col1, pd.Col2

Các kế hoạch

Redacted kế hoạch tốtkế hoạch xấu .

Thông tin thêm

Các FORCESEEKgợi ý đã được bổ sung vào thời điểm đó để cố gắng giải quyết vấn đề này rất giống nhau và ổn định kế hoạch. Và dù sao, dù có hay không có nó, tôi thực sự muốn hiểu những gì đang xảy ra ở đây.

Tôi không thể tái tạo vấn đề theo ý muốn, vì vậy thật khó để nói rằng nếu thay thế SELECT INTObằng một bảng rõ ràng sẽ tạo ra sự khác biệt. Tuy nhiên, tôi tin rằng nó nên hành xử theo cùng một cách.

SELECT
    database_id, 
    is_auto_create_stats_on, 
    is_auto_update_stats_on, 
    is_auto_update_stats_async_on
FROM sys.databases
WHERE
    database_id IN (2, <relevant user databases>)

trả về:

  database_id   is_auto_create_stats_on   is_auto_update_stats_on   is_auto_update_stats_async_on  
 ------------- ------------------------- ------------------------- ------------------------------- 
  2             1                         1                         0                              
  7             1                         1                         1                              
  37            1                         1                         1                              

Rõ ràng là tìm kiếm này là khủng khiếp, nhưng câu hỏi là tại sao nó không thực hiện tìm kiếm tốt ngay từ đầu.

Truy vấn không trả về hàng 1m, ước tính sai. Có thể có một số thay đổi nhỏ trong đầu ra, nhưng số lượng hàng luôn khá thấp (nhiều nhất có thể là hàng trăm).

Ngay cả những hàng trả về tương đối nhiều hàng cũng tạo ra các kế hoạch tìm kiếm bởi Idvà không bao giờ bởi status(không chọn lọc như bạn có thể thấy). Tôi dường như không thể tái tạo kế hoạch tìm kiếm trạng thái, bất kể giá trị nào được biên dịch. Tôi thậm chí đã cố gắng thêm một waitfor delaygiữa việc tạo bảng tạm thời và truy vấn thứ hai, và cập nhật số liệu thống kê / biên dịch lại trong phiên thứ hai, cũng không có hiệu lực.

Câu trả lời:


12

Kế hoạch xấu có một cảnh báo trên bảng tạm thời rằng cột tham gia không có số liệu thống kê. Đưa cái gì?

Có thể có một lý do bí truyền hơn cho việc này, nhưng nhiều khả năng là một thất bại trong việc tạo số liệu thống kê đơn giản. Ví dụ, điều này có thể xảy ra khi tác vụ không nhận được tài nguyên bộ nhớ mà nó cần hoặc khi việc tạo số liệu thống kê đang được điều chỉnh (quá nhiều phần tổng hợp đồng thời). Xem Thống kê giấy trắng của Microsoft được sử dụng bởi Trình tối ưu hóa truy vấn trong Microsoft SQL Server 2008 . Bạn có thể gỡ lỗi này thêm bằng cách xem thông số tự động Profiler hoặc Sự kiện mở rộng và các sự kiện khác cùng một lúc.

Điều đó nói rằng, sẽ cần rất nhiều thông tin và điều tra để đổ lỗi cho việc lựa chọn kế hoạch trước cửa thống kê bảng tạm thời bị mất tích. Ngay cả khi không có số liệu thống kê chi tiết, trình tối ưu hóa vẫn có thể thấy tổng số thẻ của bảng tạm thời và điều này dường như là một yếu tố quan trọng ở đây.

... nhưng có thể được biên dịch lại cho kế hoạch tốt ngay sau đó. Cùng tham số biên dịch.

Các @Monthstham số có thể là giống nhau, nhưng số lượng hàng trong bảng tạm thời (từ quan điểm không rõ View_Feeder) là khác nhau, và các kế hoạch cung cấp không hiển thị giá trị của @MyId.

Đi từ thông tin có sẵn: Gói 'tốt' (chỉ ước tính, không có dữ liệu hiệu suất được trình bày) dựa trên bảng tạm thời chứa 4 hàng . "Kế hoạch xấu" dựa trên một bảng tạm thời với 114 hàng . Chắc chắn việc thiếu mật độ và thông tin biểu đồ có thể không hữu ích, nhưng thật dễ dàng để thấy cách trình tối ưu hóa có thể chọn một kế hoạch khác cho 4 so với 114 hàng, mặc dù các mật độ và phân phối không xác định.

Nếu các ước tính về các toán tử kế hoạch không phụ thuộc vào bảng tạm thời bị tắt, thì đây là một tín hiệu mạnh mẽ cho thấy các thống kê bảng chính hiện tại không đại diện cho dữ liệu cơ bản. Việc thiếu thông tin trong câu hỏi làm cho điều này không thể đánh giá.

Tuy nhiên, có thể thấy rằng trình tối ưu hóa đang được yêu cầu lựa chọn giữa các lựa chọn thay thế tối ưu phụ ở đây. Cả hai kế hoạch được trình bày đều thể hiện sự lựa chọn 'rõ ràng là tốt', vì cả hai đều liên quan đến việc tra cứu (thiếu chỉ số 'bao phủ') và lọc muộn (xem tiếp theo). Tra cứu đặc biệt có chi phí cao liên quan đến chúng, điều này phụ thuộc một cách hợp lý vào các ước tính về tim mạch.

Sử dụng chế độ xem hạn chế các lựa chọn tối ưu hóa và gợi ý:

  • Khung nhìn chứa một vị trí GROUP BYngăn không cho vị vp.Col3 > DATEADD(MONTH, @Months * -1, GETDATE())từ bị đẩy xuống, mặc dù phép biến đổi sẽ có hiệu lực trong trường hợp rất cụ thể này.
    • Xếp hàng chế độ xem cho truy vấn sẽ cung cấp một cách tự nhiên để lọc cột ngày / giờ trước đó (mặc dù câu hỏi không nêu rõ liệu tái cấu trúc truy vấn có phải là một tùy chọn không).
  • Không thể gợi ý một chỉ mục trên một khung nhìn và FORCESEEKchỉ cần yêu cầu trình tối ưu hóa tìm bất kỳ gói tìm kiếm chỉ mục nào (không nhất thiết phải sử dụng chỉ mục bạn muốn). Xóa chế độ xem cũng sẽ loại bỏ hạn chế này.

Cho phép vị ngữ đẩy xuống sẽ mở ra các cơ hội lập chỉ mục trên bàn lớn. Ví dụ:

CREATE INDEX give_me_a_good_name
ON dbo.LargeTable (Col1, [Status], Col4) 
INCLUDE (P_Id);

... cung cấp đường dẫn truy cập tốt cho truy vấn viết lại:

DECLARE @Date datetime = DATEADD(MONTH, @Months * -1, GETDATE());

SELECT
    MT.Col1,
    ST.Col2,
    MAX(LT.Col4)
FROM #MyTemp AS MT
JOIN dbo.LargeTable AS LT
    ON LT.Col1 = MT.Col1
JOIN dbo.SmallTable AS ST
    ON ST.P_id = LT.P_Id
WHERE
    LT.[Status] IN (3, 4)
    AND LT.Col4 > @Date
GROUP BY
    MT.Col1,
    ST.Col2
OPTION (RECOMPILE);

Kế hoạch ví dụ

Một xem xét khác là ảnh hưởng của bộ đệm tạm thời và thống kê bộ đệm như được mô tả trong các bài viết của tôi Các bảng tạm thời trong thủ tục lưu trữgiải thích bộ đệm tạm thời của bảng tạm thời . Nếu một kế hoạch tốt phụ thuộc vào nội dung hiện tại của đối tượng tạm thời, thì rõ ràng UPDATE STATISTICS #MyTemp;trước truy vấn chính và thêm OPTION (RECOMPILE)vào truy vấn chính có thể là một giải pháp tốt.

Ngoài ra, nếu một hình dạng kế hoạch cụ thể luôn tối ưu cho truy vấn này, bạn có sẵn nhiều tùy chọn, bao gồm nhiều gợi ý, hướng dẫn kế hoạch và buộc kế hoạch lưu trữ truy vấn. Bạn có thể thấy rằng sử dụng biến bảng thay vì bảng tạm thời là lựa chọn tốt hơn, vì nó ưu tiên trường hợp cardinality thấp và không cung cấp (hoặc dựa vào) số liệu thống kê.

Tóm lại, có một số cải tiến chung nên được thực hiện trước khi lo lắng về lý do (ảnh hưởng của) số liệu thống kê bị thiếu thường xuyên trên bảng tạm thời:

  • Đảm bảo số liệu thống kê là đại diện và hữu ích cho trình tối ưu hóa
  • Kiểm tra thực tế so với ước tính cho một loạt các giá trị tham số
  • Cung cấp (các) đường dẫn truy cập dữ liệu tốt cho truy vấn bằng cách cải thiện các chỉ mục hiện có
  • Xóa chế độ xem nếu có thể; hoặc xem xét một view chế độ xem được tham số hóa '(hàm có giá trị bảng nội tuyến) với một vị từ rõ ràng cho tham số ngày / giờ.
  • Đảm bảo rằng việc tạo số liệu thống kê tự động không bị điều chỉnh một cách không cần thiết
  • Sử dụng đúng loại đối tượng tạm thời cho tác vụ (bảng so với biến)
  • Xem xét RECOMPILEnếu lựa chọn gói rất nhạy cảm với các giá trị tham số
  • Thêm UPDATE STATISTICSRECOMPILEnếu thống kê bộ nhớ cache là một vấn đề
  • Hãy xem xét một bảng tạm thời với khóa chính thay vì SELECT INTOnếu nó cung cấp thông tin hữu ích cho trình tối ưu hóa
  • Xem lại lược đồ để đảm bảo trình tối ưu hóa có nhiều thông tin nhất có thể (ví dụ: khóa ngoại, các ràng buộc khác)
  • Xem xét sự phù hợp của các chỉ mục / thống kê được lọc dựa trên kiến ​​thức về dữ liệu của bạn
  • Đừng rắc NOLOCKgợi ý để tăng hiệu suất

Repro

Sau đây được xây dựng từ các thông tin hạn chế có sẵn trong các kế hoạch thực hiện được tái cấu trúc được cung cấp:

DROP VIEW IF EXISTS dbo.View_VP;
DROP TABLE IF EXISTS dbo.SmallTable, dbo.LargeTable, #MyTemp;
GO
CREATE TABLE LargeTable (P_Id integer NOT NULL, Status integer NOT NULL, Col1 integer NOT NULL, Col4 datetime NOT NULL);
CREATE TABLE SmallTable (P_id integer NOT NULL, Col2 integer NOT NULL)
CREATE TABLE #MyTemp (Col1 integer NOT NULL);
GO
CREATE VIEW dbo.View_VP 
AS
    SELECT
        pp.Col1,
        pd.Col2 AS Col2,
        MAX(pp.Col4) AS Col3
    FROM LargeTable pp
    JOIN SmallTable pd
        ON pd.P_id = pp.P_Id
    WHERE 
        pp.[Status] IN (3, 4)
    GROUP BY 
        pp.Col1, pd.Col2;
GO
CREATE UNIQUE CLUSTERED INDEX PK_SmallTable ON dbo.SmallTable (P_id)
CREATE CLUSTERED INDEX ix_P_id ON dbo.LargeTable (P_Id)
CREATE INDEX ix_Col1 ON dbo.LargeTable (Col1)
CREATE INDEX ix_Status ON dbo.LargeTable ([Status])
GO
UPDATE STATISTICS dbo.LargeTable WITH ROWCOUNT = 32268200, PAGECOUNT = 322682;
UPDATE STATISTICS dbo.SmallTable WITH ROWCOUNT = 6349, PAGECOUNT = 63;
UPDATE STATISTICS #MyTemp WITH ROWCOUNT = 4;

Truy vấn là:

DECLARE @Months integer = 6;

SELECT wd.Col1
         , vp.Col2
         , vp.Col3 
    FROM dbo.View_VP vp WITH (FORCESEEK)
    INNER JOIN #MyTemp wd ON wd.Col1 = vp.Col1
    WHERE vp.Col3 > DATEADD(MONTH, @Months * -1, GETDATE())

Không có số liệu thống kê thực tế trên các bảng cơ sở, điều này ủng hộ các kế hoạch gần với ví dụ 'kế hoạch xấu' (sử dụng ix_Status):

Kế hoạch demo

Điều này cho thấy thông tin về tính chọn lọc Col1là một yếu tố quan trọng trong lựa chọn của trình tối ưu hóa.


Tất nhiên bạn đúng về sự khác biệt về số lượng của bảng tạm thời, nhưng tôi sẽ chỉ thêm phần dữ liệu mà ngay cả với 114 hàng, nó thường tạo ra một gói đi theo Id chứ không phải theo trạng thái. Cảm ơn bạn!
Alex Friedman
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.