Tối ưu hóa lựa chọn trên truy vấn con với COALESCE (Mạnh)


8

Tôi có một cái nhìn lớn mà tôi sử dụng từ trong một ứng dụng. Tôi nghĩ rằng tôi đã thu hẹp vấn đề hiệu suất của mình, nhưng tôi không biết cách khắc phục nó. Một phiên bản đơn giản hóa của khung nhìn trông như thế này:

SELECT ISNULL(SEId + '-' + PEId, '0-0') AS Id,
   *,
   DATEADD(minute, Duration, EventTime) AS EventEndTime
FROM (
    SELECT se.SEId, pe.PEId,
        COALESCE(pe.StaffName, se.StaffName) AS StaffName, -- << Problem!
        COALESCE(pe.EventTime, se.EventTime) AS EventTime,
        COALESCE(pe.EventType, se.EventType) AS EventType,
        COALESCE(pe.Duration, se.Duration) AS Duration,
        COALESCE(pe.Data, se.Data) AS Data,
        COALESCE(pe.Field, se.Field) AS Field,
        pe.ThisThing, se.OtherThing
    FROM PE pe FULL OUTER JOIN SE se 
      ON pe.StaffName = se.StaffName
     AND pe.Duration = se.Duration
     AND pe.EventTime = se.EventTime
    WHERE NOT(pe.ThisThing = 1 AND se.OtherThing = 0)
) Z

Điều đó có thể không biện minh cho toàn bộ lý do cấu trúc truy vấn, nhưng có thể cho bạn một ý tưởng - chế độ xem này kết hợp với hai bảng được thiết kế rất kém mà tôi không kiểm soát được và cố gắng tổng hợp một số thông tin từ đó.

Vì vậy, vì đây là chế độ xem được sử dụng từ ứng dụng, trong khi cố gắng tối ưu hóa, tôi bọc nó trong một CHỌN khác, như thế này:

SELECT * FROM (
    -- … above code …
) Q
WHERE StaffName = 'SMITH, JOHN Q'

bởi vì ứng dụng đang tìm kiếm các nhân viên cụ thể trong kết quả.

Vấn đề dường như là COALESCE(pe.StaffName, se.StaffName) AS StaffNamephần và tôi đang chọn từ chế độ xem trên StaffName. Nếu tôi thay đổi điều đó thành pe.StaffName AS StaffNamehoặc se.StaffName AS StaffName, các vấn đề về hiệu suất sẽ biến mất (nhưng xem cập nhật 2 bên dưới) . Nhưng điều đó sẽ không xảy ra vì một bên hay bên kia FULL OUTER JOINcó thể bị thiếu, vì vậy một hoặc một lĩnh vực khác có thể là NULL.

Tôi có thể cấu trúc lại cái này thay thế COALESCE(…)cái khác không, cái này sẽ được viết lại thành truy vấn con?

Ghi chú khác:

  • Tôi đã thêm một số chỉ mục để khắc phục các vấn đề về hiệu năng với phần còn lại của truy vấn - không có COALESCEnó rất nhanh.
  • Hơi ngạc nhiên, nhìn vào kế hoạch thực hiện không giương cờ nào, ngay cả khi WHEREbao gồm cả câu hỏi và câu hỏi phụ. Tổng chi phí truy vấn con của tôi trong máy phân tích là 0.0065736. Hừm. Phải mất bốn giây để thực hiện.
  • Thay đổi ứng dụng để truy vấn khác nhau (ví dụ như trả lại pe.StaffName AS PEStaffName, se.StaffName AS SEStaffNamevà thực hiện WHERE PEStaffName = 'X' OR SEStaffName = 'X') có thể hoạt động, nhưng như là phương sách cuối cùng - tôi thực sự hy vọng tôi có thể tối ưu hóa chế độ xem mà không cần phải dùng đến việc chạm vào ứng dụng.
  • Một thủ tục được lưu trữ có thể có ý nghĩa hơn cho việc này, nhưng ứng dụng được xây dựng với Entity Framework và tôi không thể tìm ra cách làm cho nó hoạt động tốt với SP trả về loại bảng (hoàn toàn là một chủ đề khác).

Chỉ mục

Các chỉ mục tôi đã thêm cho đến nay trông giống như thế này:

CREATE NONCLUSTERED INDEX [IX_PE_EventTime]
ON [dbo].[PE] ([EventTime])
INCLUDE ([StaffName],[Duration],[EventType],[Data],[Field],[ThisThing])

CREATE NONCLUSTERED INDEX [IX_SE_EventTime]
ON [dbo].[SE] ([EventTime])
INCLUDE ([StaffName],[Duration],[EventType],[Data],[Field],[OtherThing])

Cập nhật

Tôi đã thử mô phỏng sự thay đổi bị ảnh hưởng ở trên và nó không giúp được gì. Tức là, trước đây ) Z, tôi đã thêm AND (pe.StaffName = 'SMITH, JOHN Q' OR se.StaffName = 'SMITH, JOHN Q'), nhưng hiệu suất là như nhau. Bây giờ tôi thực sự không biết bắt đầu từ đâu.

