Mysql
 sql >> Teknologi Basis Data >  >> RDS >> Mysql

Bagaimana saya bisa lebih mengoptimalkan kueri tabel turunan yang berkinerja lebih baik daripada yang setara BERGABUNG?

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



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. INDIA, Skrip Pencari Kode STD di PHP, MYSQL, JQUERY

  2. mysql pilih stempel waktu antara a dan b mengembalikan semua atau 0 stempel waktu

  3. Mengintegrasikan MySQL dengan Python di Windows

  4. Kesalahan WAMP/MySQL tidak dalam bahasa yang benar

  5. Pelanggaran Kendala saat mempertahankan hubungan One To Many