Database
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Database

Lỗi ước tính số lượng truy vấn con

Hãy xem xét truy vấn AdventureWorks sau đây trả về các ID giao dịch trong bảng lịch sử cho ID sản phẩm 421:

SELECT TH.TransactionID
FROM Production.TransactionHistory AS TH
WHERE TH.ProductID = 421;

Trình tối ưu hóa truy vấn nhanh chóng tìm ra một kế hoạch thực thi hiệu quả với ước tính số lượng (số hàng) chính xác, như được hiển thị trong SQL Sentry Plan Explorer:

Bây giờ giả sử chúng tôi muốn tìm ID giao dịch lịch sử cho sản phẩm AdventureWorks có tên "Metal Plate 2". Có nhiều cách để diễn đạt truy vấn này trong T-SQL. Một công thức tự nhiên là:

SELECT TH.TransactionID
FROM Production.TransactionHistory AS TH
WHERE TH.ProductID = 
(
    SELECT P.ProductID 
    FROM Production.Product AS P
    WHERE P.Name = N'Metal Plate 2'
);

Kế hoạch thực hiện như sau:

Chiến lược là:

  1. Tra cứu ID sản phẩm trong bảng Sản phẩm từ tên đã cho
  2. Định vị các hàng cho ID sản phẩm đó trong bảng Lịch sử

Số hàng ước tính cho bước 1 là chính xác vì chỉ mục được sử dụng được khai báo là duy nhất và được khóa riêng trên tên sản phẩm. Do đó, thử nghiệm bình đẳng trên "Metal Plate 2" được đảm bảo trả về chính xác một hàng (hoặc 0 hàng nếu chúng tôi chỉ định tên sản phẩm không tồn tại).

Ước tính 257 hàng được đánh dấu cho bước hai kém chính xác hơn:chỉ có 13 hàng thực sự gặp phải. Sự khác biệt này phát sinh do trình tối ưu hóa không biết ID sản phẩm cụ thể nào được liên kết với sản phẩm có tên "Metal Plate 2". Nó coi giá trị là không xác định, tạo ra ước tính về số lượng bằng cách sử dụng thông tin mật độ trung bình. Tính toán sử dụng các phần tử từ đối tượng thống kê được hiển thị bên dưới:

DBCC SHOW_STATISTICS 
(
    'Production.TransactionHistory', 
    'IX_TransactionHistory_ProductID'
)
WITH STAT_HEADER, DENSITY_VECTOR;

Thống kê cho thấy bảng chứa 113443 hàng với 441 ID sản phẩm duy nhất (1 / 0,002267574 =441). Giả sử việc phân phối các hàng trên các ID sản phẩm là đồng nhất, ước tính số lượng trung bình mong đợi một ID sản phẩm phù hợp (113443/441) =257,24 hàng. Hóa ra, sự phân bố không đặc biệt đồng đều; chỉ có 13 hàng cho sản phẩm "Tấm kim loại 2".

Một bên

Bạn có thể nghĩ rằng ước tính 257 hàng sẽ chính xác hơn. Ví dụ:do ID sản phẩm và tên đều bị hạn chế là duy nhất, SQL Server có thể tự động duy trì thông tin về mối quan hệ 1-1 này. Sau đó, nó sẽ biết rằng "Metal Plate 2" được liên kết với ID sản phẩm 479 và sử dụng thông tin chi tiết đó để tạo ra ước tính chính xác hơn bằng cách sử dụng biểu đồ ProductID:

DBCC SHOW_STATISTICS 
(
    'Production.TransactionHistory', 
    'IX_TransactionHistory_ProductID'
)
WITH HISTOGRAM;

Ước tính 13 hàng được suy ra theo cách này sẽ chính xác đúng. Tuy nhiên, ước tính 257 hàng không phải là một điều không hợp lý, dựa trên thông tin thống kê có sẵn và các giả định đơn giản hóa thông thường (như phân phối đồng đều) được áp dụng bởi ước lượng số lượng ngày nay. Ước tính chính xác luôn tốt, nhưng ước tính "hợp lý" cũng hoàn toàn có thể chấp nhận được.

Kết hợp hai truy vấn

Giả sử bây giờ chúng tôi muốn xem tất cả các ID lịch sử giao dịch trong đó ID sản phẩm là 421 HOẶC tên của sản phẩm là "Metal Plate 2". Một cách tự nhiên để kết hợp hai truy vấn trước là:

