SQL Server tạo các gói khác nhau khi điều kiện OR được sắp xếp lại


7

Tôi đã xem xét một truy vấn hoạt động kém như thế này:

WHERE manymany.Active = -1
  AND manymany.Check1 = -1
  AND manymany.WebsiteID = @P1
  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 (main.TextCol1 IS NOT NULL OR main.TextCol2 IS NOT NULL)
ORDER BY aux.SortCode

Tôi đã vô tình sử dụng trình thiết kế truy vấn SSMS trên truy vấn này và nó đã viết lại truy vấn như sau:

WHERE manymany.Active = -1
  AND manymany.Check1 = -1
  AND manymany.WebsiteID = @P2
  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 main.TextCol1 IS NOT NULL

   OR manymany.Active = -1
  AND manymany.Check1 = -1
  AND manymany.WebsiteID = @P2
  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 main.TextCol2 IS NOT NULL
ORDER BY aux.SortCode

Nếu bạn nhìn kỹ, bạn sẽ nhận thấy rằng nó chỉ đơn giản là mở rộng ORđiều kiện bằng cách lặp lại tất cả các điều kiện tức là nó đã thay đổi a AND (b OR c)thành (a AND b) OR (a AND c).

Truy vấn kết quả nhỏ hơn 50% về chi phí và 33% nhỏ hơn về thời gian thực hiện. Tôi chỉ đơn giản là không hiểu tại sao việc sắp xếp lại ORđiều kiện đã thay đổi kế hoạch khi cả hai truy vấn giống hệt nhau (?). Tôi có thể tự mở rộng ORđiều kiện bằng cách sao chép các điều kiện nhưng tại sao tôi phải làm vậy?

Dán kế hoạch và ảnh chụp màn hình:

Kế hoạch thực hiện

Số hàng:

main     2718
manymany 188761
aux      19

Ghi chú:

  • TextCol1 và TextCol2 là textkiểu dữ liệu và không thể được lập chỉ mục
  • Có avg. 170,20 hồ sơ trong nhiều bảng cho mỗi id trang web

1
Để hiểu (hoặc ít nhất là cố gắng làm điều đó), bạn phải hiển thị DDL của bảng. Một ví dụ đơn giản - nếu tồn tại 2 chỉ số riêng biệt có thể được sử dụng để tối ưu hóa các điều kiện (a AND b) và (a AND c), chúng có thể được sử dụng trong biến thể "mở rộng", nhưng không thể trong "cơ sở" một ...
Akina

@Akina Tôi có các chỉ mục bao gồm tất cả các mệnh đề ngoại trừ bảng có liên quan đến IS NOT NULL... các cột đó là textkiểu dữ liệu.
Salman A

Thêm kế hoạch truy vấn vào pastetheplan.com sẽ hữu ích
Randi Vertongen

Nếu bạn dán toàn bộ truy vấn với tất cả các phép nối sẽ giúp mọi người hiểu rõ hơn. Ngoài ra điều này phụ thuộc vào bảng được aliased main, (có bao nhiêu hồ sơ mỗi bảng chứa,) tương tự áp dụng cho manymanyauxbảng aliased và cách bạn tham gia cùng họ.
MarmiK

1
Vui lòng thêm các CREATE TABLEcâu lệnh với tất cả các chỉ mục) và truy vấn đầy đủ. Điều FROMkhoản ít nhất là rất cần thiết để hiểu làm thế nào kế hoạch có thể được sản xuất.
ypercubeᵀᴹ

Câu trả lời:


2

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 ORgiố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 Mainbảng và manymanybảng. nhập mô tả hình ảnh ở đây

Lưu ý rằng EXPR1021EXPR1022 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 manymanybảng.

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

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 ANDlọ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 ORphầ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 ANDvị 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:

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

Và trong manymanybảng, cùng một tuyên bố được đánh giá hai lần:

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

Trong kế hoạch 'chậm', các câu lệnh không được thêm vào cùng với ORcá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).

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

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

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 mainbảng với manymanybả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 manymanybảng.

Kết quả là, cùng một vị từ được thực hiện hai lần trên manymanybảng:

nhập mô tả hình ảnh ở đây Đố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 mainbàn

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

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 mainmanymanyxảy ra, một lần nữa cho tất cả các kết hợp có thể nhập mô tả hình ảnh ở đây

Lưu ý rằng EXPR1021EXPR1022 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 manymanybảng.

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

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 ANDlọ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 ORphầ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 ANDvị 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 manymanybảng để lọc phần còn lại để có được 15 hàng.

Main bộ lọc bảng

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

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

manymany bộ lọc bảng

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


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:

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

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: nhập mô tả hình ảnh ở đây

Ước tính 93 hàng sẽ được trả về bởi vị từ khi quét:

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

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.manymanybả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_4trên the dbo.Mainbảng, nhập mô tả hình ảnh ở đây

Nó đang lọc với một vị từ tìm kiếm, trả lại 27 hàng cho nested loopstoán tử Tham gia, một lần nữa nối với dbo.manymanybả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 :

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

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.Mainbàn:

Với vị ngữ: TextCol1 IS NOT NULL OR TextCol2 IS NOT NULL

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

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.manymanybàn