Cập nhật 2

Nhận xét của @ypercube về việc cần tham gia đầy đủ khiến tôi nhận ra rằng truy vấn tổng hợp của mình đã bỏ qua một thành phần có thể quan trọng. Mặc dù, vâng, tôi cần tham gia đầy đủ, thử nghiệm tôi đã thực hiện ở trên bằng cách bỏ COALESCEvà chỉ kiểm tra một mặt của giá trị không có giá trị sẽ làm cho mặt khác của tham gia đầy đủ không liên quan và trình tối ưu hóa có thể sử dụng điều này Thực tế để tăng tốc độ truy vấn. Ngoài ra, tôi đã cập nhật ví dụ để cho thấy đây StaffNamethực sự là một trong những khóa tham gia - có lẽ có ảnh hưởng đáng kể đến câu hỏi. Bây giờ tôi cũng đang nghiêng về đề nghị của anh ấy rằng việc chia nhỏ điều này thành một liên minh ba chiều thay vì tham gia đầy đủ có thể là câu trả lời, và sẽ đơn giản hóa sự phong phú của những COALESCEgì tôi đang làm. Đang thử nó ngay bây giờ.


Những chỉ số nào bạn đã thêm? Bạn có bao gồm cả Tên nhân viên trong chỉ mục không?
Mark Sinkinson

@MarkSinkinson Tôi có một chỉ số nonclustered trên mỗi bảng trên KeyField, cả hai chỉ số INCLUDEcác StaffNamelĩnh vực và một số lĩnh vực khác. Tôi có thể đăng định nghĩa chỉ số trong câu hỏi. Tôi đang làm việc trên máy chủ thử nghiệm này để tôi có thể thêm bất kỳ chỉ mục nào bạn nghĩ có thể hữu ích để thử!
S'pht'Kr

1
Bạn có WHERE pe.ThisThing = 1 AND se.OtherThing = 0điều kiện hủy FULL OUTERtham gia và thực hiện truy vấn tương đương với tham gia bên trong. Bạn có chắc chắn cần một sự tham gia ĐẦY ĐỦ?
ypercubeᵀᴹ

@ypercube Tôi xin lỗi, về phần mã hóa không khí rất tệ, điểm quan trọng hơn là tôi có điều kiện trên cả hai bảng, nhưng vâng, tôi tính đến null ở hai bên trong truy vấn thực. Tôi đang hợp nhất hai bảng và tìm kiếm kết quả khớp, nhưng tôi cần dữ liệu có sẵn từ một trong hai bảng khi không có bản ghi khớp ở bên trái hoặc bên phải - vì vậy, tôi cần tham gia đầy đủ.
S'pht'Kr

1
Một nghĩ: đó là một Longshot nhưng bạn có thể cố gắng để phá vỡ các truy vấn nội bộ thành ba phần ( INNER JOIN, LEFT JOINvới WHERE IS NULLkiểm tra, RIGHT JOIN với IS NULL) và sau đó UNION ALLba phần. Cách này sẽ không cần sử dụng COALESCE()và nó có thể (chỉ có thể) giúp trình tối ưu hóa tìm ra cách viết lại.
ypercubeᵀᴹ

Câu trả lời:


4

Điều này khá dài nhưng vì OP nói rằng nó hoạt động, tôi thêm nó vào như một câu trả lời (cứ tự nhiên sửa nó nếu bạn thấy có gì sai).

Cố gắng chia truy vấn nội bộ thành ba phần ( INNER JOIN, LEFT JOINvới WHERE IS NULLkiểm tra, RIGHT JOINvới IS NULLkiểm tra) và sau đó UNION ALLlà ba phần. Điều này có những ưu điểm sau:

  • Trình tối ưu hóa có ít tùy chọn chuyển đổi có sẵn cho các phép FULLnối hơn so với (phổ biến hơn) INNERvà các phép LEFTnối.

  • Bảng Zdẫn xuất có thể được loại bỏ (bạn vẫn có thể làm điều đó) khỏi định nghĩa khung nhìn.

  • Các NOT(pe.ThisThing = 1 AND se.OtherThing = 0)sẽ là cần thiết chỉ trên INNERphần tham gia.

  • Cải thiện nhỏ, việc sử dụng COALESCE()sẽ là tối thiểu nếu có (tôi cho rằng se.SEIdpe.PEIdkhông thể rỗng. Nếu nhiều cột không thể bị vô hiệu hóa, bạn sẽ có thể xóa nhiều COALESCE()cuộc gọi hơn .)
    Quan trọng hơn, trình tối ưu hóa có thể đẩy xuống bất kỳ điều kiện nào trong các truy vấn của bạn liên quan đến các cột này (hiện COALESCE()không chặn được việc đẩy.)

  • Tất cả những điều trên sẽ cung cấp cho trình tối ưu hóa nhiều tùy chọn hơn để chuyển đổi / viết lại bất kỳ truy vấn nào sử dụng chế độ xem để nó có thể tìm thấy một kế hoạch thực hiện có thể sử dụng các chỉ mục trên các bảng bên dưới.

