Truy vấn không trả lời khi thêm hai cột


9

Khi tôi thêm hai cột vào lựa chọn của mình, truy vấn không trả lời. Các loại cột là nvarchar(2000). Đó là một chút khác thường.

  • Phiên bản SQL Server là 2014.
  • Chỉ có một chỉ số chính.
  • Toàn bộ hồ sơ chỉ có 1000 hàng.

Đây là kế hoạch thực hiện trước (kế hoạch XML ):

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

Kế hoạch thực hiện sau (kế hoạch XML ):

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

Đây là truy vấn:

select top(100)
  Batch_Tasks_Queue.id,
  btq.id,
  Batch_Tasks_Queue.[Parameters], -- this field
  btq.[Parameters]  -- and this field
from
        Batch_Tasks_Queue with(nolock)
    inner join  Batch_Tasks_Queue btq with(nolock)  on  Batch_Tasks_Queue.Start_Time < btq.Start_Time
                            and btq.Start_Time < Batch_Tasks_Queue.Finish_Time
                            and Batch_Tasks_Queue.id <> btq.id                            
                            and btq.Start_Time is not null
                            and btq.State in (3, 4)                          
where
    Batch_Tasks_Queue.Start_Time is not null      
    and Batch_Tasks_Queue.State in (3, 4)
    and Batch_Tasks_Queue.Operation_Type = btq.Operation_Type
    and Batch_Tasks_Queue.Operation_Type not in (23, 24, 25, 26, 27, 28, 30)

order by
    Batch_Tasks_Queue.Start_Time desc

Tổng số kết quả là 17 hàng. Dữ liệu bẩn (gợi ý nolock) không quan trọng.

Đây là cấu trúc bảng:

CREATE TABLE [dbo].[Batch_Tasks_Queue](
    [Id] [int] NOT NULL,
    [OBJ_VERSION] [numeric](8, 0) NOT NULL,
    [Operation_Type] [numeric](2, 0) NULL,
    [Request_Time] [datetime] NOT NULL,
    [Description] [varchar](1000) NULL,
    [State] [numeric](1, 0) NOT NULL,
    [Start_Time] [datetime] NULL,
    [Finish_Time] [datetime] NULL,
    [Parameters] [nvarchar](2000) NULL,
    [Response] [nvarchar](max) NULL,
    [Billing_UserId] [int] NOT NULL,
    [Planned_Start_Time] [datetime] NULL,
    [Input_FileId] [uniqueidentifier] NULL,
    [Output_FileId] [uniqueidentifier] NULL,
    [PRIORITY] [numeric](2, 0) NULL,
    [EXECUTE_SEQ] [numeric](2, 0) NULL,
    [View_Access] [numeric](1, 0) NULL,
    [Seeing] [numeric](1, 0) NULL,
 CONSTRAINT [PKBachTskQ] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [Batch_Tasks_QueueData]
) ON [Batch_Tasks_QueueData] TEXTIMAGE_ON [Batch_Tasks_QueueData]
GO    
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[Batch_Tasks_Queue]  WITH NOCHECK ADD  CONSTRAINT [FK0_BtchTskQ_BlngUsr] FOREIGN KEY([Billing_UserId])
REFERENCES [dbo].[BILLING_USER] ([ID])
GO
ALTER TABLE [dbo].[Batch_Tasks_Queue] CHECK CONSTRAINT [FK0_BtchTskQ_BlngUsr]
GO

Thảo luận về câu hỏi này đã được chuyển đến phòng chat này .
Paul White 9

Câu trả lời:


15

Tóm lược

Các vấn đề chính là:

  • Lựa chọn gói của trình tối ưu hóa giả định phân phối đồng nhất các giá trị.
  • Thiếu chỉ số phù hợp có nghĩa là:
    • Quét bảng là lựa chọn duy nhất.
    • Join là một ngây thơ vòng lồng nhau tham gia, chứ không phải là một chỉ số lồng vòng tham gia. Trong một phép nối ngây thơ, các vị từ tham gia được đánh giá tại phép nối thay vì bị đẩy xuống phía bên trong của phép nối.

