Ini adalah pertanyaan yang sangat menarik. Selama pengoptimalannya, Anda mungkin menemukan dan memahami banyak informasi baru tentang cara kerja MySQL. Saya tidak yakin bahwa saya akan punya waktu untuk menulis semuanya secara detail sekaligus, tetapi saya dapat memperbaruinya secara bertahap.
Mengapa lambat
Pada dasarnya ada dua skenario:cepat dan lambat .
Dalam cepat skenario Anda berjalan dalam urutan yang telah ditentukan sebelumnya di atas meja dan mungkin pada saat yang sama dengan cepat mengambil beberapa data dengan id untuk setiap baris dari tabel lain. Dalam hal ini Anda berhenti berjalan segera setelah Anda memiliki cukup baris yang ditentukan oleh klausa LIMIT Anda. Dari mana datangnya pesanan? Dari indeks b-tree yang Anda miliki di tabel atau urutan hasil yang ditetapkan dalam subquery.
Dalam lambat skenario Anda tidak memiliki urutan yang telah ditentukan sebelumnya, dan MySQL harus secara implisit memasukkan semua data ke dalam tabel sementara, mengurutkan tabel pada beberapa bidang dan mengembalikan n baris dari klausa LIMIT Anda. Jika salah satu bidang yang Anda masukkan ke dalam tabel sementara itu bertipe TEXT (bukan VARCHAR), MySQL bahkan tidak mencoba untuk menyimpan tabel itu di RAM dan mem-flush dan mengurutkannya pada disk (karenanya pemrosesan IO tambahan).
Hal pertama yang harus diperbaiki
Ada banyak situasi ketika Anda tidak dapat membuat indeks yang memungkinkan Anda untuk mengikuti urutannya (ketika Anda ORDER BY kolom dari tabel yang berbeda, misalnya), jadi aturan praktis dalam situasi seperti itu adalah meminimalkan data yang akan dimasukkan MySQL dalam tabel sementara. Bagaimana kamu melakukannya? Anda hanya memilih pengidentifikasi baris dalam subkueri dan setelah Anda memiliki id, Anda menggabungkan id ke tabel itu sendiri dan tabel lain untuk mengambil konten. Yaitu Anda membuat meja kecil dengan pesanan dan kemudian menggunakan skenario cepat. (Ini sedikit bertentangan dengan SQL pada umumnya, tetapi setiap rasa SQL memiliki caranya sendiri untuk mengoptimalkan kueri seperti itu).
Secara kebetulan, SELECT -- everything is ok here
terlihat lucu, karena ini adalah tempat pertama yang tidak baik.
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)
;
Itu adalah langkah pertama, tetapi bahkan sekarang Anda dapat melihat bahwa Anda tidak perlu membuat serialisasi LEFT JOINS dan json yang tidak berguna ini untuk baris yang tidak Anda perlukan. (Saya melewatkan GROUP BY p.id
, karena saya tidak melihat LEFT JOIN mana yang mungkin menghasilkan beberapa baris, Anda tidak melakukan agregasi apa pun).
belum menulis tentang:
- indeks
- memformulasi ulang klausa CASE (gunakan UNION ALL)
- mungkin memaksa indeks