Tôi biết rằng làm COALESCE
trên một vài cột và tham gia vào chúng không phải là một thực hành tốt.
Tạo ước lượng phân phối và ước lượng phân phối tốt là đủ khó khi lược đồ là 3NF + (với các khóa và ràng buộc) và truy vấn có quan hệ và chủ yếu là SPJG (nhóm lựa chọn-chiếu-tham gia theo nhóm). Mô hình CE được xây dựng trên những nguyên tắc đó. Các tính năng khác thường hơn hoặc không liên quan có trong một truy vấn, người ta càng tiến gần đến ranh giới của những gì mà khung tim và tính chọn lọc có thể xử lý. Đi quá xa và CE sẽ bỏ cuộc và đoán .
Hầu hết các ví dụ MCVE là SPJ đơn giản (không có G), mặc dù với các đẳng thức bên ngoài chủ yếu (được mô hình hóa như là nối bên trong cộng với chống semijoin) chứ không phải là Equijoin bên trong đơn giản hơn (hoặc semijoin). Tất cả các mối quan hệ đều có khóa, mặc dù không có khóa ngoại hoặc các ràng buộc khác. Tất cả trừ một trong những người tham gia là một-nhiều, điều đó là tốt.
Ngoại lệ là sự kết hợp nhiều-nhiều bên ngoài giữa X_DETAIL_1
và X_DETAIL_LINK
. Chức năng duy nhất của phép nối này trong MCVE là có khả năng trùng lặp các hàng trong X_DETAIL_1
. Đây là một loại khác thường của một điều.
Các biến vị ngữ đẳng thức đơn giản (các lựa chọn) và toán tử vô hướng cũng tốt hơn. Ví dụ: thuộc tính so sánh bằng / thuộc tính thường xuyên hoạt động tốt trong mô hình. Nó là tương đối "dễ dàng" để sửa đổi biểu đồ và thống kê tần số để phản ánh việc áp dụng các vị từ đó.
COALESCE
được xây dựng trên CASE
, lần lượt được triển khai trong nội bộ như IIF
(và điều này cũng đúng trước khi IIF
xuất hiện trong ngôn ngữ Transact-SQL). Các mô hình CE IIF
như là một UNION
với hai đứa trẻ loại trừ lẫn nhau, mỗi đứa trẻ bao gồm một dự án về một lựa chọn về mối quan hệ đầu vào. Mỗi thành phần được liệt kê đều có hỗ trợ mô hình, vì vậy việc kết hợp chúng là tương đối đơn giản. Mặc dù vậy, càng nhiều lớp trừu tượng, kết quả cuối cùng càng kém chính xác - một lý do tại sao các kế hoạch thực hiện lớn hơn có xu hướng kém ổn định và đáng tin cậy.
ISNULL
mặt khác, là nội tại của động cơ. Nó không được xây dựng bằng cách sử dụng bất kỳ thành phần cơ bản hơn. Áp dụng hiệu ứng của ISNULL
biểu đồ, ví dụ, đơn giản như thay thế bước cho NULL
các giá trị (và nén khi cần thiết). Nó vẫn còn tương đối mờ, như các toán tử vô hướng đi, và vì vậy tốt nhất nên tránh khi có thể. Tuy nhiên, nói chung là thân thiện với trình tối ưu hóa (ít tối ưu hóa-không thân thiện) hơn làCASE
thay thế dựa trên cơ sở.
CE (70 và 120+) rất phức tạp, thậm chí theo tiêu chuẩn SQL Server. Đây không phải là trường hợp áp dụng logic đơn giản (với công thức bí mật) cho mỗi toán tử. CE biết về các khóa và phụ thuộc chức năng; nó biết cách ước tính bằng cách sử dụng tần số, thống kê đa biến và biểu đồ; và có rất nhiều trường hợp đặc biệt, tinh chỉnh, kiểm tra và cân bằng, và các cấu trúc hỗ trợ. Nó thường ước tính ví dụ tham gia theo nhiều cách (tần số, biểu đồ) và quyết định kết quả hoặc điều chỉnh dựa trên sự khác biệt giữa hai cách.
Một điều cơ bản cuối cùng cần trình bày: Ước tính cardinality ban đầu chạy cho mọi hoạt động trong cây truy vấn, từ dưới lên. Tính chọn lọc và cardinality được lấy từ các toán tử lá trước (quan hệ cơ sở). Biểu đồ đã sửa đổi và thông tin mật độ / tần số được lấy từ các toán tử cha. Cây càng đi xa, chất lượng ước tính càng thấp có xu hướng tích lũy.
Ước tính toàn diện ban đầu này cung cấp một điểm khởi đầu và diễn ra tốt trước khi bất kỳ xem xét nào được đưa ra cho một kế hoạch thực hiện cuối cùng (nó xảy ra một cách trước cả giai đoạn biên soạn kế hoạch tầm thường). Cây truy vấn tại thời điểm này có xu hướng phản ánh hình thức bằng văn bản của truy vấn khá chặt chẽ (mặc dù đã loại bỏ các truy vấn con và đơn giản hóa được áp dụng, v.v.)
Ngay sau khi ước tính ban đầu, SQL Server thực hiện sắp xếp lại tham gia heuristic, nói một cách lỏng lẻo cố gắng sắp xếp lại cây để đặt các bảng nhỏ hơn và tính chọn lọc cao sẽ tham gia trước. Nó cũng cố gắng định vị các liên kết bên trong trước khi tham gia bên ngoài và các sản phẩm chéo. Khả năng của nó không sâu rộng; những nỗ lực của nó không đầy đủ; và nó không xem xét chi phí vật lý (vì chúng chưa tồn tại - chỉ có thông tin thống kê và thông tin siêu dữ liệu). Sắp xếp lại heuristic là thành công nhất trên cây Equijoin đơn giản bên trong. Nó tồn tại để cung cấp một điểm khởi đầu "tốt hơn" để tối ưu hóa dựa trên chi phí.
Tại sao điều này tham gia ước tính cardinality rất lớn?
MCVE có một sự tham gia nhiều-nhiều-nhiều-nhiều-nhiều "bất thường" , và một đẳng thức tham gia với COALESCE
vị ngữ. Cây toán tử cũng có một phép nối bên trong cuối cùng , thứ tự sắp xếp tham gia heuristic không thể di chuyển lên cây đến một vị trí ưa thích hơn. Bỏ qua tất cả các vô hướng và hình chiếu, cây tham gia là:
LogOp_Join [ Card=4.52803e+009 ]
LogOp_LeftOuterJoin [ Card=481577 ]
LogOp_LeftOuterJoin [ Card=481577 ]
LogOp_LeftOuterJoin [ Card=481577 ]
LogOp_LeftOuterJoin [ Card=481577 ]
LogOp_Get TBL: X_DRIVING_TABLE(alias TBL: dt) [ Card=481577 ]
LogOp_Get TBL: X_DETAIL_1(alias TBL: d1) [ Card=70 ]
LogOp_Get TBL: X_DETAIL_LINK(alias TBL: lnk) [ Card=47 ]
LogOp_Get TBL: X_DETAIL_2(alias TBL: d2) X_DETAIL_2 [ Card=119 ]
LogOp_Get TBL: X_DETAIL_3(alias TBL: d3) X_DETAIL_3 [ Card=281 ]
LogOp_Get TBL: X_LAST_TABLE(alias TBL: lst) X_LAST_TABLE [ Card=94025 ]
Lưu ý ước tính cuối cùng bị lỗi đã được đưa ra. Nó được in dưới dạng Card=4.52803e+009
và được lưu trữ bên trong dưới dạng giá trị dấu phẩy động chính xác kép 4.5280277425e + 9 (4528027742.5 theo số thập phân).
Bảng dẫn xuất trong truy vấn ban đầu đã bị xóa và các phép chiếu được chuẩn hóa. Một đại diện SQL của cây trên đó ước tính độ chính xác và độ chọn lọc ban đầu được thực hiện là:
SELECT
PRIMARY_ID = COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)
FROM X_DRIVING_TABLE dt
LEFT OUTER JOIN X_DETAIL_1 d1
ON dt.ID = d1.ID
LEFT OUTER JOIN X_DETAIL_LINK lnk
ON d1.LINK_ID = lnk.LINK_ID
LEFT OUTER JOIN X_DETAIL_2 d2
ON dt.ID = d2.ID
LEFT OUTER JOIN X_DETAIL_3 d3
ON dt.ID = d3.ID
INNER JOIN X_LAST_TABLE lst
ON lst.JOIN_ID = COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)
(Bên cạnh đó, sự lặp lại COALESCE
cũng có mặt trong kế hoạch cuối cùng - một lần trong tính toán vô hướng cuối cùng, và một lần ở phía bên trong của phép nối bên trong).
Thông báo tham gia cuối cùng. Phép nối bên trong này là (theo định nghĩa) sản phẩm cartesian X_LAST_TABLE
và đầu ra nối trước đó, với một lựa chọn (biến vị ngữ nối) lst.JOIN_ID = COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)
được áp dụng. Cardinality của sản phẩm cartesian chỉ đơn giản là 481577 * 94025 = 45280277425.
Để làm được điều đó, chúng ta cần xác định và áp dụng tính chọn lọc của vị ngữ. Sự kết hợp của COALESCE
cây mở rộng mờ đục (về UNION
và IIF
, nhớ) cùng với tác động đến thông tin chính, biểu đồ và tần số xuất phát của phép nối ngoài nhiều-nhiều-nhiều-nhiều-nhiều-dự phòng kết hợp trước đó có nghĩa là CE không thể rút ra một ước tính chấp nhận được trong bất kỳ cách thông thường nào.
Kết quả là, nó đi vào Logic Guess. Logic đoán phức tạp vừa phải, với các lớp thuật toán đoán "có giáo dục" và thuật toán đoán "không được giáo dục" đã thử. Nếu không tìm thấy cơ sở nào tốt hơn cho dự đoán, mô hình sử dụng dự đoán cuối cùng, để so sánh đẳng thức là: sqllang!x_Selectivity_Equal
= cố định 0,1 độ chọn lọc (đoán 10%):
-- the moment of doom
movsd xmm0,mmword ptr [sqllang!x_Selectivity_Equal
Kết quả là 0,1 độ chọn lọc trên sản phẩm cartesian: 481577 * 94025 * 0.1 = 4528027742.5 (~ 4.52804e + 009) như đã đề cập trước đó.
Viết lại
Khi tham gia có vấn đề được nhận xét , một ước tính tốt hơn được tạo ra bởi vì "dự đoán cuối cùng" có tính chọn lọc cố định được tránh (thông tin chính được giữ lại bởi các phép nối 1 M). Chất lượng của ước tính vẫn có độ tin cậy thấp, bởi vì một COALESCE
vị từ tham gia hoàn toàn không thân thiện với CE. Ước tính sửa đổi ít nhất trông hợp lý hơn với con người, tôi cho rằng.
Khi truy vấn được viết với phép nối ngoài để X_DETAIL_LINK
đặt cuối cùng , thứ tự heuristic có thể hoán đổi nó với phép nối bên trong cuối cùng thành X_LAST_TABLE
. Đặt liên kết bên trong ngay bên cạnh tham gia bên ngoài có vấn đề hạn chế khả năng sắp xếp lại sớm cơ hội để cải thiện ước tính cuối cùng, vì các tác động của liên kết ngoài nhiều "bất thường" nhiều dự phòng đến sau khi ước tính chọn lọc khó khăn cho COALESCE
. Một lần nữa, các ước tính tốt hơn một chút so với dự đoán cố định, và có lẽ sẽ không thể đứng lên để xác định kiểm tra chéo trong tòa án của pháp luật.
Sắp xếp lại một hỗn hợp các kết nối bên trong và bên ngoài là khó khăn và tốn thời gian (thậm chí tối ưu hóa toàn bộ giai đoạn 2 chỉ cố gắng một tập hợp con hạn chế của các bước di chuyển lý thuyết).
Đề ISNULL
xuất lồng nhau trong câu trả lời của Max Vernon quản lý để tránh dự đoán cố định giải cứu, nhưng ước tính cuối cùng là một hàng 0 không thể thực hiện được (nâng lên một hàng cho độ chính xác). Đây cũng có thể là một phỏng đoán cố định của 1 hàng, cho tất cả các cơ sở thống kê mà phép tính có.
Tôi sẽ mong đợi một ước tính cardinality tham gia giữa 0 và 481577 hàng.
Đây là một kỳ vọng hợp lý, ngay cả khi người ta chấp nhận rằng ước tính cardinality có thể xảy ra ở các thời điểm khác nhau (trong quá trình tối ưu hóa dựa trên chi phí) về các khác nhau về mặt vật lý, nhưng giống hệt nhau về mặt ngữ nghĩa - với kế hoạch cuối cùng là một loại tốt nhất của nhau tốt nhất (mỗi nhóm ghi nhớ). Việc thiếu một đảm bảo tính nhất quán trên toàn kế hoạch không có nghĩa là một cá nhân tham gia sẽ có thể thể hiện sự tôn trọng, tôi hiểu điều đó.
Mặt khác, nếu chúng ta kết thúc với dự đoán về biện pháp cuối cùng , hy vọng đã bị mất, vậy tại sao phải bận tâm. Chúng tôi đã thử tất cả các thủ thuật mà chúng tôi biết, và đã từ bỏ. Nếu không có gì khác, ước tính cuối cùng hoang dã là một dấu hiệu cảnh báo tuyệt vời rằng không phải mọi thứ đều diễn ra tốt đẹp trong CE trong quá trình biên dịch và tối ưu hóa truy vấn này.
Khi tôi thử MCVE, 120+ CE đã tạo ra ước tính cuối cùng của hàng không (= 1) (giống như lồng nhau ISNULL
) cho truy vấn ban đầu, điều này không thể chấp nhận được theo cách nghĩ của tôi.
Giải pháp thực sự có thể liên quan đến thay đổi thiết kế, cho phép các phép nối đơn giản không có COALESCE
hoặc ISNULL
, và lý tưởng là các khóa ngoại và các ràng buộc khác hữu ích cho việc biên dịch truy vấn.
bigint
thay vìdecimal(18, 0)
bạn sẽ nhận được lợi ích: 1) sử dụng 8 byte thay vì 9 cho mỗi giá trị và 2) sử dụng loại dữ liệu có thể so sánh byte thay vì loại dữ liệu đóng gói, có thể có hàm ý cho thời gian CPU khi so sánh các giá trị.