Chi tiết

Hai kế hoạch về cơ bản khá giống nhau, mặc dù hiệu suất có thể rất khác nhau:

Lập kế hoạch với các cột thêm

Lấy một cột có các cột bổ sung không hoàn thành trong thời gian hợp lý trước tiên:

Kế hoạch chậm

Các tính năng thú vị là:

  1. Top tại nút 0 giới hạn các hàng được trả về 100. Nó cũng đặt mục tiêu hàng cho trình tối ưu hóa, vì vậy mọi thứ bên dưới nó trong kế hoạch được chọn để trả về 100 hàng đầu tiên một cách nhanh chóng.
  2. Quét tại nút 4 tìm các hàng từ bảng trong đó Start_Timekhông phải là null, Statelà 3 hoặc 4 và Operation_Typelà một trong các giá trị được liệt kê. Bảng được quét hoàn toàn một lần, với mỗi hàng được kiểm tra đối với các vị từ được đề cập. Chỉ các hàng vượt qua tất cả các bài kiểm tra chảy vào Sắp xếp. Trình tối ưu hóa ước tính rằng 38.283 hàng sẽ đủ điều kiện.
  3. Sắp xếp tại nút 3 tiêu thụ tất cả các hàng từ Quét tại nút 4 và sắp xếp chúng theo thứ tự Start_Time DESC. Đây là thứ tự trình bày cuối cùng được yêu cầu bởi truy vấn.
  4. Trình tối ưu hóa ước tính rằng 93 hàng (thực tế là 93.2791) sẽ phải được đọc từ Sắp xếp để toàn bộ kế hoạch trả về 100 hàng (tính cho hiệu quả mong đợi của phép nối).
  5. Các vòng lặp Nested Loops tại nút 2 dự kiến ​​sẽ thực hiện đầu vào bên trong của nó (nhánh dưới) 94 lần (thực tế là 94.2791). Hàng bổ sung được yêu cầu bởi trao đổi song song dừng tại nút 1 vì lý do kỹ thuật.
  6. Quét tại nút 5 quét toàn bộ bảng trên mỗi lần lặp. Nó tìm thấy các hàng Start_Timekhông rỗng và Statelà 3 hoặc 4. Điều này được ước tính để tạo ra 400.875 hàng trên mỗi lần lặp. Hơn 94.2791 lần lặp, tổng số hàng là gần 38 triệu.
  7. Các vòng lặp Nested Loops tại nút 2 cũng áp dụng các biến vị ngữ nối. Nó kiểm tra xem có Operation_Typekhớp với nhau không, Start_Timetừ nút 4 nhỏ hơn Start_Timenút 5, Start_Timetừ nút 5 nhỏ hơn Finish_Timenút 4 và hai Idgiá trị không khớp nhau.
  8. Luồng tập hợp (dừng trao đổi song song) tại nút 1 hợp nhất các luồng được sắp xếp từ mỗi luồng cho đến khi 100 hàng được tạo. Bản chất giữ trật tự của hợp nhất trên nhiều luồng là những gì đòi hỏi hàng bổ sung được đề cập trong bước 5.

Sự kém hiệu quả rõ ràng là ở bước 6 và 7 ở trên. Quét toàn bộ bảng tại nút 5 cho mỗi lần lặp chỉ thậm chí hơi hợp lý nếu nó chỉ xảy ra 94 lần như trình tối ưu hóa dự đoán. Bộ so sánh ~ 38 triệu mỗi hàng tại nút 2 cũng là một chi phí lớn.

Điều quan trọng, ước tính mục tiêu hàng 93/94 cũng có khả năng sai, vì nó phụ thuộc vào phân phối giá trị. Trình tối ưu hóa giả định phân phối đồng đều trong trường hợp không có thông tin chi tiết hơn. Nói một cách đơn giản, điều này có nghĩa là nếu 1% số hàng trong bảng được dự kiến ​​đủ điều kiện, thì lý do tối ưu hóa để tìm 1 hàng khớp, nó cần đọc 100 hàng.

