Chỉ mục trên cột được tính toán liên tục cần tra cứu khóa để lấy các cột trong biểu thức được tính


24

Tôi có một cột được tính toán bền vững trên một bảng được tạo thành các cột được nối đơn giản, ví dụ:

CREATE TABLE dbo.T 
(   
    ID INT IDENTITY(1, 1) NOT NULL CONSTRAINT PK_T_ID PRIMARY KEY,
    A VARCHAR(20) NOT NULL,
    B VARCHAR(20) NOT NULL,
    C VARCHAR(20) NOT NULL,
    D DATE NULL,
    E VARCHAR(20) NULL,
    Comp AS A + '-' + B + '-' + C PERSISTED NOT NULL 
);

Trong trường hợp này Compkhông phải là duy nhất và D là hợp lệ kể từ ngày của mỗi kết hợp A, B, C, do đó tôi sử dụng truy vấn sau để lấy ngày kết thúc cho mỗi kết quả A, B, C(về cơ bản là ngày bắt đầu tiếp theo cho cùng một giá trị Comp):

SELECT  t1.ID,
        t1.Comp,
        t1.D,
        D2 = (  SELECT  TOP 1 t2.D
                FROM    dbo.T t2
                WHERE   t2.Comp = t1.Comp
                AND     t2.D > t1.D
                ORDER BY t2.D
            )
FROM    dbo.T t1
WHERE   t1.D IS NOT NULL -- DON'T CARE ABOUT INACTIVE RECORDS
ORDER BY t1.Comp;

Sau đó tôi đã thêm một chỉ mục vào cột được tính toán để hỗ trợ truy vấn này (và cả các mục khác):

CREATE NONCLUSTERED INDEX IX_T_Comp_D ON dbo.T (Comp, D) WHERE D IS NOT NULL;

Kế hoạch truy vấn tuy nhiên làm tôi ngạc nhiên. Tôi đã nghĩ rằng vì tôi có một mệnh đề where nêu rõ điều đó D IS NOT NULLvà tôi đang sắp xếp theo Comp, và không tham chiếu bất kỳ cột nào ngoài chỉ mục mà chỉ mục trên cột được tính toán có thể được sử dụng để quét t1 và t2, nhưng tôi thấy một chỉ mục được nhóm quét.

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

Vì vậy, tôi buộc phải sử dụng chỉ số này để xem liệu nó có mang lại một kế hoạch tốt hơn không:

SELECT  t1.ID,
        t1.Comp,
        t1.D,
        D2 = (  SELECT  TOP 1 t2.D
                FROM    dbo.T t2
                WHERE   t2.Comp = t1.Comp
                AND     t2.D > t1.D
                ORDER BY t2.D
            )
FROM    dbo.T t1 WITH (INDEX (IX_T_Comp_D))
WHERE   t1.D IS NOT NULL
ORDER BY t1.Comp;

Mà đã cho kế hoạch này

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

Điều này cho thấy một tra cứu khóa đang được sử dụng, các chi tiết đó là:

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

Bây giờ, theo tài liệu SQL-Server:

Bạn có thể tạo một chỉ mục trên một cột được tính toán được xác định bằng biểu thức xác định, nhưng không chính xác, nếu cột được đánh dấu PERSISTED trong câu lệnh CREATE TABLE hoặc ALTER TABLE. Điều này có nghĩa là Công cụ cơ sở dữ liệu lưu trữ các giá trị được tính toán trong bảng và cập nhật chúng khi bất kỳ cột nào khác mà cột được tính phụ thuộc được cập nhật. Công cụ cơ sở dữ liệu sử dụng các giá trị bền vững này khi nó tạo một chỉ mục trên cột và khi chỉ mục được tham chiếu trong một truy vấn. Tùy chọn này cho phép bạn tạo một chỉ mục trên một cột được tính toán khi Cơ sở dữ liệu không thể chứng minh chính xác liệu một hàm trả về các biểu thức cột được tính toán, đặc biệt là hàm CLR được tạo trong .NET Framework, có tính xác định và chính xác.

Vì vậy, nếu, như các tài liệu nói "Công cụ cơ sở dữ liệu lưu trữ các giá trị được tính toán trong bảng" và giá trị cũng đang được lưu trữ trong chỉ mục của tôi, tại sao cần phải có Tra cứu khóa để lấy A, B và C khi chúng không được tham chiếu trong truy vấn nào cả? Tôi giả sử chúng đang được sử dụng để tính toán Comp, nhưng tại sao? Ngoài ra, tại sao truy vấn có thể sử dụng chỉ mục trên t2, nhưng không bật t1?

Truy vấn và DDL trên SQL Fiddle

