Đây là một truy vấn rất thú vị. Trong quá trình tối ưu hóa của nó, bạn có thể khám phá và hiểu được nhiều thông tin mới về cách hoạt động của MySQL. Tôi không chắc mình sẽ có thời gian để viết mọi thứ chi tiết ngay lập tức, nhưng tôi có thể cập nhật dần dần.
Tại sao nó chậm
Về cơ bản có hai tình huống:a nhanh chóng và một chậm .
Trong một nhanh chóng tình huống bạn đang đi theo một số thứ tự được xác định trước trên một bảng và có thể đồng thời nhanh chóng tìm nạp một số dữ liệu theo id cho mỗi hàng từ các bảng khác. Trong trường hợp này, bạn dừng bước ngay sau khi bạn có đủ số hàng được điều khoản LIMIT chỉ định. Đơn hàng đến từ đâu? Từ chỉ mục cây b mà bạn có trên bảng hoặc thứ tự của kết quả được đặt trong một truy vấn con.
Trong một chậm rãi tình huống bạn không có thứ tự xác định trước đó và MySQL phải đặt tất cả dữ liệu vào một bảng tạm thời, sắp xếp bảng trên một số trường và trả về n hàng từ mệnh đề LIMIT của bạn. Nếu bất kỳ trường nào bạn đưa vào bảng tạm thời đó thuộc loại TEXT (không phải VARCHAR), MySQL thậm chí không cố gắng giữ bảng đó trong RAM và xóa và sắp xếp nó trên đĩa (do đó xử lý IO bổ sung).
Điều đầu tiên cần khắc phục
Có nhiều tình huống khi bạn không thể xây dựng một chỉ mục cho phép bạn tuân theo thứ tự của nó (ví dụ:khi bạn ĐẶT HÀNG THEO các cột từ các bảng khác nhau), vì vậy quy tắc ngón tay cái trong những tình huống như vậy là giảm thiểu dữ liệu mà MySQL sẽ đưa vào trong bảng tạm thời. Làm thế nào bạn có thể làm điều đó? Bạn chỉ chọn số nhận dạng của các hàng trong một truy vấn con và sau khi có id, bạn kết hợp các id với chính bảng đó và các bảng khác để tìm nạp nội dung. Đó là bạn làm một bảng nhỏ với một đơn đặt hàng và sau đó sử dụng kịch bản nhanh. (Điều này hơi mâu thuẫn với SQL nói chung, nhưng mỗi phiên bản của SQL có phương tiện riêng để tối ưu hóa các truy vấn theo cách đó).
Thật trùng hợp, SELECT -- everything is ok here
trông buồn cười, vì đây là nơi đầu tiên không ổn.
SELECT p.*
, u.name user_name, u.status user_status
, c.name city_name, t.name town_name, d.name dist_name
, pm.meta_name, pm.meta_email, pm.meta_phone
, (SELECT concat("{",
'"id":"', pc.id, '",',
'"content":"', replace(pc.content, '"', '\\"'), '",',
'"date":"', pc.date, '",',
'"user_id":"', pcu.id, '",',
'"user_name":"', pcu.name, '"}"') last_comment_json
FROM post_comments pc
LEFT JOIN users pcu ON (pcu.id = pc.user_id)
WHERE pc.post_id = p.id
ORDER BY pc.id DESC LIMIT 1) AS last_comment
FROM (
SELECT id
FROM posts p
WHERE p.status = 'published'
ORDER BY
(CASE WHEN p.created_at >= unix_timestamp(now() - INTERVAL p.reputation DAY)
THEN +p.reputation ELSE NULL END) DESC,
p.id DESC
LIMIT 0,10
) ids
JOIN posts p ON ids.id = p.id -- mind the join for the p data
LEFT JOIN users u ON (u.id = p.user_id)
LEFT JOIN citys c ON (c.id = p.city_id)
LEFT JOIN towns t ON (t.id = p.town_id)
LEFT JOIN dists d ON (d.id = p.dist_id)
LEFT JOIN post_metas pm ON (pm.post_id = p.id)
;
Đó là bước đầu tiên, nhưng ngay cả bây giờ bạn có thể thấy rằng bạn không cần phải thực hiện các tuần tự LEFT JOINS và json vô dụng này cho các hàng bạn không cần. (Tôi đã bỏ qua GROUP BY p.id
, bởi vì tôi không thấy LEFT JOIN có thể dẫn đến một số hàng, bạn không thực hiện bất kỳ tổng hợp nào).
chưa viết về:
- chỉ mục
- định dạng lại mệnh đề CASE (sử dụng UNION ALL)
- có thể buộc một chỉ mục