Nếu bạn đã chạy truy vấn này đến khi hoàn thành (có thể mất một thời gian rất dài), rất có thể bạn sẽ thấy rằng nhiều hơn 93/94 hàng phải được đọc từ Sắp xếp để cuối cùng tạo ra 100 hàng. Trong trường hợp xấu nhất, hàng thứ 100 sẽ được tìm thấy bằng cách sử dụng hàng cuối cùng từ Sắp xếp. Giả sử ước tính của trình tối ưu hóa tại nút 4 là chính xác, điều này có nghĩa là chạy Quét tại nút 5 38.284 lần, với tổng số khoảng 15 tỷ hàng. Nó có thể nhiều hơn nếu các ước tính Quét cũng tắt.

Kế hoạch thực hiện này cũng bao gồm một cảnh báo chỉ mục bị thiếu:

/*
The Query Processor estimates that implementing the following index
could improve the query cost by 72.7096%.

WARNING: This is only an estimate, and the Query Processor is making
this recommendation based solely upon analysis of this specific query.
It has not considered the resulting index size, or its workload-wide
impact, including its impact on INSERT, UPDATE, DELETE performance.
These factors should be taken into account before creating this index.
*/

CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
ON [dbo].[Batch_Tasks_Queue] ([Operation_Type],[State],[Start_Time])
INCLUDE ([Id],[Parameters])

Trình tối ưu hóa đang cảnh báo bạn về thực tế rằng việc thêm một chỉ mục vào bảng sẽ cải thiện hiệu suất.

Lập kế hoạch mà không cần thêm cột

Kế hoạch ít chậm

Đây thực chất là cùng một kế hoạch như kế hoạch trước đó, với việc bổ sung Index Spool tại nút 6 và Bộ lọc tại nút 5. Sự khác biệt quan trọng là:

  1. Index Spool tại nút 6 là Spool Eager. Nó háo hức tiêu thụ kết quả của việc quét bên dưới nó và xây dựng một chỉ mục tạm thời được khóa Operation_TypeStart_Time, với Idtư cách là một cột không khóa.
  2. Các vòng lặp Nested Tham gia tại nút 2 bây giờ là một tham gia chỉ mục. Không tham gia các vị từ được đánh giá ở đây, thay vào đó là giá trị mỗi lần lặp hiện tại của Operation_Type, Start_Time, Finish_Time, và Idtừ quá trình quét tại nút 4 được truyền cho các chi nhánh bên phía như tài liệu tham khảo bên ngoài.
  3. Quét tại nút 7 chỉ được thực hiện một lần.
  4. Chỉ số Spool tại nút 6 tìm kiếm các hàng từ chỉ mục tạm thời Operation_Typephù hợp với giá trị tham chiếu bên ngoài hiện tại và Start_Timenằm trong phạm vi được xác định bởi các tham chiếu bên ngoài Start_TimeFinish_Time.
  5. Bộ lọc tại nút 5 kiểm tra Idcác giá trị từ Spool Index để tìm sự bất bình đẳng so với giá trị tham chiếu bên ngoài hiện tại của Id.

Những cải tiến chính là:

  • Quét bên trong chỉ được thực hiện một lần
  • Một chỉ mục tạm thời trên ( Operation_Type, Start_Time) với Idtư cách là một cột được bao gồm cho phép một vòng lặp lồng nhau được lập chỉ mục. Chỉ mục được sử dụng để tìm kiếm các hàng khớp trên mỗi lần lặp thay vì quét toàn bộ bảng mỗi lần.

Như trước đây, trình tối ưu hóa bao gồm cảnh báo về một chỉ mục bị thiếu:

