Tại sao một truy vấn con làm giảm ước tính hàng xuống 1?


26

Hãy xem xét các truy vấn đơn giản nhưng có sẵn sau đây:

SELECT 
  ID
, CASE
    WHEN ID <> 0 
    THEN (SELECT TOP 1 ID FROM X_OTHER_TABLE) 
    ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE_2) 
  END AS ID2
FROM X_HEAP;

Tôi hy vọng ước tính hàng cuối cùng cho truy vấn này bằng với số lượng hàng trong X_HEAPbảng. Bất cứ điều gì tôi đang làm trong truy vấn con không nên quan trọng đối với ước tính hàng vì nó không thể lọc ra bất kỳ hàng nào. Tuy nhiên, trên SQL Server 2016 tôi thấy ước tính hàng giảm xuống 1 vì truy vấn con:

truy vấn xấu

Lý do tại sao điều này xảy ra? Tôi có thể làm gì với nó?

Rất dễ dàng để tái tạo vấn đề này với cú pháp đúng. Đây là một tập hợp các định nghĩa bảng sẽ làm điều đó:

CREATE TABLE dbo.X_HEAP (ID INT NOT NULL)
CREATE TABLE dbo.X_OTHER_TABLE (ID INT NOT NULL);
CREATE TABLE dbo.X_OTHER_TABLE_2 (ID INT NOT NULL);

INSERT INTO dbo.X_HEAP WITH (TABLOCK)
SELECT TOP (1000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM master..spt_values;

CREATE STATISTICS X_HEAP__ID ON X_HEAP (ID) WITH FULLSCAN;

liên kết fiddle db .

Câu trả lời:


22

Dự toán cardinality (CE) này xuất hiện khi:

  1. Tham gia là một tham gia bên ngoài với một vị từ thông qua
  2. Độ chọn lọc của vị từ đi qua được ước tính là chính xác 1 .

Lưu ý: Máy tính cụ thể được sử dụng để xác định độ chọn lọc không quan trọng.


Chi tiết

CE tính toán độ chọn lọc của phép nối ngoài là tổng của:

  • Tính chọn lọc nối bên trong với cùng một vị từ
  • Tính chọn lọc chống tham gia với cùng một vị từ

Sự khác biệt duy nhất giữa phép nối ngoài và phép nối bên trong là phép nối ngoài cũng trả về các hàng không khớp với vị từ nối. Việc chống tham gia cung cấp chính xác sự khác biệt này. Ước tính cardinality cho bên trong và chống tham gia dễ dàng hơn so với tham gia bên ngoài trực tiếp.

Quá trình ước tính chọn lọc tham gia rất đơn giản:

  • Đầu tiên, tính chọn lọc của vị từ truyền qua được đánh giá. SPT
    • Điều này được thực hiện bằng cách sử dụng máy tính nào phù hợp với hoàn cảnh.
    • Vị ngữ là toàn bộ, bao gồm bất kỳ IsFalseOrNullthành phần phủ định .
  • Độ chọn lọc tham gia bên trong: = 1 - SPT
  • Chống chọn lọc tham gia: = SPT

Chống tham gia đại diện cho các hàng sẽ 'đi qua' tham gia. Tham gia bên trong đại diện cho các hàng sẽ không 'đi qua'. Lưu ý rằng 'đi qua' có nghĩa là các hàng chảy qua liên kết mà không chạy bên trong. Để nhấn mạnh: tất cả các hàng sẽ được trả về bởi phép nối, sự khác biệt là giữa các hàng chạy phía bên trong của phép nối trước khi nổi lên và các hàng không.

Rõ ràng, việc thêm vào phải luôn luôn có tổng độ chọn lọc là 1, nghĩa là tất cả các hàng được trả về bởi phép nối, như mong đợi.1 - SPTSPT

Thật vậy, phép tính trên hoạt động chính xác như được mô tả cho tất cả các giá trị ngoại trừ 1 .SPT

Khi = 1, cả hai lựa chọn tham gia bên trong và chống tham gia được ước tính bằng 0, dẫn đến ước tính số lượng thẻ (cho toàn bộ liên kết) của một hàng. Theo như tôi có thể nói, điều này là vô ý, và nên được báo cáo là một lỗi.SPT


Một vấn đề liên quan

Lỗi này có nhiều khả năng biểu hiện hơn người ta có thể nghĩ, do giới hạn CE riêng biệt. Điều này phát sinh khi CASEbiểu thức sử dụng một EXISTSmệnh đề (như là phổ biến). Ví dụ: truy vấn được sửa đổi sau đây từ câu hỏi không gặp phải ước tính số lượng thẻ không mong muốn:

-- This is fine
SELECT 
    CASE
        WHEN XH.ID = 1
        THEN (SELECT TOP (1) XOT.ID FROM dbo.X_OTHER_TABLE AS XOT) 
    END
FROM dbo.X_HEAP AS XH;

Giới thiệu một tầm thường EXISTSkhông gây ra vấn đề bề mặt:

-- This is not fine
SELECT 
    CASE
        WHEN EXISTS (SELECT 1 WHERE XH.ID = 1)
        THEN (SELECT TOP (1) XOT.ID FROM dbo.X_OTHER_TABLE AS XOT) 
    END
FROM dbo.X_HEAP AS XH;

Sử dụng EXISTSgiới thiệu một nửa tham gia (được tô sáng) cho kế hoạch thực hiện:

Kế hoạch bán tham gia

Ước tính cho bán tham gia là tốt. Vấn đề là CE coi cột thăm dò liên quan là một phép chiếu đơn giản, với độ chọn lọc cố định là 1:

Semijoin with probe column treated as a Project.

Selectivity of probe column = 1

Điều này tự động đáp ứng một trong các điều kiện cần thiết cho vấn đề CE này để hiển thị, bất kể nội dung của EXISTSđiều khoản.


Để biết thông tin cơ bản quan trọng, hãy xem Truy vấn con trong CASEBiểu thức của Craig Freedman.


22

Điều này chắc chắn có vẻ như hành vi ngoài ý muốn. Đúng là ước tính cardinality không cần nhất quán ở mỗi bước của kế hoạch nhưng đây là kế hoạch truy vấn tương đối đơn giản và ước tính cardinality cuối cùng không phù hợp với những gì truy vấn đang thực hiện. Ước tính số lượng thẻ thấp như vậy có thể dẫn đến các lựa chọn kém cho các loại tham gia và phương thức truy cập cho các bảng khác ở hạ lưu trong một kế hoạch phức tạp hơn.

Thông qua thử nghiệm và lỗi, chúng tôi có thể đưa ra một vài truy vấn tương tự mà vấn đề không xuất hiện:

SELECT 
  ID
, CASE
    WHEN ID <> 0 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE) 
    ELSE (SELECT -1) 
  END AS ID2