SELECT TH.TransactionID
FROM Production.TransactionHistory AS TH
WHERE TH.ProductID = 421
OR TH.ProductID =
(
    SELECT P.ProductID 
    FROM Production.Product AS P
    WHERE P.Name = N'Metal Plate 2'
);

Kế hoạch thực thi bây giờ phức tạp hơn một chút, nhưng nó vẫn chứa các yếu tố dễ nhận biết của kế hoạch một vị từ:

Chiến lược là:

  1. Tìm bản ghi lịch sử cho sản phẩm 421
  2. Tra cứu id sản phẩm cho sản phẩm có tên "Metal Plate 2"
  3. Tìm bản ghi lịch sử cho id sản phẩm được tìm thấy ở bước 2
  4. Kết hợp các hàng từ bước 1 &3
  5. Xóa mọi bản sao (vì sản phẩm 421 cũng có thể là sản phẩm có tên "Metal Plate 2")

Bước 1 đến bước 3 hoàn toàn giống như bước trước. Các ước tính giống nhau được tạo ra vì những lý do giống nhau. Bước 4 là bước mới, nhưng rất đơn giản:nó nối 19 hàng dự kiến ​​với 257 hàng dự kiến, để đưa ra ước tính là 276 hàng.

Bước 5 là một trong những điều thú vị. Tổng hợp luồng loại bỏ trùng lặp có đầu vào ước tính là 276 hàng và đầu ra ước tính là 113443 hàng. Một tổng hợp xuất ra nhiều hàng hơn số hàng mà nó nhận được dường như là không thể, phải không?

* Bạn sẽ thấy ước tính 102099 hàng tại đây nếu bạn đang sử dụng mô hình ước tính số lượng trước năm 2014.

Lỗi ước tính bản số

Ước tính Tổng hợp luồng không thể thực hiện được trong ví dụ của chúng tôi là do lỗi trong ước tính bản số. Đó là một ví dụ thú vị, vì vậy chúng ta sẽ khám phá nó một cách chi tiết.

Xóa truy vấn con

Bạn có thể ngạc nhiên khi biết rằng trình tối ưu hóa truy vấn SQL Server không hoạt động trực tiếp với các truy vấn con. Chúng bị loại bỏ khỏi cây truy vấn logic sớm trong quá trình biên dịch và được thay thế bằng một cấu trúc tương đương mà trình tối ưu hóa được thiết lập để làm việc và lý luận về nó. Trình tối ưu hóa có một số quy tắc loại bỏ các truy vấn phụ. Chúng có thể được liệt kê theo tên bằng cách sử dụng truy vấn sau (DMV được tham chiếu ít được ghi lại bằng tài liệu, nhưng không được hỗ trợ):

SELECT name 
FROM sys.dm_exec_query_transformation_stats
WHERE name LIKE 'RemoveSubq%';

Kết quả (trên SQL Server 2014):

Truy vấn kiểm tra kết hợp có hai vị từ ("lựa chọn" trong thuật ngữ quan hệ) trên bảng lịch sử, được kết nối bằng OR . Một trong những vị từ này bao gồm một truy vấn con. Toàn bộ cây con (cả vị từ và truy vấn con) được chuyển đổi theo quy tắc đầu tiên trong danh sách ("loại bỏ truy vấn con khi lựa chọn") thành một phép nối bán phần trên sự kết hợp của các vị từ riêng lẻ. Mặc dù không thể biểu diễn chính xác kết quả của chuyển đổi nội bộ này bằng cách sử dụng cú pháp T-SQL, nhưng nó gần như là:

SELECT TH.TransactionID
FROM Production.TransactionHistory AS TH
WHERE EXISTS
(
    SELECT 1
    WHERE TH.ProductID = 421
 
    UNION ALL
 
    SELECT 1
    FROM Production.Product AS P
    WHERE P.Name = N'Metal Plate 2'
    AND P.ProductID = TH.ProductID
)
OPTION (QUERYRULEOFF ApplyUAtoUniSJ);

Hơi đáng tiếc là xấp xỉ T-SQL của tôi về cây bên trong sau khi loại bỏ truy vấn con có chứa một truy vấn con, nhưng trong ngôn ngữ của bộ xử lý truy vấn thì không (nó là một phép nối bán phần). Nếu bạn muốn xem biểu mẫu nội bộ thô thay vì cố gắng của tôi về một bản T-SQL tương đương, hãy yên tâm rằng điều đó sẽ xuất hiện trong giây lát.

