MySQL: Tối ưu hóa UNION với OR OR BY BY BY trong các truy vấn bên trong


9

Tôi chỉ thiết lập một hệ thống ghi nhật ký bao gồm nhiều bảng có cùng bố cục.

Có một bảng cho mỗi nguồn dữ liệu.

Đối với người xem nhật ký, tôi muốn

  • UNION tất cả các bảng nhật ký ,
  • lọc chúng bằng tài khoản ,
  • thêm một cột giả để xác định nguồn,
  • sắp xếp chúng theo thời gian ,
  • giới hạn chúng để phân trang .

Tất cả các bảng có chứa một trường được gọi zeitpunktlà cột ngày / thời gian được lập chỉ mục.

Nỗ lực đầu tiên của tôi là:

(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt AS zeit,
 'hp' AS source FROM is_log AS l WHERE l.account_id = 730)

UNION

(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt,
 'ig' AS source FROM ig_is_log AS l WHERE l.account_id = 730)

ORDER BY zeit DESC LIMIT 10;

Trình tối ưu hóa không thể sử dụng các chỉ mục ở đây vì tất cả các hàng từ cả hai bảng được trả về bởi các truy vấn con và được sắp xếp sau UNION.

Cách giải quyết của tôi là như sau:

(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt AS zeit,
 'hp' AS source FROM is_log AS l WHERE l.account_id = 730
 ORDER BY l.zeitpunkt DESC LIMIT 10)

UNION

(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt,
 'ig' AS source FROM ig_is_log AS l WHERE l.account_id = 730
 ORDER BY l.zeitpunkt DESC LIMIT 10)

ORDER BY zeit DESC LIMIT 10;

Tôi đã mong đợi công cụ truy vấn sẽ sử dụng các chỉ mục ở đây vì cả hai truy vấn con nên được sắp xếp và giới hạn trước UNIONđó, sau đó hợp nhất và sắp xếp các hàng.

Tôi thực sự nghĩ rằng đây sẽ là nó, nhưng chạy EXPLAINtrên truy vấn cho tôi biết các truy vấn con vẫn tìm kiếm cả hai bảng.

EXPLAINingbản thân các truy vấn con cho tôi thấy sự tối ưu hóa mong muốn nhưng UNIONingchúng không thực hiện được.

Tôi đã bỏ lỡ một cái gì đó?

Tôi biết rằng ORDER BYcác mệnh đề trong UNIONcác truy vấn con được bỏ qua mà không có a LIMIT, nhưng có một giới hạn.

Chỉnh sửa:
Trên thực tế, có lẽ cũng sẽ có các truy vấn mà không cóaccount_idđiều kiện.

Các bảng đã tồn tại và chứa đầy dữ liệu. Có thể có những thay đổi trong cách bố trí tùy thuộc vào nguồn nên tôi muốn chia chúng. Ngoài ra, các khách hàng đăng nhập sử dụng các thông tin khác nhau vì một lý do.

Tôi phải giữ một loại lớp giữa các trình đọc nhật ký và các bảng thực tế.

Dưới đây là các kế hoạch thực hiện cho toàn bộ truy vấn và truy vấn con đầu tiên cũng như cách bố trí bảng chi tiết:

https://gist.github.com/ca8fc1093cd95b1c6fc0


1
Chỉ số tốt nhất cho điều này sẽ là hợp chất (account_id, zeitpunkt). Bạn có một chỉ số như vậy? Điều tốt nhất thứ hai sẽ là (tôi nghĩ) đơn (zeitpunkt)- nhưng hiệu quả nếu điều đó được sử dụng phụ thuộc vào tần suất account_id=730xuất hiện của các hàng .
ypercubeᵀᴹ

2
Và tại sao UNION DISTINCT? Không cần thiết phải sắp xếp và phân biệt ở đó, vì kết quả sẽ khác nhau giữa các truy vấn con, do cột nhận dạng phụ. Sử dụng UNION ALL.
ypercubeᵀᴹ

1
Ngoài đề xuất của @ ypercube, tôi có một câu hỏi: sẽ tốt hơn nếu có tất cả các nhật ký đó trong cùng một bảng, với việc thêm sourcecột? Bằng cách này, bạn có thể tránh UNIONs và sử dụng (các) chỉ mục trên tất cả dữ liệu của mình.
dezso

1
@ypercube Trên thực tế, có lẽ cũng sẽ có các truy vấn mà không có điều kiện account_id . Các DISTINCT cờ là một đang bị diệt vong của một cố gắng trước đó và thực sự là vô dụng vì kết quả sẽ luôn luôn khác nhau và vì DISTINCT là hành vi dafualt. Các bảng đã tồn tại và chứa đầy dữ liệu. Dù sao, có thể có những thay đổi trong cách bố trí tùy thuộc vào nguồn nên tôi muốn giữ chúng được chia. Ngoài ra, các khách hàng đăng nhập sử dụng các thông tin khác nhau vì một lý do. Tôi phải giữ một loại lớp giữa các trình đọc nhật ký và các bảng thực tế.
Lukas

OK, nhưng kiểm tra nếu thay đổi để UNION ALLmang lại kế hoạch thực hiện khác nhau.
ypercubeᵀᴹ

Câu trả lời:


7

Vì tò mò, bạn có thể thử phiên bản này không? Nó có thể lừa trình tối ưu hóa để sử dụng cùng các chỉ mục mà các truy vấn con sẽ sử dụng riêng:

SELECT *
FROM
(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt AS zeit,
 'hp' AS source FROM is_log AS l WHERE l.account_id = 730
 ORDER BY l.zeitpunkt DESC LIMIT 10) 
    AS a

UNION ALL

SELECT *
FROM
(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt,
 'ig' AS source FROM ig_is_log AS l WHERE l.account_id = 730
 ORDER BY l.zeitpunkt DESC LIMIT 10)
    AS b

ORDER BY zeit DESC LIMIT 10;

Tôi vẫn nghĩ rằng chỉ số tốt nhất bạn có thể có là hợp chất (account_id, zeitpunkt). Nó sẽ mang lại 10 hàng nhanh chóng và không cần thủ thuật nào.


Sửa đổi của bạn hóa ra để mang lại kết quả mong muốn. Cảm ơn! Cũng như một lưu ý phụ: đến bây giờ tôi không chắc chỉ số nào sẽ tốt hơn. Tôi thậm chí có thể sử dụng cả hai. Tôi sẽ phải kiểm tra số lượng người dùng và log entries / userquy mô sẽ như thế nào .
Lukas

Nếu bạn sẽ cần truy vấn và không có truy vấn account_id=?, hãy giữ cả hai.
ypercubeᵀᴹ

@ypercube, +1 cái này rất thông minh và cũng hoạt động trong tình huống (tương tự) của tôi quá! Bạn có thể giải thích lý do tại sao gói các truy vấn hợp nhất trong một SELECT * FROMthủ thuật giả mạo MySQL sử dụng các chỉ mục không?
dk vitamin

@dk vitamin: Trình tối ưu hóa MySQL không thông minh lắm, thông thường khi có bảng dẫn xuất như ở đây (SELECT ...) AS a, nó cố gắng đánh giá và tối ưu hóa bảng dẫn xuất riêng biệt với các bảng dẫn xuất khác và sau đó là toàn bộ truy vấn.
ypercubeᵀᴹ

@Lukas, Trên thực tế vì bạn cần đảm bảo rằng chỉ mục được sử dụng, sử dụng / thêm force indexsẽ cung cấp cho bạn một giải pháp tốt hơn.
Pacerier
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.