Cách tối ưu hóa một truy vấn chạy chậm trên các vòng lặp lồng nhau (Tham gia bên trong)


39

TL; DR

Vì câu hỏi này tiếp tục nhận được lượt xem, tôi sẽ tóm tắt nó ở đây để những người mới không phải chịu đựng lịch sử:

JOIN table t ON t.member = @value1 OR t.member = @value2 -- this is slow as hell
JOIN table t ON t.member = COALESCE(@value1, @value2)    -- this is blazing fast
-- Note that here if @value1 has a value, @value2 is NULL, and vice versa

Tôi nhận ra đây có thể không phải là vấn đề của mọi người, nhưng bằng cách làm nổi bật độ nhạy của các mệnh đề ON, nó có thể giúp bạn nhìn đúng hướng. Trong mọi trường hợp, văn bản gốc có ở đây cho các nhà nhân học trong tương lai:

Văn bản gốc

Hãy xem xét các truy vấn đơn giản sau đây (chỉ có 3 bảng liên quan)

    SELECT

        l.sku_id AS ProductId,
        l.is_primary AS IsPrimary,
        v1.category_name AS Category1,
        v2.category_name AS Category2,
        v3.category_name AS Category3,
        v4.category_name AS Category4,
        v5.category_name AS Category5

    FROM category c4
    JOIN category_voc v4 ON v4.category_id = c4.category_id and v4.language_code = 'en'

    JOIN category c3 ON c3.category_id = c4.parent_category_id
    JOIN category_voc v3 ON v3.category_id = c3.category_id and v3.language_code = 'en'

    JOIN category c2 ON c2.category_id = c3.category_id
    JOIN category_voc v2 ON v2.category_id = c2.category_id and v2.language_code = 'en'

    JOIN category c1 ON c1.category_id = c2.parent_category_id
    JOIN category_voc v1 ON v1.category_id = c1.category_id and v1.language_code = 'en'

    LEFT OUTER JOIN category c5 ON c5.parent_category_id = c4.category_id
    LEFT OUTER JOIN category_voc v5 ON v5.category_id = c5.category_id and v5.language_code = @lang

    JOIN category_link l on l.sku_id IN (SELECT value FROM #Ids) AND
    (
        l.category_id = c4.category_id OR
        l.category_id = c5.category_id
    )

    WHERE c4.[level] = 4 AND c4.version_id = 5

Đây là một truy vấn khá đơn giản, phần khó hiểu duy nhất là tham gia danh mục cuối cùng, theo cách này vì loại 5 có thể tồn tại hoặc không tồn tại. Ở cuối truy vấn tôi đang tìm kiếm thông tin danh mục cho mỗi ID sản phẩm (ID SKU) và đó là nơi mà bảng_link rất lớn xuất hiện. Cuối cùng, bảng #Ids chỉ là một bảng tạm thời chứa 10.000 Id.

Khi được thực thi, tôi nhận được kế hoạch thực hiện thực tế sau đây:

Kế hoạch thực hiện

Như bạn có thể thấy, gần 90% thời gian được dành cho các Vòng lặp lồng nhau (Tham gia bên trong). Dưới đây là thông tin thêm về các vòng lặp lồng nhau:

Các vòng lặp lồng nhau (Tham gia bên trong)

Lưu ý rằng tên bảng không khớp chính xác vì tôi đã chỉnh sửa tên bảng truy vấn để dễ đọc, nhưng nó khá dễ khớp (ads_alt_carget = category). Có cách nào để tối ưu hóa truy vấn này? Cũng lưu ý rằng trong sản xuất, bảng tạm thời #Ids không tồn tại, đó là Thông số có giá trị của Bảng trong cùng 10.000.000 Id được chuyển cho Quy trình được lưu trữ.

Thông tin bổ sung:

  • các chỉ mục danh mục trên category_id và Parent_carget_id
  • chỉ mục category_voc trên category_id, ngôn ngữ_code
  • index_link index trên sku_id, category_id

Chỉnh sửa (đã giải quyết)

Như được chỉ ra bởi câu trả lời được chấp nhận, vấn đề là mệnh đề OR trong category_link THAM GIA. Tuy nhiên, mã được đề xuất trong câu trả lời được chấp nhận là rất chậm, chậm hơn cả mã gốc. Một giải pháp nhanh hơn và cũng sạch hơn nhiều chỉ đơn giản là thay thế điều kiện THAM GIA hiện tại bằng cách sau:

JOIN category_link l on l.sku_id IN (SELECT value FROM @p1) AND l.category_id = COALESCE(c5.category_id, c4.category_id)

Điều chỉnh phút này là giải pháp nhanh nhất, được thử nghiệm đối với phép nối kép từ câu trả lời được chấp nhận và cũng được thử nghiệm với CROSS ỨNG DỤNG theo đề xuất của valverij.


Chúng ta sẽ cần xem phần còn lại của kế hoạch truy vấn.
RBarryYoung

Chỉ cần một nhận xét: với nhiều người phụ thuộc tham gia các lỗi ước tính cardinality có thể xảy ra. Thông thường, hiệu năng truy vấn bị trật bánh bởi đánh giá thấp cardinality.
usr

Liệu kế hoạch thực hiện có gợi ý cho các chỉ mục? Ngoài ra, đừng quên rằng bạn có thể đặt các khóa và chỉ mục chính trên các bảng tạm thời của mình (thông tin thêm ở đây )

@rbarry Nếu sau khi thử các giải pháp hiện tại tôi không nhận được gì, tôi sẽ cải thiện câu hỏi

1
Điều gì về việc sao chép truy vấn với UNION và thoát khỏi OR

Câu trả lời:


17

Vấn đề dường như nằm ở phần này của mã:

JOIN category_link l on l.sku_id IN (SELECT value FROM #Ids) AND
(
    l.category_id = c4.category_id OR
    l.category_id = c5.category_id
)

ortrong điều kiện tham gia luôn luôn đáng ngờ. Một gợi ý là chia phần này thành hai phần:

JOIN category_link l1 on l1.sku_id in (SELECT value FROM #Ids) and l1.category_id = cr.category_id
left outer join
category_link l1 on l2.sku_id in (SELECT value FROM #Ids) and l2.category_id = cr.category_id

Sau đó, bạn phải sửa đổi phần còn lại của truy vấn để xử lý việc này. . . coalesce(l1.sku_id, l2.sku_id)ví dụ trong selectmệnh đề.


Với số lần lọc được thực hiện trên mà đặc biệt là tham gia, tôi cũng muốn thử thay đổi JOINmột CROSS APPLYvới INchuyển đổi sang một EXISTStrong APPLY's WHEREkhoản.

Cảm ơn Gordon, tôi sẽ kiểm tra điều này đầu tiên vào buổi sáng. @Valverij, tôi không quen với việc áp dụng chéo, bạn có thể mô tả giải pháp của bạn nhiều hơn không, có thể trong một Câu trả lời thích hợp, vì vậy tôi có thể bỏ phiếu nếu nó trở thành kịch bản nhanh nhất?

3
Tôi chấp nhận câu trả lời này vì đó là câu trả lời đầu tiên chỉ ra vấn đề của tôi. Tuy nhiên, giải pháp được đề xuất là cực kỳ chậm, chậm hơn cả mã gốc. Tuy nhiên, biết rằng mệnh đề OR là vấn đề, chỉ cần thay thế nó bằng cách ON l.category_id = ISNULL(c5.category_id, c4.category_idthực hiện thủ thuật.
Luis Ferrao

1
@LuisFerrao. . . Cảm ơn bạn đã thông tin thêm. Nó rất hữu ích để biết rằng coalesce()đẩy bộ tối ưu hóa đúng hướng.
Gordon Linoff

9

Như một người dùng khác đã đề cập, việc tham gia này có thể là nguyên nhân:

JOIN category_link l on l.sku_id IN (SELECT value FROM #Ids) AND
(
    l.category_id = c4.category_id OR
    l.category_id = c5.category_id
)

Bên cạnh việc chia chúng ra thành nhiều phép nối, bạn cũng có thể thử CROSS APPLY

CROSS APPLY (
    SELECT [some column(s)]
    FROM category_link x
    WHERE EXISTS(SELECT value FROM #Ids WHERE value = x.sku_id)
    AND (x.category_id = c4.category_id OR x.category_id = c5.category_id)        
) l

Từ liên kết MSDN ở trên:

Hàm có giá trị bảng đóng vai trò là đầu vào bên phải và biểu thức bảng bên ngoài đóng vai trò là đầu vào bên trái. Đầu vào bên phải được ước tính cho mỗi hàng từ đầu vào bên trái và các hàng được tạo ra được kết hợp cho đầu ra cuối cùng .

Về cơ bản, APPLYgiống như một truy vấn con lọc các bản ghi ở bên phải trước, sau đó áp dụng chúng cho phần còn lại của truy vấn của bạn.

Bài viết này thực hiện rất tốt việc giải thích nó là gì và khi nào sử dụng nó: http://explainextends.com/2009/07/16/inner-join-vs-cross-apply/

Tuy nhiên, điều quan trọng cần lưu ý là CROSS APPLYkhông phải lúc nào cũng thực hiện nhanh hơn một INNER JOIN. Trong nhiều tình huống, nó có thể sẽ giống nhau. Tuy nhiên, trong những trường hợp hiếm hoi, tôi thực sự đã thấy nó chậm hơn (một lần nữa, tất cả phụ thuộc vào cấu trúc bảng của bạn và chính truy vấn).

Theo nguyên tắc chung, nếu tôi thấy mình tham gia vào một bảng có quá nhiều câu lệnh có điều kiện, thì tôi có xu hướng nghiêng về phía APPLY

Cũng là một lưu ý thú vị: OUTER APPLYsẽ hành động như mộtLEFT JOIN

Ngoài ra, xin vui lòng lưu ý lựa chọn của tôi để sử dụng EXISTShơn là IN. Khi thực hiện INmột truy vấn con, hãy nhớ rằng nó sẽ trả về toàn bộ tập kết quả, ngay cả khi nó đã tìm thấy giá trị của bạn. Với EXISTSMặc dù vậy, nó sẽ ngăn chặn các subquery ngay lập tức nó tìm thấy một trận đấu.


Tôi đã thử nghiệm giải pháp này kỹ lưỡng. Khi bạn viết nó, nó khá chậm, nhưng bạn đã quên áp dụng lời khuyên mà bạn đã bắt đầu tin nhắn của mình. Thay thế AND x.cat = c4.cat OR x.cat = c5.catbằng x.cat = ISNULL(c5.cat, c4.cat)và loại bỏ các điều khoản trong thực hiện điều này là giải pháp nhanh nhất thứ hai, và xứng đáng với một phiếu bầu tán thành, bởi vì nó khá nhiều thông tin.
Luis Ferrao

Cảm ơn. Dòng IN thực sự không được phép ở đó (không thể quyết định sử dụng IN hoặc gắn bó với OR), tôi sẽ xóa nó.
valverij
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.