Gợi ý truy vấn không có tài liệu được bao gồm trong T-SQL ở trên là để ngăn chặn sự chuyển đổi tiếp theo cho những người bạn muốn xem logic được chuyển đổi trong biểu mẫu kế hoạch thực thi. Các chú thích bên dưới hiển thị vị trí của hai vị từ sau khi chuyển đổi:

Trực giác đằng sau sự chuyển đổi là một hàng lịch sử đủ điều kiện nếu một trong hai vị từ được thỏa mãn. Bất kể bạn thấy T-SQL gần đúng và hình minh họa kế hoạch thực thi của tôi hữu ích như thế nào, tôi hy vọng ít nhất là rõ ràng một cách hợp lý rằng phần viết lại thể hiện cùng một yêu cầu như truy vấn ban đầu.

Tôi nên nhấn mạnh rằng trình tối ưu hóa không tạo cú pháp T-SQL thay thế theo nghĩa đen hoặc tạo ra các kế hoạch thực thi hoàn chỉnh ở các giai đoạn trung gian. T-SQL và các biểu diễn kế hoạch thực thi ở trên hoàn toàn nhằm mục đích hỗ trợ cho việc hiểu. Nếu bạn quan tâm đến các chi tiết thô, thì phần trình bày nội bộ đã hứa của cây truy vấn đã chuyển đổi (được chỉnh sửa một chút cho rõ ràng / không gian) là:

Lưu ý ước tính số lượng thẻ số áp dụng bán nối được đánh dấu. Nó là 113443 hàng khi sử dụng công cụ ước tính cardinality 2014 (102099 hàng nếu sử dụng CE cũ). Hãy nhớ rằng bảng lịch sử AdventureWorks chứa tổng cộng 113443 hàng, do đó, điều này thể hiện 100% tính chọn lọc (90% đối với CE cũ).

Trước đó, chúng tôi đã thấy rằng việc áp dụng một trong hai vị từ này chỉ dẫn đến một số lượng nhỏ các kết quả phù hợp:19 hàng cho ID sản phẩm 421 và 13 hàng (ước tính 257) cho "Metal Plate 2". Ước tính rằng sự liên kết (OR) trong số hai vị từ sẽ trả về tất cả các hàng trong bảng cơ sở dường như hoàn toàn là bonkers.

Chi tiết lỗi

Các chi tiết về tính toán chọn lọc cho kết nối bán phần chỉ hiển thị trong SQL Server 2014 khi sử dụng công cụ ước tính số lượng mới với cờ theo dõi (không có tài liệu) 2363. Có thể thấy điều gì đó tương tự với Sự kiện mở rộng, nhưng đầu ra cờ theo dõi thuận tiện hơn để sử dụng ở đây. Phần có liên quan của đầu ra được hiển thị bên dưới:

Công cụ ước tính số lượng sử dụng công cụ tính Kết hợp cố định với độ chọn lọc 100%. Do đó, bản số đầu ra ước tính của kết nối bán giống như đầu vào của nó, có nghĩa là tất cả 113443 hàng từ bảng lịch sử được mong đợi đủ điều kiện.

Bản chất chính xác của lỗi là phép tính chọn lọc bán tham gia bỏ sót bất kỳ vị từ nào được định vị ngoài một liên kết tất cả trong cây đầu vào. Trong hình minh họa bên dưới, việc thiếu các vị từ trong phép nối bán tự có nghĩa là mọi hàng sẽ đủ điều kiện; nó bỏ qua tác dụng của các vị từ bên dưới từ nối (liên kết tất cả).

Hành vi này càng đáng ngạc nhiên hơn khi bạn cho rằng tính toán chọn lọc đang hoạt động trên biểu diễn cây mà trình tối ưu hóa tự tạo ra (hình dạng của cây và vị trí của các vị từ là kết quả của việc nó xóa truy vấn con).

Một vấn đề tương tự xảy ra với công cụ ước tính thẻ số trước năm 2014, nhưng ước tính cuối cùng được cố định ở 90% đầu vào kết hợp bán ước tính (vì những lý do thú vị liên quan đến ước tính vị từ cố định đảo ngược 10%, điều này quá khó để có được thành).

Ví dụ