/*
The Query Processor estimates that implementing the following index
could improve the query cost by 24.1475%.

WARNING: This is only an estimate, and the Query Processor is making
this recommendation based solely upon analysis of this specific query.
It has not considered the resulting index size, or its workload-wide
impact, including its impact on INSERT, UPDATE, DELETE performance.
These factors should be taken into account before creating this index.
*/

CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
ON [dbo].[Batch_Tasks_Queue] ([State],[Start_Time])
INCLUDE ([Id],[Operation_Type])
GO

Phần kết luận

Kế hoạch không có các cột bổ sung sẽ nhanh hơn vì trình tối ưu hóa đã chọn để tạo một chỉ mục tạm thời cho bạn.

Kế hoạch với các cột thêm sẽ làm cho chỉ số tạm thời đắt hơn để xây dựng. Các [Parameters] cột là nvarchar(2000), mà sẽ thêm lên đến 4000 byte cho mỗi hàng của chỉ số. Chi phí bổ sung đủ để thuyết phục người tối ưu hóa rằng việc xây dựng chỉ mục tạm thời trên mỗi lần thực hiện sẽ không tự trả.

Trình tối ưu hóa cảnh báo trong cả hai trường hợp rằng một chỉ số vĩnh viễn sẽ là một giải pháp tốt hơn. Thành phần lý tưởng của chỉ số phụ thuộc vào khối lượng công việc rộng hơn của bạn. Đối với truy vấn cụ thể này, các chỉ mục được đề xuất là điểm khởi đầu hợp lý, nhưng bạn nên hiểu lợi ích và chi phí liên quan.

sự giới thiệu

Một loạt các chỉ mục có thể sẽ có ích cho truy vấn này. Điểm quan trọng là một số loại chỉ số không bao gồm là cần thiết. Từ thông tin được cung cấp, theo tôi, một chỉ số hợp lý sẽ là:

CREATE NONCLUSTERED INDEX i1
ON dbo.Batch_Tasks_Queue (Start_Time DESC)
INCLUDE (Operation_Type, [State], Finish_Time);

Tôi cũng được khuyến khích tổ chức truy vấn tốt hơn một chút và trì hoãn tìm kiếm các [Parameters]cột rộng trong chỉ mục được nhóm cho đến khi tìm thấy 100 hàng trên cùng (sử dụng Idlàm khóa):

SELECT TOP (100)
    BTQ1.id,
    BTQ2.id,
    BTQ3.[Parameters],
    BTQ4.[Parameters]
FROM dbo.Batch_Tasks_Queue AS BTQ1
JOIN dbo.Batch_Tasks_Queue AS BTQ2 WITH (FORCESEEK)
    ON BTQ2.Operation_Type = BTQ1.Operation_Type
    AND BTQ2.Start_Time > BTQ1.Start_Time
    AND BTQ2.Start_Time < BTQ1.Finish_Time
    AND BTQ2.id != BTQ1.id
    -- Look up the [Parameters] values
JOIN dbo.Batch_Tasks_Queue AS BTQ3
    ON BTQ3.Id = BTQ1.Id
JOIN dbo.Batch_Tasks_Queue AS BTQ4
    ON BTQ4.Id = BTQ2.Id
WHERE
    BTQ1.[State] IN (3, 4)
    AND BTQ2.[State] IN (3, 4)
    AND BTQ1.Operation_Type NOT IN (23, 24, 25, 26, 27, 28, 30)
    AND BTQ2.Operation_Type NOT IN (23, 24, 25, 26, 27, 28, 30)
    -- These predicates are not strictly needed
    AND BTQ1.Start_Time IS NOT NULL
    AND BTQ2.Start_Time IS NOT NULL
ORDER BY
    BTQ1.Start_Time DESC;

Trường hợp các [Parameters]cột không cần thiết, truy vấn có thể được đơn giản hóa thành:

SELECT TOP (100)
    BTQ1.id,
    BTQ2.id