NB Tôi đã gắn thẻ SQL Server 2008 vì đây là phiên bản mà sự cố chính của tôi gặp phải, nhưng tôi cũng có hành vi tương tự vào năm 2012.

Câu trả lời:


20

Tại sao cần phải có Tra cứu khóa để nhận A, B và C khi chúng không được tham chiếu trong truy vấn? Tôi giả sử chúng đang được sử dụng để tính toán Comp, nhưng tại sao?

Các cột A, B, and C được tham chiếu trong kế hoạch truy vấn - chúng được sử dụng bởi tìm kiếm trên T2.

Ngoài ra, tại sao truy vấn có thể sử dụng chỉ mục trên t2, nhưng không phải trên t1?

Trình tối ưu hóa quyết định rằng quét chỉ mục được nhóm rẻ hơn so với quét chỉ mục không được lọc và sau đó thực hiện tra cứu để lấy các giá trị cho các cột A, B và C.

Giải trình

Câu hỏi thực sự là tại sao trình tối ưu hóa cảm thấy cần phải truy xuất A, B và C cho chỉ mục tìm kiếm. Chúng tôi hy vọng nó sẽ đọc Compcột bằng cách quét chỉ mục không bao gồm, và sau đó thực hiện tìm kiếm trên cùng một chỉ mục (bí danh T2) để xác định vị trí bản ghi Top 1.

Trình tối ưu hóa truy vấn mở rộng các tham chiếu cột được tính toán trước khi bắt đầu tối ưu hóa, để cho nó cơ hội đánh giá chi phí của các gói truy vấn khác nhau. Đối với một số truy vấn, việc mở rộng định nghĩa của cột được tính toán cho phép trình tối ưu hóa tìm các kế hoạch hiệu quả hơn.

Khi trình tối ưu hóa gặp một truy vấn con tương quan, nó sẽ cố gắng 'hủy đăng ký nó' thành một hình thức mà nó thấy dễ dàng hơn để lý giải. Nếu nó không thể tìm thấy một sự đơn giản hóa hiệu quả hơn, nó dùng đến việc viết lại truy vấn con tương quan dưới dạng một ứng dụng (một phép nối tương quan):

Áp dụng viết lại

Thực tế là việc áp dụng unrolling này đặt cây truy vấn logic thành một dạng không hoạt động tốt với chuẩn hóa dự án (giai đoạn sau có vẻ khớp các biểu thức chung với các cột được tính toán, trong số những thứ khác).

Trong trường hợp của bạn, cách viết truy vấn tương tác với các chi tiết bên trong của trình tối ưu hóa sao cho định nghĩa biểu thức mở rộng không khớp với cột được tính toán và bạn kết thúc bằng một tìm kiếm tham chiếu các cột A, B, and Cthay vì cột được tính toán , Comp. Đây là nguyên nhân gốc rễ.

Cách giải quyết

Một ý tưởng để khắc phục hiệu ứng phụ này là viết truy vấn dưới dạng áp dụng theo cách thủ công:

SELECT
    T1.ID,
    T1.Comp,
    T1.D,
    CA.D2
FROM dbo.T AS T1
CROSS APPLY
(  
    SELECT TOP (1)
        D2 = T2.D
    FROM dbo.T AS T2
    WHERE
        T2.Comp = T1.Comp
        AND T2.D > T1.D
    ORDER BY
        T2.D ASC
) AS CA
WHERE
    T1.D IS NOT NULL -- DON'T CARE ABOUT INACTIVE RECORDS
ORDER BY
    T1.Comp;

Thật không may, truy vấn này sẽ không sử dụng chỉ mục được lọc như chúng ta hy vọng. Kiểm tra bất bình đẳng trên cột Dbên trong các từ chối áp dụng NULLs, do đó, vị từ dự phòng rõ ràng WHERE T1.D IS NOT NULLđược tối ưu hóa.

Không có biến vị ngữ rõ ràng đó, logic khớp chỉ mục được lọc quyết định nó không thể sử dụng chỉ mục được lọc. Có một số cách để khắc phục hiệu ứng phụ thứ hai này, nhưng cách dễ nhất có lẽ là thay đổi áp dụng chéo cho một ứng dụng bên ngoài (phản ánh logic của việc viết lại trình tối ưu hóa được thực hiện trước đó trong truy vấn con tương quan):

SELECT
    T1.ID,
    T1.Comp,
    T1.D,
    CA.D2
FROM dbo.T AS T1
OUTER APPLY
(  
    SELECT TOP (1)
        D2 = T2.D
    FROM dbo.T AS T2
    WHERE
        T2.Comp = T1.Comp
        AND T2.D > T1.D
    ORDER BY
        T2.D ASC
) AS CA
WHERE
    T1.D IS NOT NULL -- DON'T CARE ABOUT INACTIVE RECORDS
ORDER BY
    T1.Comp;