Như đã đề cập ở trên, lỗi này xuất hiện khi ước lượng được thực hiện cho một phép nối bán với các vị từ liên quan được đặt bên ngoài một liên kết tất cả. Việc sắp xếp nội bộ này có xảy ra trong quá trình tối ưu hóa truy vấn hay không phụ thuộc vào cú pháp T-SQL ban đầu và trình tự chính xác của các hoạt động tối ưu hóa nội bộ. Các ví dụ sau đây cho thấy một số trường hợp lỗi xảy ra và không xảy ra:

Ví dụ 1

Ví dụ đầu tiên này kết hợp một thay đổi nhỏ đối với truy vấn thử nghiệm:

SELECT TH.TransactionID
FROM Production.TransactionHistory AS TH
WHERE TH.ProductID = (SELECT 421) -- The only change
OR TH.ProductID =
(
    SELECT P.ProductID 
    FROM Production.Product AS P
    WHERE P.Name = N'Metal Plate 2'
);

Kế hoạch thực hiện ước tính là:

Ước tính cuối cùng của 403 hàng không phù hợp với ước tính đầu vào của phép nối vòng lặp lồng nhau, nhưng nó vẫn là ước tính hợp lý (theo nghĩa đã thảo luận trước đó). Nếu gặp lỗi, ước tính cuối cùng sẽ là 113443 hàng (hoặc 102099 hàng khi sử dụng mô hình CE trước năm 2014).

Ví dụ 2

Trong trường hợp bạn chuẩn bị chạy ra ngoài và viết lại tất cả các so sánh liên tục của mình dưới dạng truy vấn con tầm thường để tránh lỗi này, hãy xem điều gì sẽ xảy ra nếu chúng ta thực hiện một thay đổi nhỏ khác, lần này thay thế kiểm tra bình đẳng trong vị từ thứ hai bằng IN. Ý nghĩa của truy vấn vẫn không thay đổi:

SELECT TH.TransactionID
FROM Production.TransactionHistory AS TH
WHERE TH.ProductID = (SELECT 421) -- Change 1
OR TH.ProductID IN                -- Change 2
(
    SELECT P.ProductID 
    FROM Production.Product AS P
    WHERE P.Name = N'Metal Plate 2'
);

Lỗi trở lại:

Ví dụ 3

Mặc dù cho đến nay, bài viết này chỉ tập trung vào một vị từ kết hợp chứa một truy vấn con, ví dụ sau đây cho thấy rằng cùng một đặc tả truy vấn được thể hiện bằng cách sử dụng EXISTS và UNION ALL cũng dễ bị tấn công:

SELECT TH.TransactionID
FROM Production.TransactionHistory AS TH
WHERE EXISTS
(
    SELECT 1
    WHERE TH.ProductID = 421
    UNION ALL
    SELECT 1
    FROM Production.Product AS P
    WHERE P.Name = N'Metal Plate 2'
    AND P.ProductID = TH.ProductID
);

Kế hoạch thực hiện:

Ví dụ 4

Dưới đây là hai cách khác để diễn đạt cùng một truy vấn logic trong T-SQL:

SELECT TH.TransactionID 
FROM Production.TransactionHistory AS TH 
WHERE TH.ProductID = 421
UNION
SELECT TH.TransactionID 
FROM Production.TransactionHistory AS TH 
WHERE TH.ProductID = 
(
    SELECT P.ProductID
    FROM Production.Product AS P
    WHERE P.Name = N'Metal Plate 2'
);
 
SELECT TH.TransactionID 
FROM Production.TransactionHistory AS TH 
WHERE TH.ProductID = 421
UNION
SELECT TH.TransactionID 
FROM Production.TransactionHistory AS TH 
JOIN Production.Product AS P
    ON P.ProductID = TH.ProductID
    AND P.Name = N'Metal Plate 2';

Cả hai truy vấn đều không gặp lỗi và cả hai đều tạo ra cùng một kế hoạch thực thi:

Các công thức T-SQL này xảy ra để tạo ra một kế hoạch thực thi với các ước tính hoàn toàn nhất quán (và hợp lý).

Ví dụ 5

Bạn có thể tự hỏi liệu ước tính không chính xác có quan trọng hay không. Trong các trường hợp được trình bày cho đến nay, nó không phải, ít nhất là không trực tiếp. Vấn đề phát sinh khi lỗi xảy ra trong một truy vấn lớn hơn và ước tính không chính xác ảnh hưởng đến các quyết định của trình tối ưu hóa ở nơi khác. Như một ví dụ được mở rộng tối thiểu, hãy xem xét việc trả lại kết quả của truy vấn thử nghiệm của chúng tôi theo thứ tự ngẫu nhiên:

SELECT TH.TransactionID
FROM Production.TransactionHistory AS TH
WHERE TH.ProductID = 421
OR TH.ProductID =
(
    SELECT P.ProductID 
    FROM Production.Product AS P
    WHERE P.Name = N'Metal Plate 2'
)
ORDER BY NEWID(); -- New

Kế hoạch thực hiện cho thấy ước tính không chính xác ảnh hưởng đến hoạt động sau này. Ví dụ:nó là cơ sở để cấp bộ nhớ dành riêng cho loại:

Nếu bạn muốn xem một ví dụ thực tế hơn về tác động tiềm ẩn của lỗi này, hãy xem câu hỏi gần đây của Richard Mansell trên trang web Hỏi &Đáp SQLPerformance.com, câu trả lời.SQLPerformance.com.

Tóm tắt và Kết luận

Lỗi này được kích hoạt khi trình tối ưu hóa thực hiện ước tính bản số cho một kết nối bán phần, trong các trường hợp cụ thể. Đó là một lỗi khó phát hiện và khắc phục vì một số lý do:

  • Không có cú pháp T-SQL rõ ràng để chỉ định một kết nối bán phần, vì vậy khó có thể biết trước một truy vấn cụ thể có dễ bị lỗi này hay không.
  • Trình tối ưu hóa có thể giới thiệu một nửa tham gia trong nhiều trường hợp khác nhau, không phải tất cả đều là những ứng cử viên bán tham gia rõ ràng.
  • Bán kết có vấn đề thường được chuyển đổi thành một thứ khác bởi hoạt động của trình tối ưu hóa sau này, vì vậy chúng tôi thậm chí không thể dựa vào việc có hoạt động bán kết hợp trong kế hoạch thực thi cuối cùng.
  • Không phải mọi ước tính về số lượng có vẻ kỳ lạ đều do lỗi này gây ra. Thật vậy, nhiều ví dụ về loại này là một tác dụng phụ được mong đợi và vô hại của hoạt động trình tối ưu hóa bình thường.
  • Ước tính độ chọn lọc bán kết hợp sai sẽ luôn là 90% hoặc 100% đầu vào của nó, nhưng điều này thường sẽ không tương ứng với bản số của bảng được sử dụng trong kế hoạch. Hơn nữa, bản số đầu vào kết nối bán được sử dụng trong tính toán thậm chí có thể không hiển thị trong kế hoạch thực thi cuối cùng.
  • Thường có nhiều cách để diễn đạt cùng một truy vấn logic trong T-SQL. Một số trong số này sẽ kích hoạt lỗi, trong khi những người khác thì không.

Những cân nhắc này khiến việc đưa ra lời khuyên thiết thực để phát hiện hoặc khắc phục lỗi này trở nên khó khăn. Việc kiểm tra các kế hoạch thực thi đối với các ước tính "quá mức" và điều tra các truy vấn có hiệu suất kém hơn nhiều so với dự kiến ​​là điều đáng giá, nhưng cả hai đều có thể có nguyên nhân không liên quan đến lỗi này. Điều đó nói rằng, cần đặc biệt kiểm tra các truy vấn bao gồm một tổ hợp các vị từ và một truy vấn con. Như các ví dụ trong bài viết này cho thấy, đây không phải là cách duy nhất để gặp phải lỗi, nhưng tôi hy vọng nó là một cách phổ biến.

Nếu bạn đủ may mắn để chạy SQL Server 2014, với công cụ ước tính bản số mới được bật, bạn có thể xác nhận lỗi bằng cách kiểm tra thủ công đầu ra cờ theo dõi 2363 để có ước tính chọn lọc 100% cố định trên một kết nối bán phần, nhưng đây là hầu như không thuận tiện. Đương nhiên, bạn sẽ không muốn sử dụng cờ theo dõi không có giấy tờ trên hệ thống sản xuất.

Bạn có thể tìm thấy báo cáo lỗi của User Voice cho sự cố này tại đây. Vui lòng bỏ phiếu và bình luận nếu bạn muốn vấn đề này được điều tra (và có thể được khắc phục).


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Viên ngọc T-SQL bị thừa

  2. ODBC 4.0

  3. Làm thế nào để tính toán tổng số chạy trong Redshift

  4. Salesforce SOQL từ Microsoft Office

  5. Rủi ro khi sử dụng bộ nhớ động trong Hyper-V