FROM dbo.Batch_Tasks_Queue AS BTQ1
JOIN dbo.Batch_Tasks_Queue AS BTQ2 WITH (FORCESEEK)
    ON BTQ2.Operation_Type = BTQ1.Operation_Type
    AND BTQ2.Start_Time > BTQ1.Start_Time
    AND BTQ2.Start_Time < BTQ1.Finish_Time
    AND BTQ2.id != BTQ1.id
WHERE
    BTQ1.[State] IN (3, 4)
    AND BTQ2.[State] IN (3, 4)
    AND BTQ1.Operation_Type NOT IN (23, 24, 25, 26, 27, 28, 30)
    AND BTQ2.Operation_Type NOT IN (23, 24, 25, 26, 27, 28, 30)
    AND BTQ1.Start_Time IS NOT NULL
    AND BTQ2.Start_Time IS NOT NULL
ORDER BY
    BTQ1.Start_Time DESC;

Các FORCESEEKgợi ý là có để giúp đảm bảo tối ưu hóa lựa chọn một vòng lồng nhau được lập chỉ mục kế hoạch (có một sự cám dỗ dựa trên chi phí cho tôi ưu hoa để chọn một băm hoặc (nhiều nhiều) Merge Tham gia khác, mà không có xu hướng làm việc tốt với các loại truy vấn trong thực tế. Cả hai kết thúc với số dư lớn, nhiều mục trên mỗi thùng trong trường hợp băm và nhiều tua lại để hợp nhất).

Thay thế

Nếu truy vấn (bao gồm các giá trị cụ thể của nó) đặc biệt quan trọng đối với hiệu suất đọc, tôi sẽ xem xét hai chỉ mục được lọc thay thế:

CREATE NONCLUSTERED INDEX i1
ON dbo.Batch_Tasks_Queue (Start_Time DESC)
INCLUDE (Operation_Type, [State], Finish_Time)
WHERE 
    Start_Time IS NOT NULL
    AND [State] IN (3, 4)
    AND Operation_Type <> 23
    AND Operation_Type <> 24
    AND Operation_Type <> 25
    AND Operation_Type <> 26
    AND Operation_Type <> 27
    AND Operation_Type <> 28
    AND Operation_Type <> 30;

CREATE NONCLUSTERED INDEX i2
ON dbo.Batch_Tasks_Queue (Operation_Type, [State], Start_Time)
WHERE 
    Start_Time IS NOT NULL
    AND [State] IN (3, 4)
    AND Operation_Type <> 23
    AND Operation_Type <> 24
    AND Operation_Type <> 25
    AND Operation_Type <> 26
    AND Operation_Type <> 27
    AND Operation_Type <> 28
    AND Operation_Type <> 30;

Đối với truy vấn không cần [Parameters]cột, kế hoạch ước tính sử dụng các chỉ mục được lọc là:

Kế hoạch chỉ mục được lọc đơn giản

Quét chỉ mục tự động trả về tất cả các hàng đủ điều kiện mà không đánh giá bất kỳ vị từ bổ sung nào. Đối với mỗi lần lặp của các vòng lặp lồng nhau tham gia, chỉ mục tìm kiếm thực hiện hai thao tác tìm kiếm:

  1. Một tiền tố tìm kiếm khớp với Operation_TypeState= 3, sau đó tìm kiếm phạm vi của các Start_Timegiá trị, vị từ còn lại trên Idbất đẳng thức.
  2. Một tiền tố tìm kiếm khớp với Operation_TypeState= 4, sau đó tìm kiếm phạm vi của các Start_Timegiá trị, vị từ còn lại trên Idbất đẳng thức.

Khi [Parameters]cần cột, kế hoạch truy vấn chỉ cần thêm tối đa 100 lần tra cứu đơn lẻ cho mỗi bảng:

Kế hoạch chỉ mục được lọc với các cột thêm

Là một lưu ý cuối cùng, bạn nên xem xét sử dụng các loại số nguyên tiêu chuẩn tích hợp thay vì numericnơi áp dụng.


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.