nhập mô tả hình ảnh ở đây (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.manymanyvà dẫn đến một bộ lọc lớn hơn nhiều, trên 27 hàng.

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

Bộ lọc lớn hơn nhiều với nhiều ORhàng trên 27 hàng.

Một điểm khác biệt nữa là toán tử tra cứu Key:

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

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.

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

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

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 UNIONcâ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;

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)?
Salman A

@SalmanA Hãy nói rằng a thuộc về bảng nhiều người và B và C thuộc về bảng Chính. a AND (b OR c)Nó giống như truy vấn chậm, trong đó bhoặc cđược lọc trực tiếp trên bảng chính, sau đó tham gia với a(ManyMany) để lấy kết quả còn lại. (a and b) or (a and c)đang lọc sau khi nối hai bảng, để lọc chúng sau khi lấy kết quả của phép nối Vì acchỉ có thể được lọc cùng với ab.
Randi Vertongen

@SalmanA SQL không thấy giống nhau vì 1*11+1không giống với SQL. Đây là ngôn ngữ máy tính và máy ở cuối. Nếu nó trở nên đủ thông minh, tại sao DBA lại được yêu cầu.!?
MarmiK

2
@RandiVertongen ở đây bạn giải thích lý do tại sao các gói khác nhau có hiệu suất khác nhau và loại nào sẽ tốt hơn, nhưng câu hỏi của OP là tại sao việc viết 2 biểu thức hoàn toàn tương đương lại ảnh hưởng đến các kế hoạch của trình tối ưu hóa. SQL là (về mặt lý thuyết) là một ngôn ngữ khai báo (không bắt buộc), vì vậy chúng tôi nói với động cơ chúng ta muốn và không bao nó nên làm. Đối với ví dụ này có vẻ như chúng ta có thể ảnh hưởng như thế nào.
EzLo

@EzLo Bạn đã đúng, tôi đã thêm các giả định của mình, tôi có thể hoàn toàn không có cơ sở.
Randi Vertongen

0

Trong truy vấn đầu tiên, Nó phải bắt đầu bằng một lần quét trong khi với lần thứ hai, nó có thể sử dụng chỉ mục không phân cụm cho tìm kiếm.

Hãy nghĩ về nó giống như bạn có 100 viên bi trong một cái túi và bạn muốn kiểm tra chúng để chỉ chọn ra những viên có màu xanh lam và trắng hoặc xanh và đỏ.

Truy vấn đầu tiên là nói, xem qua từng viên bi và chọn ra tất cả các màu xanh. Khi bạn đã thực hiện điều đó, hãy kiểm tra tất cả chúng và xem nếu có bất kỳ màu trắng hoặc đỏ.

Truy vấn thứ hai là nói đi vào túi và chỉ lấy viên bi màu xanh và trắng hoặc xanh và đỏ.

Truy vấn thứ hai sẽ nhanh hơn vì bạn sẽ không phải nhìn vào từng viên bi cho màu xanh đầu tiên. Bạn có thể kết hợp bước đó với những gì bạn thực sự muốn đó là màu xanh và trắng hoặc xanh và đỏ.

Đó là cách tôi nhìn vào nó. Cuối cùng, truy vấn đầu tiên yêu cầu quét bảng và lần thứ hai sử dụng tìm kiếm ngay từ đầu. Nó vẫn phải thực hiện tra cứu khóa và quét vì chỉ mục không phân cụm không có tất cả thông tin cần thiết, nhưng vào thời điểm đó, nó có một bộ dữ liệu nhỏ hơn nhiều để xem qua nên nhanh hơn.


0

Kế hoạch liên kết không hoạt động.

Bạn đã cung cấp thông tin không đầy đủ. Điều quan trọng là phải biết cột tham gia bảng và loại dữ liệu của họ.

aux.SortCodeauxBí danh là gì? Nó nằm ở đâu trong Truy vấn?

Có thêm phạm vi để sắp xếp lại truy vấn.

Trong trường hợp này,

AND (main.TextCol1 IS NOT NULL OR main.TextCol2 IS NOT NULL)

Trình tối ưu hóa Sql Cardianility Estimaterất kém.

Lặp đi lặp lại cùng một điều kiện với ORý tưởng là xấu.

Bạn có thể đặt manybản ghi #Tempbảng trong bảng hoặcCTE

Sau đó cuối cùng tham gia manymanyvới #Tempbảng.

DECLARE @Dt Datetime=CURRENT_TIMESTAMP

select many.Col,many.OnlyRequiredColumn
from many into #Temp
WHERE main.Active = -1
  AND main.StatusID = 1
  AND (main.FromDate>=@Dt AND main.UptoDate<=@Dt)
  AND (main.TextCol1 IS NOT NULL OR main.TextCol2 IS NOT NULL)
ORDER BY aux.SortCode

Nếu #Tempcó hơn 100/200 bản ghi thì bạn có thể tạo indextrên Cột mà Tham gia với nhiều người.

select *
from manymany
join #Temp on manymany.somecolumn=#temp.somecolumn
WHERE manymany.Active = -1
  AND manymany.Check1 = -1
  AND manymany.WebsiteID = @P1
  AND (manymany.FromDate>=@Dt AND manymany.UptoDate<=@Dt)
--ORDER BY aux.SortCode
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.