MySQL: Optimalkan UNION dengan "ORDER BY" di pertanyaan dalam

9

Saya baru saja membuat sistem logging yang terdiri dari beberapa tabel dengan tata letak yang sama.

Ada satu tabel untuk setiap sumber data.

Untuk penampil log, saya mau

  • UNION semua tabel log ,
  • saring dengan akun ,
  • tambahkan kolom semu untuk identifikasi sumber,
  • urutkan berdasarkan waktu ,
  • dan batasi untuk pagination .

Semua tabel berisi bidang yang disebut zeitpunktkolom tanggal / waktu yang diindeks.

Upaya pertama saya adalah:

(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;

Pengoptimal tidak dapat menggunakan indeks di sini karena semua baris dari kedua tabel dikembalikan oleh subqueries dan diurutkan setelah UNION.

Solusi saya adalah sebagai berikut:

(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;

Saya mengharapkan mesin kueri akan menggunakan indeks di sini karena kedua subquery harus diurutkan dan dibatasi sebelumnya UNION, yang kemudian menggabungkan dan mengurutkan baris.

Saya benar-benar berpikir ini akan menjadi itu, tetapi menjalankan EXPLAINkueri memberitahu saya subqueries masih mencari kedua tabel.

EXPLAINingsubqueries itu sendiri menunjukkan kepada saya optimasi yang diinginkan tetapi UNIONingmereka bersama-sama tidak.

Apakah saya melewatkan sesuatu?

Saya tahu bahwa ORDER BYklausa di dalam UNIONsubqueries diabaikan tanpa LIMIT, tetapi ada batasnya.

Sunting:
Sebenarnya, mungkin juga akan ada permintaan tanpaaccount_idsyarat.

Tabel sudah ada dan diisi dengan data. Mungkin ada perubahan dalam tata letak tergantung pada sumbernya sehingga saya ingin membuat mereka terbagi. Selain itu, klien logging menggunakan kredensial berbeda karena suatu alasan.

Saya harus menyimpan semacam layer antara pembaca log dan tabel aktual.

Berikut adalah rencana eksekusi untuk seluruh kueri dan subquery pertama serta tata letak tabel secara rinci:

https://gist.github.com/ca8fc1093cd95b1c6fc0

Lukas
sumber
1
Indeks terbaik untuk ini adalah senyawa (account_id, zeitpunkt). Apakah Anda memiliki indeks seperti itu? Yang terbaik adalah (saya pikir) single (zeitpunkt)- tetapi efisiensi jika yang digunakan tergantung pada seberapa sering baris account_id=730muncul.
ypercubeᵀᴹ
2
Dan mengapa UNION DISTINCT? Tidak perlu memaksakan pengurutan dan perbedaan di sana, karena hasilnya akan berbeda di seluruh subkueri, karena tambahan, kolom identifikasi. Gunakan UNION ALL.
ypercubeᵀᴹ
1
Selain saran @ ypercube, saya punya pertanyaan: bukankah akan lebih baik untuk memiliki semua log di tabel yang sama, dengan penambahan sourcekolom? Dengan cara ini Anda dapat menghindari UNIONdan menggunakan indeks di semua data Anda.
dezso
1
@ ypercube Sebenarnya, mungkin juga akan ada kueri tanpa kondisi account_id . The DISTINCT bendera adalah randa dari mencoba sebelumnya dan sebenarnya sia-sia karena hasilnya akan selalu berbeda dan karena DISTINCT adalah perilaku dafualt. Tabel sudah ada dan diisi dengan data. Ngomong-ngomong, mungkin ada perubahan dalam tata letak tergantung pada sumbernya jadi saya ingin membuat mereka terbagi. Selain itu, klien logging menggunakan kredensial berbeda karena suatu alasan. Saya harus menyimpan semacam layer antara pembaca log dan tabel aktual.
Lukas
OK, tapi periksa apakah berubah untuk UNION ALLmenghasilkan rencana eksekusi yang berbeda.
ypercubeᵀᴹ

Jawaban:

8

Karena penasaran, dapatkah Anda mencoba versi ini? Mungkin menipu pengoptimal untuk menggunakan indeks yang sama yang akan digunakan subqueries secara terpisah:

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;

Saya masih berpikir bahwa indeks terbaik yang bisa Anda miliki adalah senyawa (account_id, zeitpunkt). Ini akan menghasilkan 10 baris dengan cepat, dan tidak ada trik yang diperlukan.

ypercubeᵀᴹ
sumber
Modifikasi Anda ternyata membawa hasil yang diinginkan. Terima kasih! Sama seperti catatan tambahan: sekarang saya tidak yakin indeks mana yang lebih baik. Saya bahkan bisa menggunakan keduanya. Saya harus memeriksa bagaimana jumlah pengguna dan log entries / userskala kemauan.
Lukas
Jika Anda membutuhkan kueri dengan dan tanpa kueri account_id=?, pertahankan keduanya.
ypercubeᵀᴹ
@ ypercube, +1 ini sangat pintar dan bekerja dalam situasi saya (serupa) juga! Bisakah Anda jelaskan mengapa membungkus kueri gabungan dalam SELECT * FROMtrik tiruan MySQL agar menggunakan indeks?
dkamins
@dkamins: Pengoptimal MySQL tidak terlalu pintar, biasanya ketika ada tabel turunan seperti di sini (SELECT ...) AS a, ia mencoba untuk mengevaluasi dan mengoptimalkan tabel turunan secara terpisah dari tabel turunan lainnya dan kemudian seluruh kueri.
ypercubeᵀᴹ
@Lukas, Sebenarnya karena Anda perlu memastikan bahwa indeks digunakan, menggunakan / menambahkan force indexakan memberi Anda solusi yang lebih baik.
Pacerier