Buộc dòng chảy khác biệt


19

Tôi có một cái bàn như thế này:

CREATE TABLE Updates
(
    UpdateId INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
    ObjectId INT NOT NULL
)

Về cơ bản theo dõi cập nhật cho các đối tượng với một ID ngày càng tăng.

Người tiêu dùng của bảng này sẽ chọn một đoạn gồm 100 ID đối tượng riêng biệt, được sắp xếp theo thứ tự UpdateIdvà bắt đầu từ một cụ thể UpdateId. Về cơ bản, theo dõi nơi nó rời đi và sau đó truy vấn bất kỳ cập nhật nào.

Tôi thấy đây là một vấn đề tối ưu hóa thú vị vì tôi chỉ có thể tạo một kế hoạch truy vấn tối ưu tối đa bằng cách viết các truy vấn xảy ra để làm những gì tôi muốn do chỉ mục, nhưng không đảm bảo những gì tôi muốn:

SELECT DISTINCT TOP 100 ObjectId
FROM Updates
WHERE UpdateId > @fromUpdateId

Trong trường hợp @fromUpdateIdlà một tham số thủ tục lưu trữ.

Với một kế hoạch:

SELECT <- TOP <- Hash match (flow distinct, 100 rows touched) <- Index seek

Do tìm kiếm trên UpdateIdchỉ mục đang được sử dụng, kết quả đã tốt đẹp và được sắp xếp từ ID cập nhật thấp nhất đến cao nhất như tôi muốn. Và điều này tạo ra một kế hoạch phân biệt dòng chảy , đó là những gì tôi muốn. Nhưng thứ tự rõ ràng là hành vi không được đảm bảo, vì vậy tôi không muốn sử dụng nó.

Thủ thuật này cũng dẫn đến cùng một kế hoạch truy vấn (mặc dù với TOP dư thừa):

WITH ids AS
(
    SELECT ObjectId
    FROM Updates
    WHERE UpdateId > @fromUpdateId
    ORDER BY UpdateId OFFSET 0 ROWS
)
SELECT DISTINCT TOP 100 ObjectId FROM ids

Mặc dù, tôi không chắc chắn (và nghi ngờ là không) nếu điều này thực sự đảm bảo cho việc đặt hàng.

Một truy vấn tôi hy vọng SQL Server sẽ đủ thông minh để đơn giản hóa điều này, nhưng cuối cùng nó lại tạo ra một kế hoạch truy vấn rất tệ:

SELECT TOP 100 ObjectId
FROM Updates
WHERE UpdateId > @fromUpdateId
GROUP BY ObjectId
ORDER BY MIN(UpdateId)

Với một kế hoạch:

SELECT <- Top N Sort <- Hash Match aggregate (50,000+ rows touched) <- Index Seek

Tôi đang cố gắng tìm cách tạo ra một kế hoạch tối ưu với tìm kiếm chỉ mục UpdateIdvà một luồng khác biệt để loại bỏ các bản sao ObjectId. Có ý kiến ​​gì không?

Dữ liệu mẫu nếu bạn muốn nó. Các đối tượng sẽ hiếm khi có nhiều hơn một bản cập nhật và hầu như không bao giờ có nhiều hơn một hàng trong một bộ 100 hàng, đó là lý do tại sao tôi sau một luồng khác biệt , trừ khi tôi không biết gì hơn? Tuy nhiên, không có gì đảm bảo rằng một đơn ObjectIdsẽ không có hơn 100 hàng trong bảng. Bảng có hơn 1.000.000 hàng và dự kiến ​​sẽ tăng nhanh.

Giả sử người dùng này có một cách khác để tìm cái thích hợp tiếp theo @fromUpdateId. Không cần phải trả lại nó trong truy vấn này.

Câu trả lời:


15

Trình tối ưu hóa SQL Server không thể tạo ra kế hoạch thực hiện mà bạn đang thực hiện với sự đảm bảo mà bạn cần, bởi vì toán tử Hash Match Flow Distinc không bảo toàn thứ tự.

Mặc dù, tôi không chắc chắn (và nghi ngờ là không) nếu điều này thực sự đảm bảo cho việc đặt hàng.