Bây giờ trình tối ưu hóa không cần sử dụng bản ghi lại ứng dụng (do đó, tính năng khớp cột được tính toán như mong đợi) và vị từ cũng không được tối ưu hóa, do đó, chỉ mục được lọc có thể được sử dụng cho cả hoạt động truy cập dữ liệu và tìm kiếm sử dụng Compcột cả từ hai phía:

Kế hoạch áp dụng bên ngoài

Điều này thường được ưu tiên hơn việc thêm A, B và C làm INCLUDEdcác cột trong chỉ mục được lọc, vì nó giải quyết nguyên nhân gốc rễ của vấn đề và không yêu cầu mở rộng chỉ mục một cách không cần thiết.

Các cột được tính toán bền bỉ

Là một lưu ý phụ, không cần thiết phải đánh dấu cột được tính là PERSISTED, nếu bạn không nhớ lặp lại định nghĩa của nó trong một CHECKràng buộc:

CREATE TABLE dbo.T 
(   
    ID integer IDENTITY(1, 1) NOT NULL,
    A varchar(20) NOT NULL,
    B varchar(20) NOT NULL,
    C varchar(20) NOT NULL,
    D date NULL,
    E varchar(20) NULL,
    Comp AS A + '-' + B + '-' + C,

    CONSTRAINT CK_T_Comp_NotNull
        CHECK (A + '-' + B + '-' + C IS NOT NULL),

    CONSTRAINT PK_T_ID 
        PRIMARY KEY (ID)
);

CREATE NONCLUSTERED INDEX IX_T_Comp_D
ON dbo.T (Comp, D) 
WHERE D IS NOT NULL;

Cột được tính toán chỉ được yêu cầu PERSISTEDtrong trường hợp này nếu bạn muốn sử dụng một NOT NULLràng buộc hoặc tham chiếu Comptrực tiếp cột (thay vì lặp lại định nghĩa của nó) trong một CHECKràng buộc.


2
+1 BTW Tôi đã gặp một trường hợp tra cứu không cần thiết khác trong khi nhìn vào điều này mà bạn có thể (hoặc có thể không) tìm thấy sự quan tâm. Câu đố SQL .
Martin Smith

@MartinSmith Vâng, điều đó thật thú vị. Một quy tắc chung khác viết lại ( FOJNtoLSJNandLASJN) dẫn đến những thứ không hoạt động như chúng ta mong đợi và để lại rác (BaseRow / Checksums) hữu ích trong một số loại kế hoạch (ví dụ: con trỏ) nhưng không cần thiết ở đây.
Paul White nói GoFundMonica

Ah Chklà tổng kiểm tra! Cảm ơn tôi đã không chắc chắn về điều đó. Ban đầu tôi đã nghĩ nó có thể là một cái gì đó để làm với các ràng buộc kiểm tra.
Martin Smith

6

Mặc dù điều này có thể là một chút ngẫu nhiên do tính chất nhân tạo của dữ liệu thử nghiệm của bạn, nhưng như bạn đã đề cập SQL 2012, tôi đã thử viết lại:

SELECT  ID,
        Comp,
        D,
        D2 = LEAD(D) OVER(PARTITION BY COMP ORDER BY D)
FROM    dbo.T 
WHERE   D IS NOT NULL
ORDER BY Comp;

Điều này mang lại một kế hoạch chi phí thấp tốt đẹp bằng cách sử dụng chỉ mục của bạn và với số lần đọc thấp hơn đáng kể so với các tùy chọn khác (và kết quả tương tự cho dữ liệu thử nghiệm của bạn).

Chi phí Plan Explorer cho bốn tùy chọn: Bản gốc;  bản gốc với gợi ý;  áp dụng bên ngoài và chì

Tôi nghi ngờ dữ liệu thực của bạn phức tạp hơn nên có thể có một số tình huống trong đó truy vấn này hành xử khác với ngữ nghĩa của bạn, nhưng đôi khi nó cho thấy các tính năng mới có thể tạo ra sự khác biệt thực sự.

Tôi đã thử nghiệm với một số dữ liệu khác nhau và tìm thấy một số tình huống phù hợp và một số thì không:

--Example 1: results matched
TRUNCATE TABLE dbo.t

-- Generate some more interesting test data
;WITH cte AS
(
SELECT TOP 1000 ROW_NUMBER() OVER ( ORDER BY ( SELECT 1 ) ) rn
FROM master.sys.columns c1
    CROSS JOIN master.sys.columns c2
    CROSS JOIN master.sys.columns c3
)
INSERT T (A, B, C, D)
SELECT  'A' + CAST( a.rn AS VARCHAR(5) ),
        'B' + CAST( a.rn AS VARCHAR(5) ),
        'C' + CAST( a.rn AS VARCHAR(5) ),
        DATEADD(DAY, a.rn + b.rn, '1 Jan 2013')