FROM dbo.X_HEAP;

SELECT 
  ID
, CASE
    WHEN ID < 500 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE) 
    WHEN ID >= 500 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2) 
  END AS ID2
FROM dbo.X_HEAP;

Chúng tôi cũng có thể đưa ra nhiều truy vấn hơn mà vấn đề xuất hiện:

SELECT 
  ID
, CASE
    WHEN ID < 500 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE) 
    WHEN ID >= 500 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2) 
    ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE) 
  END AS ID2
FROM dbo.X_HEAP;

SELECT 
  ID
, CASE
    WHEN ID = 0 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE) 
    ELSE (SELECT -1) 
  END AS ID2
FROM dbo.X_HEAP;

SELECT 
  ID
, CASE
    WHEN ID = 0 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE) 
    ELSE (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2) 
  END AS ID2
FROM dbo.X_HEAP;

Dường như có một mẫu: nếu có một biểu thức bên trong CASEdự kiến ​​sẽ không được thực thi và biểu thức kết quả là một truy vấn con đối với bảng thì ước tính hàng giảm xuống 1 sau biểu thức đó.

Nếu tôi viết truy vấn đối với một bảng có chỉ mục được nhóm, các quy tắc sẽ thay đổi phần nào. Chúng tôi có thể sử dụng cùng một dữ liệu:

CREATE TABLE dbo.X_CI (ID INT NOT NULL, PRIMARY KEY (ID))

INSERT INTO dbo.X_CI WITH (TABLOCK)
SELECT * FROM dbo.X_HEAP;

UPDATE STATISTICS X_CI WITH FULLSCAN;

Truy vấn này có ước tính 1000 hàng cuối cùng:

SELECT 
  ID
, CASE
    WHEN ID = 0 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2) 
    ELSE (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE) 
  END
FROM dbo.X_CI;

Nhưng truy vấn này có ước tính 1 hàng cuối cùng:

SELECT 
  ID
, CASE
    WHEN ID <> 0 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE) 
    ELSE (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2) 
  END
FROM dbo.X_CI;

Để tìm hiểu sâu hơn về vấn đề này, chúng ta có thể sử dụng cờ theo dõi không có giấy tờ 2363 để lấy thông tin về cách trình tối ưu hóa truy vấn thực hiện các phép tính chọn lọc. Tôi thấy hữu ích khi ghép cờ theo dõi đó với cờ theo dõi không có giấy tờ 8606 . TF 2363 dường như đưa ra các tính toán chọn lọc cho cả cây được đơn giản hóa và cây sau khi chuẩn hóa dự án. Có cả hai cờ theo dõi được kích hoạt làm cho nó rõ ràng tính toán nào áp dụng cho cây nào.

