Nah, saya menemukan solusi. Butuh banyak eksperimen, dan menurut saya sedikit keberuntungan, tapi ini dia:
CREATE TABLE magic ENGINE=MEMORY
SELECT
s.shop_id AS shop_id,
s.id AS shift_id,
st.dow AS dow,
st.start AS start,
st.end AS end,
su.user_id AS manager_id
FROM shifts s
JOIN shift_times st ON s.id = st.shift_id
JOIN shifts_users su ON s.id = su.shift_id
JOIN shift_positions sp ON su.shift_position_id = sp.id AND sp.level = 1
ALTER TABLE magic ADD INDEX (shop_id, dow);
CREATE TABLE tickets_extra ENGINE=MyISAM
SELECT
t.id AS ticket_id,
(
SELECT m.manager_id
FROM magic m
WHERE DAYOFWEEK(t.created) = m.dow
AND TIME(t.created) BETWEEN m.start AND m.end
AND m.shop_id = t.shop_id
) AS manager_created,
(
SELECT m.manager_id
FROM magic m
WHERE DAYOFWEEK(t.resolved) = m.dow
AND TIME(t.resolved) BETWEEN m.start AND m.end
AND m.shop_id = t.shop_id
) AS manager_resolved
FROM tickets t;
DROP TABLE magic;
Penjelasan Panjang
Sekarang, saya akan menjelaskan mengapa ini berhasil, dan kerabat saya melalui proses dan langkah-langkah untuk sampai ke sini.
Pertama, saya tahu kueri yang saya coba menderita karena tabel turunan yang sangat besar, dan selanjutnya GABUNG ke ini. Saya mengambil tabel tiket saya yang diindeks dengan baik dan menggabungkan semua data shift_times ke dalamnya, lalu membiarkan MySQL mengunyahnya saat mencoba bergabung dengan tabel shift dan shift_positions. Raksasa turunan ini akan menjadi kekacauan tak terindeks sebanyak 2 juta baris.
Sekarang, saya tahu ini sedang terjadi. Alasan saya mengambil jalan ini adalah karena cara "tepat" untuk melakukan ini, menggunakan GABUNG secara ketat membutuhkan waktu yang lebih lama. Ini karena sedikit kekacauan yang diperlukan untuk menentukan siapa manajer shift yang diberikan. Saya harus bergabung ke shift_times untuk mencari tahu apa itu shift yang benar, sementara secara bersamaan bergabung ke shift_positions untuk mengetahui level pengguna. Saya tidak berpikir pengoptimal MySQL menangani ini dengan sangat baik, dan akhirnya membuat tabel gabungan sementara yang sangat besar, lalu memfilter apa yang tidak berlaku.
Jadi, karena tabel turunan tampaknya menjadi "jalan yang harus ditempuh", saya dengan keras kepala bertahan dalam hal ini untuk sementara waktu. Saya mencoba memasukkannya ke dalam klausa GABUNG, tidak ada perbaikan. Saya mencoba membuat tabel sementara dengan tabel turunan di dalamnya, tetapi sekali lagi itu terlalu lambat karena tabel temp tidak diindeks.
Saya menyadari bahwa saya harus menangani perhitungan shift, waktu, posisi ini dengan bijaksana. Saya pikir, mungkin VIEW akan menjadi cara untuk pergi. Bagaimana jika saya membuat VIEW yang berisi informasi ini:(shop_id, shift_id, dow, start, end, manager_id). Kemudian, saya hanya perlu bergabung dengan tabel tiket berdasarkan shop_id dan seluruh perhitungan DAYOFWEEK/TIME, dan saya akan berbisnis. Tentu saja, saya gagal untuk mengingat bahwa MySQL menangani VIEW lebih mudah. Itu tidak mewujudkannya sama sekali, itu hanya menjalankan kueri yang akan Anda gunakan untuk mendapatkan tampilan untuk Anda. Jadi dengan menggabungkan tiket ke ini, pada dasarnya saya menjalankan kueri asli saya - tidak ada peningkatan.
Jadi, alih-alih VIEW saya memutuskan untuk menggunakan TABEL SEMENTARA. Ini bekerja dengan baik jika saya hanya mengambil salah satu manajer (dibuat atau diselesaikan) pada satu waktu, tetapi masih cukup lambat. Juga, saya menemukan bahwa dengan MySQL Anda tidak dapat merujuk ke tabel yang sama dua kali dalam kueri yang sama (saya harus bergabung dengan tabel sementara saya dua kali untuk dapat membedakan antara manager_created dan manager_resolved). Ini adalah WTF yang besar, karena saya dapat melakukannya selama saya tidak menentukan "TEMPORARY" - di sinilah CREATE TABLE magic ENGINE=MEMORY berperan.
Dengan tabel sementara semu ini, saya mencoba GABUNG hanya untuk manager_created lagi. Itu dilakukan dengan baik, tapi masih agak lambat. Namun, ketika saya BERGABUNG lagi untuk mendapatkan manager_resolved dalam kueri yang sama, waktu kueri kembali naik ke stratosfer. Melihat EXPLAIN menunjukkan pemindaian tabel penuh tiket (baris ~2 juta), seperti yang diharapkan, dan JOIN ke tabel ajaib masing-masing ~2.087. Sekali lagi, sepertinya saya akan gagal.
Saya sekarang mulai berpikir tentang bagaimana menghindari GABUNG sama sekali dan saat itulah saya menemukan beberapa posting papan pesan kuno yang tidak jelas di mana seseorang menyarankan menggunakan subpilihan (tidak dapat menemukan tautan dalam riwayat saya). Inilah yang menyebabkan kueri SELECT kedua yang ditunjukkan di atas (pembuatan ticket_extra). Dalam hal memilih hanya satu bidang manajer, itu berkinerja baik, tetapi sekali lagi dengan keduanya itu omong kosong. Saya melihat EXPLAIN dan melihat ini:
*************************** 1. row ***************************
id: 1
select_type: PRIMARY
table: t
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 173825
Extra:
*************************** 2. row ***************************
id: 3
select_type: DEPENDENT SUBQUERY
table: m
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 2037
Extra: Using where
*************************** 3. row ***************************
id: 2
select_type: DEPENDENT SUBQUERY
table: m
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 2037
Extra: Using where
3 rows in set (0.00 sec)
Ack, SUBQUERY TERGANTUNG yang ditakuti. Ini sering disarankan untuk menghindari ini, karena MySQL biasanya akan mengeksekusinya dengan cara luar-dalam, mengeksekusi kueri dalam untuk setiap baris luar. Saya mengabaikan ini, dan bertanya-tanya:"Yah... bagaimana jika saya baru saja mengindeks tabel ajaib bodoh ini?". Dengan demikian, indeks ADD (shop_id, dow) lahir.
Lihat ini:
mysql> CREATE TABLE magic ENGINE=MEMORY
<snip>
Query OK, 3220 rows affected (0.40 sec)
mysql> ALTER TABLE magic ADD INDEX (shop_id, dow);
Query OK, 3220 rows affected (0.02 sec)
mysql> CREATE TABLE tickets_extra ENGINE=MyISAM
<snip>
Query OK, 1933769 rows affected (24.18 sec)
mysql> drop table magic;
Query OK, 0 rows affected (0.00 sec)
Sekarang ITU apa yang saya bicarakan!
Kesimpulan
Ini jelas merupakan pertama kalinya saya membuat tabel non-TEMPORARY dengan cepat, dan mengindeksnya dengan cepat, hanya untuk melakukan satu kueri secara efisien. Saya kira saya selalu berasumsi bahwa menambahkan indeks dengan cepat adalah operasi yang sangat mahal. (Menambahkan indeks di tabel tiket saya yang terdiri dari 2 juta baris dapat memakan waktu lebih dari satu jam). Namun, hanya dengan 3.000 baris, ini adalah cakewalk.
Jangan takut dengan SUBQUERI TERGANTUNG, membuat tabel SEMENTARA yang sebenarnya tidak, mengindeks dengan cepat, atau alien. Semuanya bisa menjadi hal yang baik dalam situasi yang tepat.
Terima kasih atas semua bantuannya StackOverflow. :-D