FROM cte a
    CROSS JOIN cte b
WHERE a.rn % 3 = 0
 AND b.rn % 5 = 0
ORDER BY 1, 2, 3
GO


-- Original query
SELECT  t1.ID,
        t1.Comp,
        t1.D,
        D2 = (  SELECT  TOP 1 D
                FROM    dbo.T t2
                WHERE   t2.Comp = t1.Comp
                AND     t2.D > t1.D
                ORDER BY D
            )
INTO #tmp1
FROM    dbo.T t1 
WHERE   t1.D IS NOT NULL
ORDER BY t1.Comp;
GO

SELECT  ID,
        Comp,
        D,
        D2 = LEAD(D) OVER(PARTITION BY COMP ORDER BY D)
INTO #tmp2
FROM    dbo.T 
WHERE   D IS NOT NULL
ORDER BY Comp;
GO


-- Checks ...
SELECT * FROM #tmp1
EXCEPT
SELECT * FROM #tmp2

SELECT * FROM #tmp2
EXCEPT
SELECT * FROM #tmp1


Example 2: results did not match
TRUNCATE TABLE dbo.t

-- Generate some more interesting test data
;WITH cte AS
(
SELECT TOP 1000 ROW_NUMBER() OVER ( ORDER BY ( SELECT 1 ) ) rn
FROM master.sys.columns c1
    CROSS JOIN master.sys.columns c2
    CROSS JOIN master.sys.columns c3
)
INSERT T (A, B, C, D)
SELECT  'A' + CAST( a.rn AS VARCHAR(5) ),
        'B' + CAST( a.rn AS VARCHAR(5) ),
        'C' + CAST( a.rn AS VARCHAR(5) ),
        DATEADD(DAY, a.rn, '1 Jan 2013')
FROM cte a

-- Add some more data
INSERT dbo.T (A, B, C, D)
SELECT A, B, C, D 
FROM dbo.T
WHERE DAY(D) In ( 3, 7, 9 )


INSERT dbo.T (A, B, C, D)
SELECT A, B, C, DATEADD( day, 1, D )
FROM dbo.T
WHERE DAY(D) In ( 12, 13, 17 )


SELECT * FROM #tmp1
EXCEPT
SELECT * FROM #tmp2

SELECT * FROM #tmp2
EXCEPT
SELECT * FROM #tmp1

SELECT * FROM #tmp2
INTERSECT
SELECT * FROM #tmp1


select * from #tmp1
where comp = 'A2-B2-C2'

select * from #tmp2
where comp = 'A2-B2-C2'

1
Vâng, nó sử dụng chỉ số nhưng chỉ đến một điểm. Nếu compkhông phải là một cột được tính toán, bạn không thấy sắp xếp.
Martin Smith

Cảm ơn. Kịch bản thực tế của tôi không phức tạp hơn nhiều và LEADchức năng hoạt động chính xác như tôi muốn trong phiên bản địa phương năm 2012 của tôi. Thật không may, sự bất tiện nhỏ này đối với tôi không được coi là một lý do đủ tốt để nâng cấp các máy chủ sản xuất ...
GarethD

-1

Khi tôi cố gắng thực hiện các hành động tương tự, a nhận được kết quả khác. Đầu tiên, kế hoạch thực hiện của tôi cho bảng không có chỉ mục trông như sau:nhập mô tả hình ảnh ở đây

Như chúng ta có thể thấy từ Quét chỉ mục cụm (t2), vị từ được sử dụng để xác định các hàng cần thiết được trả về (vì điều kiện):

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

Khi chỉ mục được thêm vào, không phải là vấn đề nếu nó được xác định bởi toán tử CÓ hay không, kế hoạch thực hiện đã trở thành như sau:

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

Như chúng ta có thể thấy, Quét chỉ mục cụm được thay thế bằng Quét chỉ mục. Như chúng ta đã thấy ở trên, SQL Server sử dụng các cột nguồn của cột được tính toán để thực hiện khớp lệnh của truy vấn lồng nhau. Trong quá trình quét chỉ mục cụm, tất cả các giá trị này có thể được lấy cùng một lúc (không cần thêm thao tác nào). Khi chỉ mục được thêm vào, việc lọc các hàng cần thiết từ bảng (trong lựa chọn chính) sẽ thực hiện theo chỉ mục, nhưng các giá trị của các cột nguồn cho cột được tính compvẫn cần phải được nhận (hoạt động cuối cùng Nested Loop) .

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

Do đó, thao tác Tra cứu khóa được sử dụng - để lấy dữ liệu của các cột nguồn của cột được tính toán.

PS Trông giống như một lỗi trong SQL Server.

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.