Hãy thử truy vấn ban đầu được đăng trong câu hỏi:

SELECT 
  ID
, CASE
    WHEN ID <> 0 
    THEN (SELECT TOP 1 ID FROM X_OTHER_TABLE) 
    ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE_2) 
  END AS ID2
FROM X_HEAP
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);

Đây là một phần của đầu ra mà tôi nghĩ là có liên quan cùng với một số ý kiến:

Plan for computation:

  CSelCalcColumnInInterval -- this is the type of calculator used

      Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID -- this is the column used for the calculation

Pass-through selectivity: 0 -- all rows are expected to have a true value for the case expression

Stats collection generated: 

  CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter) -- the row estimate after the join will still be 1000

      CStCollBaseTable(ID=1, CARD=1000 TBL: X_HEAP)

      CStCollBaseTable(ID=2, CARD=1 TBL: X_OTHER_TABLE)

...

Plan for computation:

  CSelCalcColumnInInterval

      Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID

Pass-through selectivity: 1 -- no rows are expected to have a true value for the case expression

Stats collection generated: 

  CStCollOuterJoin(ID=9, CARD=1 x_jtLeftOuter) -- the row estimate after the join will still be 1

      CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter) -- here is the row estimate after the previous join

          CStCollBaseTable(ID=1, CARD=1000 TBL: X_HEAP)

          CStCollBaseTable(ID=2, CARD=1 TBL: X_OTHER_TABLE)

      CStCollBaseTable(ID=3, CARD=1 TBL: X_OTHER_TABLE_2)

Bây giờ chúng ta hãy thử nó cho một truy vấn tương tự không có vấn đề. Tôi sẽ sử dụng cái này:

SELECT 
  ID
, CASE
    WHEN ID <> 0 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE) 
    ELSE (SELECT -1) 
  END AS ID2
FROM dbo.X_HEAP
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);

Đầu ra gỡ lỗi ở cuối:

Plan for computation:

  CSelCalcColumnInInterval

      Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID

Pass-through selectivity: 1

Stats collection generated: 

  CStCollOuterJoin(ID=9, CARD=1000 x_jtLeftOuter)

      CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter)

          CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_HEAP)

          CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE)

      CStCollConstTable(ID=4, CARD=1) -- this is different than before because we select a constant instead of from a table

Hãy thử một truy vấn khác có ước tính hàng xấu:

SELECT 
  ID
, CASE
    WHEN ID < 500 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE) 
    WHEN ID >= 500 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2) 
    ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE) 
  END AS ID2
FROM dbo.X_HEAP
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);

Cuối cùng, ước tính cardinality giảm xuống 1 hàng, một lần nữa sau khi chọn lọc Pass-through = 1. Ước tính cardinality được bảo toàn sau độ chọn lọc là 0,501 và 0,499.

Plan for computation:

 CSelCalcColumnInInterval

      Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID

Pass-through selectivity: 0.501

...

Plan for computation:

  CSelCalcColumnInInterval

      Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID

Pass-through selectivity: 0.499

...

Plan for computation:

  CSelCalcColumnInInterval

      Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID

Pass-through selectivity: 1

Stats collection generated: 

  CStCollOuterJoin(ID=12, CARD=1 x_jtLeftOuter) -- this is associated with the ELSE expression

      CStCollOuterJoin(ID=11, CARD=1000 x_jtLeftOuter)

          CStCollOuterJoin(ID=10, CARD=1000 x_jtLeftOuter)

              CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_HEAP)

              CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE)

          CStCollBaseTable(ID=3, CARD=1 TBL: dbo.X_OTHER_TABLE_2)

      CStCollBaseTable(ID=4, CARD=1 TBL: X_OTHER_TABLE)

Chúng ta lại chuyển sang một truy vấn tương tự khác không có vấn đề. Tôi sẽ sử dụng cái này:

SELECT 
  ID
, CASE
    WHEN ID < 500 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE) 
    WHEN ID >= 500 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2) 
  END AS ID2
FROM dbo.X_HEAP
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);

Trong đầu ra gỡ lỗi, không bao giờ có bước nào có độ chọn lọc vượt qua là 1. Ước tính cardinality duy trì ở 1000 hàng.

Plan for computation:

  CSelCalcColumnInInterval

      Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID

Pass-through selectivity: 0.499