Bạn có thể quan sát bảo quản trật tự trong nhiều trường hợp, nhưng đây là một chi tiết thực hiện; không có gì đảm bảo, vì vậy bạn không thể dựa vào nó. Như mọi khi, thứ tự trình bày chỉ có thể được đảm bảo bởi một ORDER BYđiều khoản cấp cao nhất .

Thí dụ

Kịch bản dưới đây cho thấy Hash Match Flow Distinc không bảo toàn trật tự. Nó thiết lập bảng được đề cập với các số phù hợp 1-50.000 trong cả hai cột:

IF OBJECT_ID(N'dbo.Updates', N'U') IS NOT NULL
    DROP TABLE dbo.Updates;
GO
CREATE TABLE Updates
(
    UpdateId INT NOT NULL IDENTITY(1,1),
    ObjectId INT NOT NULL,

    CONSTRAINT PK_Updates_UpdateId PRIMARY KEY (UpdateId)
);
GO
INSERT dbo.Updates (ObjectId)
SELECT TOP (50000)
    ObjectId =
        ROW_NUMBER() OVER (
            ORDER BY C1.[object_id]) 
FROM sys.columns AS C1
CROSS JOIN sys.columns AS C2
ORDER BY
    ObjectId;

Truy vấn kiểm tra là:

DECLARE @Rows bigint = 50000;

-- Optimized for 1 row, but will be 50,000 when executed
SELECT DISTINCT TOP (@Rows)
    U.ObjectId 
FROM dbo.Updates AS U
WHERE 
    U.UpdateId > 0
OPTION (OPTIMIZE FOR (@Rows = 1));

Kế hoạch ước tính cho thấy một chỉ số tìm kiếm và lưu lượng khác biệt:

Kế hoạch dự kiến

Đầu ra dường như được yêu cầu bắt đầu với:

Bắt đầu kết quả

... nhưng các giá trị tiếp tục đi xuống bắt đầu 'mất tích':

Mô hình phá vỡ

...và cuối cùng:

Sự hỗn loạn nổ ra

Giải thích trong trường hợp cụ thể này là toán tử băm tràn:

Kế hoạch hậu thực hiện

Khi một phân vùng tràn ra, tất cả các hàng băm vào cùng một phân vùng cũng tràn ra. Các phân vùng bị đổ được xử lý sau, phá vỡ kỳ vọng rằng các giá trị riêng biệt gặp phải sẽ được phát ra ngay lập tức trong chuỗi chúng được nhận.


Có nhiều cách để viết một truy vấn hiệu quả để tạo ra kết quả theo thứ tự bạn muốn, chẳng hạn như đệ quy hoặc sử dụng một con trỏ. Tuy nhiên, không thể thực hiện được bằng cách sử dụng Hash Match Flow Distinc .


11

Tôi không hài lòng với câu trả lời này vì tôi không thể quản lý để có được một toán tử phân biệt dòng chảy cùng với các kết quả được đảm bảo là chính xác. Tuy nhiên, tôi có một giải pháp thay thế sẽ có hiệu suất tốt cùng với kết quả chính xác. Thật không may, nó yêu cầu một chỉ mục không bao gồm được tạo trên bảng.

Tôi đã tiếp cận vấn đề này bằng cách cố gắng nghĩ ra sự kết hợp của các cột mà tôi có thể ORDER BYvà có được kết quả chính xác sau khi áp dụng DISTINCTchúng. Giá trị tối thiểu của UpdateIdmỗi ObjectIdcùng với ObjectIdlà một kết hợp như vậy. Tuy nhiên, trực tiếp yêu cầu tối thiểu UpdateIddường như dẫn đến việc đọc tất cả các hàng từ bảng. Thay vào đó, chúng ta có thể gián tiếp yêu cầu giá trị tối thiểu của UpdateIdviệc tham gia vào bảng khác. Ý tưởng là quét Updatesbảng theo thứ tự, loại bỏ bất kỳ hàng nào UpdateIdkhông phải là giá trị tối thiểu cho hàng đó ObjectIdvà giữ 100 hàng đầu tiên. Dựa trên mô tả của bạn về phân phối dữ liệu, chúng tôi không cần phải ném ra rất nhiều hàng.

Để chuẩn bị dữ liệu, tôi đặt 1 triệu hàng vào một bảng có 2 hàng cho mỗi ObjectId riêng biệt:

INSERT INTO Updates WITH (TABLOCK)
SELECT t.RN / 2
FROM 
(
    SELECT TOP 1000000 -1 + ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
    FROM master..spt_values t1
    CROSS JOIN master..spt_values t2
) t;

CREATE INDEX IX On Updates (Objectid, UpdateId);

Các chỉ số không bao gồm trên ObjectidUpdateIdlà quan trọng. Nó cho phép chúng tôi loại bỏ các hàng không có mức tối thiểu UpdateIdmột cách hiệu quả Objectid. Có nhiều cách để viết một truy vấn phù hợp với mô tả ở trên. Đây là một cách sử dụng NOT EXISTS:

DECLARE @fromUpdateId INT = 9999;
SELECT ObjectId
FROM (
    SELECT DISTINCT TOP 100 u1.UpdateId, u1.ObjectId
    FROM Updates u1
    WHERE UpdateId > @fromUpdateId
    AND NOT EXISTS (
        SELECT 1
        FROM Updates u2
        WHERE u2.UpdateId > @fromUpdateId
        AND u1.ObjectId = u2.ObjectId
        AND u2.UpdateId < u1.UpdateId
    )
    ORDER BY u1.UpdateId, u1.ObjectId
) t;

Dưới đây là hình ảnh của kế hoạch truy vấn :

kế hoạch truy vấn

Trong trường hợp tốt nhất, SQL Server sẽ chỉ thực hiện 100 chỉ mục tìm kiếm so với chỉ mục không được bao gồm. Để mô phỏng việc rất không may mắn, tôi đã thay đổi truy vấn để trả 5000 hàng đầu tiên cho máy khách. Điều đó dẫn đến chỉ số 9999 tìm kiếm, vì vậy, nó giống như nhận được trung bình 100 hàng trên mỗi khác biệt ObjectId. Đây là đầu ra từ SET STATISTICS IO, TIME ON:

Bảng 'Cập nhật'. Số lượng quét 10000, đọc logic 31900, đọc vật lý 0

Thời gian thực thi máy chủ SQL: Thời gian CPU = 31 ms, thời gian trôi qua = 42 ms.


9

Tôi thích câu hỏi - Flow Distinc là một trong những toán tử yêu thích của tôi.

Bây giờ, đảm bảo là vấn đề. Khi bạn nghĩ về toán tử FD kéo các hàng từ toán tử Seek theo thứ tự, tạo ra mỗi hàng vì nó xác định nó là duy nhất, điều này sẽ cung cấp cho bạn các hàng theo đúng thứ tự. Nhưng thật khó để biết liệu có thể có một số tình huống trong đó FD không xử lý một hàng duy nhất tại một thời điểm hay không.

Về mặt lý thuyết, FD có thể yêu cầu 100 hàng từ Seek và sản xuất chúng theo bất kỳ thứ tự nào mà chúng cần.

Các gợi ý truy vấn OPTION (FAST 1, MAXDOP 1)có thể giúp ích, bởi vì nó sẽ tránh nhận được nhiều hàng hơn mức cần thiết từ toán tử Seek. Nó có phải là một sự đảm bảo không? Không hẳn. Nó vẫn có thể quyết định kéo một trang hàng tại một thời điểm, hoặc một cái gì đó tương tự.

Tôi nghĩ rằng OPTION (FAST 1, MAXDOP 1), OFFSETphiên bản của bạn sẽ mang lại cho bạn rất nhiều sự tự tin về đơn hàng, nhưng nó không phải là một sự đảm bảo.


Như tôi đã hiểu, vấn đề là toán tử Flow Distinc sử dụng bảng băm có thể tràn vào đĩa. Khi có sự cố tràn, các hàng có thể được xử lý bằng phần vẫn còn trong RAM sẽ được xử lý ngay lập tức, nhưng các hàng khác không được xử lý cho đến khi dữ liệu bị tràn được đọc lại từ đĩa. Từ những gì tôi có thể nói, bất kỳ nhà khai thác nào sử dụng bảng băm (chẳng hạn như Hash Tham gia) không được đảm bảo để duy trì trật tự do hành vi tràn của nó.
sam.bishop

Chính xác. Xem câu trả lời của Paul White.
Rob Farley
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.