Nhưng tại sao máy chủ SQL không xem cả hai truy vấn là một? Rốt cuộc, a AND (b OR c) = (a AND b) OR (a AND c)?
Về mặt logic thì nó giống nhau, và nó sẽ nhận được kết quả tương tự.
Giả định
Giả định của tôi là đối với kế hoạch 'nhanh hơn', trình tối ưu hóa không xem xét một số câu lệnh lọc ở đầu câu lệnh OR
giống như một số câu lệnh lọc ở phía dưới. Tôi có thể hoàn toàn rời khỏi đây.
lý do để có được các giả định này dựa trên vị từ bộ lọc này:
Vị từ bộ lọc này sử dụng kết quả của phép nối giữa Main
bảng và manymany
bảng.
Lưu ý rằng EXPR1021 và EXPR1022 trong bộ lọc này là các biểu thức được tạo từ toán tử vô hướng trên manymany
bảng.
Bộ lọc này bao gồm hai phần, phần thứ nhất có (.. AND .. OR .. AND ..)
và phần thứ hai AND
lọc đơn giản
(getdate()>=[Expr1021]
AND getdate()<=[Expr1022]
AND getdate()>=[DB1].[dbo].[main].[FromDate]
AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000')
AND [DB1].[dbo].[main].[TextCol1] IS NOT NULL
OR getdate()>=[Expr1021]
AND getdate()<=[Expr1022]
AND getdate()>=[DB1].[dbo].[main].[FromDate]
AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000')
AND [DB1].[dbo].[main].[TextCol2] IS NOT NULL)
AND (getdate()>=[DB1].[dbo].[main].[FromDate]
AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000')
AND [DB1].[dbo].[main].[TextCol1] IS NOT NULL OR getdate()>=[DB1].[dbo].[main].[FromDate]
AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000')
AND [DB1].[dbo].[main].[TextCol2] IS NOT NULL)
Như bạn có thể thấy, sự khác biệt duy nhất ở trên và dưới OR
phần đầu tiên của bộ lọc này là
AND [DB1].[dbo].[main].[TextCol1] IS NOT NULL
VS
AND [DB1].[dbo].[main].[TextCol2] IS NOT NULL
Và phần thứ hai cần phải đúng dù thế nào đi chăng nữa, vì chúng là những AND
vị ngữ không có bất kỳ OR
.
Kết quả là tính toán thêm của các chức năng tương tự, mà theo tôi là không cần thiết. Một lần nữa, tôi đoán ở đây là lý do máy chủ sql thực hiện các tính toán này là vì nó không biết rằng chúng giống nhau.
Đối với một số phần khác của mệnh đề where, nó biết rằng chúng giống nhau, ví dụ: Trong bảng chính, statusid = 1 chỉ được đánh giá một lần:
Và trong manymany
bảng, cùng một tuyên bố được đánh giá hai lần:
Trong kế hoạch 'chậm', các câu lệnh không được thêm vào cùng với OR
các mệnh đề và đó là lý do tại sao trình tối ưu hóa tạo ra một kế hoạch khác, áp dụng các biến vị ngữ bộ lọc trên các bảng riêng biệt (và không có bộ lọc trùng lặp).
Kết thúc các giả định
So sánh hai kế hoạch
Tôi nghĩ rằng bạn đã may mắn với hiệu suất của kế hoạch 'nhanh', nhưng kế hoạch 'nhanh' có thể trở nên xấu xí khi dữ liệu phù hợp tăng lên. Nó có thể phụ thuộc vào vị trí và thời điểm bạn đang áp dụng các bộ lọc của mình (và các yếu tố khác) .
Lọc kế hoạch nhanh
Trong gói 'nhanh': máy chủ sql áp dụng một số bộ lọc sau khi nối main
bảng với manymany
bảng do kết hợp khác nhau với các khối OR
+ ( AND ... AND ... AND...
) của hai khối. Các cột từ maintable
được lọc sau khi tìm thấy tất cả các kết hợp có thể với manymany
bảng.
Kết quả là, cùng một vị từ được thực hiện hai lần trên manymany
bảng:
Đối với các vị ngữ trên và dưới OR
.
Nhưng đây không phải là trường hợp của một số vị từ tìm kiếm trên main
bàn
Sau đó, phép nối này xảy ra và một vị trí bộ lọc thậm chí còn lớn hơn về kết quả của phép nối giữa main
và manymany
xảy ra, một lần nữa cho tất cả các kết hợp có thể
Lưu ý rằng EXPR1021 và EXPR1022 trong bộ lọc này là các biểu thức được tạo từ toán tử vô hướng trên manymany
bảng.
Bộ lọc này bao gồm hai phần, phần thứ nhất có (.. AND .. OR .. AND ..)
và phần thứ hai AND
lọc đơn giản
(getdate()>=[Expr1021]
AND getdate()<=[Expr1022]
AND getdate()>=[DB1].[dbo].[main].[FromDate]
AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000')
AND [DB1].[dbo].[main].[TextCol1] IS NOT NULL
OR getdate()>=[Expr1021]
AND getdate()<=[Expr1022]
AND getdate()>=[DB1].[dbo].[main].[FromDate]
AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000')
AND [DB1].[dbo].[main].[TextCol2] IS NOT NULL)
AND (getdate()>=[DB1].[dbo].[main].[FromDate]
AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000')
AND [DB1].[dbo].[main].[TextCol1] IS NOT NULL OR getdate()>=[DB1].[dbo].[main].[FromDate]
AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000')
AND [DB1].[dbo].[main].[TextCol2] IS NOT NULL)
Như bạn có thể thấy, sự khác biệt duy nhất ở trên và dưới OR
phần đầu tiên của bộ lọc này là
AND [DB1].[dbo].[main].[TextCol1] IS NOT NULL
VS
AND [DB1].[dbo].[main].[TextCol2] IS NOT NULL
Và phần thứ hai cần phải đúng dù thế nào đi chăng nữa, vì chúng là những AND
vị ngữ không có bất kỳ OR
.
Kết quả là tính toán thêm mà theo tôi là không cần thiết.
Lọc kế hoạch chậm
Trong gói 'chậm': máy chủ sql áp dụng bộ lọc trực tiếp vào bảng Chính do kết quả của phần AND (TextCol1 IS NOT NULL OR TextCol2 IS NOT NULL
) và sau đó kết hợp với manymany
bảng để lọc phần còn lại để có được 15 hàng.
Main
bộ lọc bảng
manymany
bộ lọc bảng
Một số thông tin khác, đôi khi chồng chéo:
Kế hoạch chậm hơn
Khi chúng ta xem xét kế hoạch chậm hơn, chỉ số cụm PK_main được sử dụng, thành một toán tử vô hướng tính toán, bộ lọc và các vòng lặp lồng nhau:
Khi chúng ta so sánh điều này với các hàng ước tính sẽ được trả về, chúng ta thấy một sự khác biệt:
Ước tính 93 hàng sẽ được trả về bởi vị từ khi quét:
Con số này thực sự thấp hơn khoảng 20 lần so với dự kiến, đó là 1947 hàng .
Sau đó, tính toán vô hướng hoặc câu lệnh này:
, CASE WHEN TextCol1 IS NOT NULL OR TextCol2 IS NOT NULL THEN -1 ELSE 0 END AS MoreFlag
, CASE WHEN Stars BETWEEN 1 AND 5 THEN Stars END AS Rating
được đánh giá trên 1947 hàng này.
Sau đó, toán tử bộ lọc ( main.TextCol1 IS NOT NULL OR main.TextCol2 IS NOT NULL
) để giảm xuống còn 1374 hàng.
Sau đó, nối 1374 hàng này vào dbo.manymany
bảng để nhận 15 hàng được trả về.
Kế hoạch nhanh hơn
Gói nhanh hơn đang sử dụng chỉ số NC: CVR_main_4
trên the dbo.Main
bảng,
Nó đang lọc với một vị từ tìm kiếm, trả lại 27 hàng cho nested loops
toán tử Tham gia, một lần nữa nối với dbo.manymany
bảng.
Và các hàng thực tế được trả về thậm chí còn thấp hơn các hàng ước tính :
27 hàng thực tế cho ước tính 152 hàng
Lọc
Một sự khác biệt lớn là nơi xảy ra quá trình lọc, trong đó trên gói 'chậm hơn', việc này được thực hiện trực tiếp trên dbo.Main
bàn:
Với vị ngữ: TextCol1 IS NOT NULL OR TextCol2 IS NOT NULL
Và đang áp dụng bộ lọc này cho 1943 hàng.
Với các bộ lọc khác xảy ra trực tiếp trên dbo.manymany
bàn
(tìm kiếm) vị ngữ trên dbo.manymany
Trong khi cái khác OR
, trong gói 'nhanh hơn', đang được lọc sau khi nối từ dbo.Main
đến dbo.manymany
và dẫn đến một bộ lọc lớn hơn nhiều, trên 27 hàng.
Bộ lọc lớn hơn nhiều với nhiều OR
hàng trên 27 hàng.
Một điểm khác biệt nữa là toán tử tra cứu Key:
trong đó có 10 cột bổ sung từ chỉ mục được nhóm, nhưng chỉ phải thực hiện điều này trong 27 hàng.
Một lý do khác mà trình tối ưu hóa chọn gói 'chậm hơn' có thể là do trình tối ưu hóa nghĩ rằng không tra cứu các cột khác sẽ tốt hơn.
Là kế hoạch nhanh thậm chí nhanh hơn, hay luôn luôn là 'nhanh hơn'?
Tôi nghĩ rằng, nếu dữ liệu đi qua bộ lọc tăng lên, kế hoạch 'chậm' sẽ tốt hơn. Không chỉ do tra cứu khóa, mà còn do toán tử bộ lọc lớn hơn nằm sâu hơn trong kế hoạch.
Nếu điều đó xảy ra, bên cạnh lập chỉ mục. Bạn có thể cải thiện việc lọc bằng cách chia truy vấn thành nhiều phần bằng cách sử dụng UNION
câu lệnh.
Thích như vậy:
SELECT main.MainID, Title, Column1, Column2, Column7, Column4, Column6, Column3, Column5
, CASE WHEN TextCol1 IS NOT NULL OR TextCol2 IS NOT NULL THEN -1 ELSE 0 END AS MoreFlag
, CASE WHEN Stars BETWEEN 1 AND 5 THEN Stars END AS Rating
FROM manymany
INNER JOIN main ON manymany.MainID = main.MainID
LEFT JOIN aux ON manymany.AuxID = aux.AuxID
WHERE manymany.WebsiteID = @P1
AND manymany.Check1 = -1
AND manymany.Active = -1
AND CURRENT_TIMESTAMP BETWEEN ISNULL(manymany.FromDate, '1950-01-01') AND ISNULL(manymany.UptoDate, '2050-01-01')
AND main.Active = -1
AND main.StatusID = 1
AND CURRENT_TIMESTAMP BETWEEN main.FromDate AND ISNULL(main.UptoDate, '2050-01-01')
AND TextCol1 IS NOT NULL
UNION
SELECT main.MainID, Title, Column1, Column2, Column7, Column4, Column6, Column3, Column5
, CASE WHEN TextCol1 IS NOT NULL OR TextCol2 IS NOT NULL THEN -1 ELSE 0 END AS MoreFlag
, CASE WHEN Stars BETWEEN 1 AND 5 THEN Stars END AS Rating
FROM manymany
INNER JOIN main ON manymany.MainID = main.MainID
LEFT JOIN aux ON manymany.AuxID = aux.AuxID
WHERE manymany.WebsiteID = @P1
AND manymany.Check1 = -1
AND manymany.Active = -1
AND CURRENT_TIMESTAMP BETWEEN ISNULL(manymany.FromDate, '1950-01-01') AND ISNULL(manymany.UptoDate, '2050-01-01')
AND main.Active = -1
AND main.StatusID = 1
AND CURRENT_TIMESTAMP BETWEEN main.FromDate AND ISNULL(main.UptoDate, '2050-01-01')
AND TextCol2 IS NOT NULL
ORDER BY SortCode;