Stats collection generated: 

  CStCollOuterJoin(ID=9, CARD=1000 x_jtLeftOuter)

      CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter)

          CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_HEAP)

          CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE)

      CStCollBaseTable(ID=3, CARD=1 TBL: dbo.X_OTHER_TABLE_2)

End selectivity computation

Điều gì về truy vấn khi nó liên quan đến một bảng có chỉ mục được nhóm? Xem xét các truy vấn sau đây với vấn đề ước tính hàng:

SELECT 
  ID
, CASE
    WHEN ID <> 0 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE) 
    ELSE (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2) 
  END
FROM dbo.X_CI
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);

Kết thúc của đầu ra gỡ lỗi tương tự như những gì chúng ta đã thấy:

Plan for computation:

  CSelCalcColumnInInterval

      Column: QCOL: [SE_DB].[dbo].[X_CI].ID

Pass-through selectivity: 1

Stats collection generated: 

  CStCollOuterJoin(ID=9, CARD=1 x_jtLeftOuter)

      CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter)

          CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_CI)

          CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE)

      CStCollBaseTable(ID=3, CARD=1 TBL: dbo.X_OTHER_TABLE_2)

Tuy nhiên, truy vấn đối với CI mà không có vấn đề có đầu ra khác nhau. Sử dụng truy vấn này:

SELECT 
  ID
, CASE
    WHEN ID = 0 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2) 
    ELSE (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE) 
  END
FROM dbo.X_CI
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);

Kết quả trong các máy tính khác nhau đang được sử dụng. CSelCalcColumnInIntervalkhông còn xuất hiện:

Plan for computation:

  CSelCalcFixedFilter (0.559)

Pass-through selectivity: 0.559

Stats collection generated: 

  CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter)

      CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_CI)

      CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE_2)

...

Plan for computation:

  CSelCalcUniqueKeyFilter

Pass-through selectivity: 0.001

Stats collection generated: 

  CStCollOuterJoin(ID=9, CARD=1000 x_jtLeftOuter)

      CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter)

          CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_CI)

          CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE_2)

      CStCollBaseTable(ID=3, CARD=1 TBL: dbo.X_OTHER_TABLE)

Để kết luận, chúng tôi dường như nhận được một ước tính hàng xấu sau khi truy vấn con theo các điều kiện sau:

  1. Máy CSelCalcColumnInIntervaltính chọn lọc được sử dụng. Tôi không biết chính xác khi nào nó được sử dụng nhưng nó dường như xuất hiện thường xuyên hơn khi bảng cơ sở là một đống.

  2. Độ chọn lọc chuyển qua = 1. Nói cách khác, một trong các CASEbiểu thức được dự kiến ​​sẽ được ước tính thành sai cho tất cả các hàng. Không thành vấn đề nếu CASEbiểu thức đầu tiên ước tính là đúng cho tất cả các hàng.

  3. Có một tham gia bên ngoài để CStCollBaseTable. Nói cách khác, CASEbiểu thức kết quả là một truy vấn con đối với bảng. Một giá trị không đổi sẽ không hoạt động.

Có lẽ trong các điều kiện đó, trình tối ưu hóa truy vấn vô tình áp dụng tính chọn lọc chuyển qua cho ước tính hàng của bảng bên ngoài thay vì cho công việc được thực hiện ở phần bên trong của vòng lặp lồng nhau. Điều đó sẽ làm giảm ước tính hàng xuống 1.

Tôi đã có thể tìm thấy hai cách giải quyết. Tôi đã không thể tái tạo vấn đề khi sử dụng APPLYthay vì truy vấn con. Đầu ra của cờ theo dõi 2363 rất khác với APPLY. Đây là một cách để viết lại truy vấn ban đầu trong câu hỏi:

SELECT 
  h.ID
, a.ID2
FROM X_HEAP h
OUTER APPLY
(
SELECT CASE
    WHEN ID <> 0 
    THEN (SELECT TOP 1 ID FROM X_OTHER_TABLE) 
    ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE_2) 
  END
) a(ID2);

truy vấn tốt 1

Các di sản CE xuất hiện để tránh vấn đề là tốt.

SELECT 
  ID
, CASE
    WHEN ID <> 0 
    THEN (SELECT TOP 1 ID FROM X_OTHER_TABLE) 
    ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE_2) 
  END AS ID2
FROM X_HEAP
OPTION (USE HINT('FORCE_LEGACY_CARDINALITY_ESTIMATION'));

truy vấn tốt 2

Một mục kết nối đã được gửi cho vấn đề này (với một số chi tiết mà Paul White cung cấp trong câu trả lời của anh ấy).

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.