Trong tất cả, khung nhìn có thể được viết là:

SELECT 
    se.SEId + '-' + pe.PEId AS Id,
    se.SEId, pe.PEId,
    pe.StaffName, 
    pe.EventTime,
    COALESCE(pe.EventType, se.EventType) AS EventType,
    pe.Duration,
    COALESCE(pe.Data, se.Data) AS Data,
    COALESCE(pe.Field, se.Field) AS Field,
    pe.ThisThing, se.OtherThing,
    DATEADD(minute, pe.Duration, pe.EventTime) AS EventEndTime
FROM PE pe INNER JOIN SE se 
  ON pe.StaffName = se.StaffName
 AND pe.Duration = se.Duration
 AND pe.EventTime = se.EventTime
WHERE NOT (pe.ThisThing = 1 AND se.OtherThing = 0) 

UNION ALL

SELECT 
    '0-0',
    NULL, pe.PEId,
    pe.StaffName, 
    pe.EventTime,
    pe.EventType,
    pe.Duration,
    pe.Data,
    pe.Field,
    pe.ThisThing, NULL,
    DATEADD(minute, pe.Duration, pe.EventTime) AS EventEndTime
FROM PE pe LEFT JOIN SE se 
  ON pe.StaffName = se.StaffName
 AND pe.Duration = se.Duration
 AND pe.EventTime = se.EventTime
WHERE NOT (pe.ThisThing = 1)
  AND se.StaffName IS NULL

UNION ALL

SELECT 
    '0-0',
    se.SEId, NULL,
    se.StaffName, 
    se.EventTime,
    se.EventType,
    se.Duration,
    se.Data,
    se.Field,
    NULL, se.OtherThing, 
    DATEADD(minute, se.Duration, se.EventTime) AS EventEndTime
FROM PE pe RIGHT JOIN SE se 
  ON pe.StaffName = se.StaffName
 AND pe.Duration = se.Duration
 AND pe.EventTime = se.EventTime
WHERE NOT (se.OtherThing = 0)
  AND pe.StaffName IS NULL ;

0

Trực giác của tôi sẽ không phải là vấn đề vì theo thời gian, COALESCE(pe.StaffName, se.StaffName) AS StaffNametất cả các hàng từ hai nguồn sẽ được kéo vào và khớp với nhau để gọi hàm là một so sánh trong bộ nhớ đơn giản so với null -tốt. Rõ ràng đây không phải là trường hợp nên có lẽ một cái gì đó trong một trong các nguồn (nếu chúng là dạng xem hoặc bảng dẫn xuất nội tuyến) hoặc bảng cơ sở (tức là thiếu chỉ mục) đang khiến trình hoạch định truy vấn nghĩ rằng nó cần quét riêng các cột này.

Không có thêm chi tiết về truy vấn chính xác mà bạn đang chạy, các cấu trúc hỗ trợ và các kế hoạch truy vấn được tạo ra, bất cứ điều gì chúng tôi đề xuất đều là phỏng đoán.

Để cố gắng buộc việc so sánh được thực hiện sau tất cả các cách khác, bạn có thể thử chỉ cần chọn cả hai giá trị trong bảng bị loại bỏ ( pe.StaffName AS pe.StaffName, se.StaffName AS seStaffName) sau đó thực hiện chọn trong truy vấn bên ngoài ( COALESCE(peStaffName, seStaffName) AS StaffName) hoặc thậm chí bạn có thể đẩy dữ liệu từ truy vấn bên trong vào một bảng tạm thời sau đó thực hiện truy vấn bên ngoài bằng cách chọn từ đó (nhưng điều đó sẽ yêu cầu một thủ tục được lưu trữ và tùy thuộc vào số lượng hàng mà kết xuất này đến tempdb có thể tốn kém và do đó có vấn đề theo đúng nghĩa của nó).


Cảm ơn David, tôi đã nhầm lẫn về khía cạnh hoang tưởng về việc tôi nên tiết lộ bao nhiêu về điều này ngay cả về cấu trúc (pe => PatientEvent, vì vậy,) nhưng tôi biết điều đó làm cho nó khó hơn. Tôi nghĩ rằng trên thực tế, việc tham gia dựa trên các chỉ mục và sau đó thực hiện "so sánh trong bộ nhớ đơn giản" để lọc lọc nhưng bảng dẫn xuất chưa được lọc Zhiện quay lại với ~ 1,5m hàng. Những gì tôi muốn nó làm là viết lại vị từ đó vào truy vấn Zđể nó sẽ sử dụng các chỉ mục, nhưng bây giờ tôi cũng bối rối vì khi tôi đặt vị từ đó vào đó, nó vẫn không sử dụng một chỉ mục. Tôi không chắc.
S'pht'Kr
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.