MySQL: delete ... where..in () vs delete..from..join, dan tabel terkunci di delete dengan subselect

9

Penafian: mohon alasan kurangnya pengetahuan saya tentang database internal. Ini dia:

Kami menjalankan aplikasi (tidak ditulis oleh kami) yang memiliki masalah kinerja besar dalam pekerjaan pembersihan berkala dalam database. Kueri terlihat seperti ini:

delete from VARIABLE_SUBSTITUTION where BUILDRESULTSUMMARY_ID in (
       select BUILDRESULTSUMMARY_ID from BUILDRESULTSUMMARY
       where BUILDRESULTSUMMARY.BUILD_KEY = "BAM-1");

Terus terang, mudah dibaca, dan SQL standar. Namun sayangnya sangat lambat. Menjelaskan kueri menunjukkan bahwa indeks yang ada aktif VARIABLE_SUBSTITUTION.BUILDRESULTSUMMARY_IDtidak digunakan:

mysql> explain delete from VARIABLE_SUBSTITUTION where BUILDRESULTSUMMARY_ID in (
    ->        select BUILDRESULTSUMMARY_ID from BUILDRESULTSUMMARY
    ->        where BUILDRESULTSUMMARY.BUILD_KEY = "BAM-1");
| id | select_type        | table                 | type            | possible_keys                    | key     | key_len | ref  | rows    | Extra       |
+----+--------------------+-----------------------+-----------------+----------------------------------+---------+---------+------+---------+-------------+
|  1 | PRIMARY            | VARIABLE_SUBSTITUTION | ALL             | NULL                             | NULL    | NULL    | NULL | 7300039 | Using where |
|  2 | DEPENDENT SUBQUERY | BUILDRESULTSUMMARY    | unique_subquery | PRIMARY,key_number_results_index | PRIMARY | 8       | func |       1 | Using where |

Ini membuatnya sangat lambat (120 detik dan lebih). Selain itu, tampaknya memblokir permintaan yang mencoba untuk memasukkan BUILDRESULTSUMMARY, keluaran dari show engine innodb status:

---TRANSACTION 68603695, ACTIVE 157 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s)
MySQL thread id 127964, OS thread handle 0x7facd0670700, query id 956555826 localhost 127.0.0.1 bamboosrv updating
update BUILDRESULTSUMMARY set CREATED_DATE='2015-06-18 09:22:05', UPDATED_DATE='2015-06-18 09:22:32', BUILD_KEY='BLA-RELEASE1-JOB1', BUILD_NUMBER=8, BUILD_STATE='Unknown', LIFE_CYCLE_STATE='InProgress', BUILD_DATE='2015-06-18 09:22:31.792', BUILD_CANCELLED_DATE=null, BUILD_COMPLETED_DATE='2015-06-18 09:52:02.483', DURATION=1770691, PROCESSING_DURATION=1770691, TIME_TO_FIX=null, TRIGGER_REASON='com.atlassian.bamboo.plugin.system.triggerReason:CodeChangedTriggerReason', DELTA_STATE=null, BUILD_AGENT_ID=199688199, STAGERESULT_ID=230943366, RESTART_COUNT=0, QUEUE_TIME='2015-06-18 09:22:04.52
------- TRX HAS BEEN WAITING 157 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 38 page no 30140 n bits 112 index `PRIMARY` of table `bamboong`.`BUILDRESULTSUMMARY` trx id 68603695 lock_mode X locks rec but not gap waiting
------------------
---TRANSACTION 68594818, ACTIVE 378 sec starting index read
mysql tables in use 2, locked 2
646590 lock struct(s), heap size 63993384, 3775190 row lock(s), undo log entries 117
MySQL thread id 127845, OS thread handle 0x7facc6bf8700, query id 956652201 localhost 127.0.0.1 bamboosrv preparing
delete from VARIABLE_SUBSTITUTION  where BUILDRESULTSUMMARY_ID in   (select BUILDRESULTSUMMARY_ID from BUILDRESULTSUMMARY where BUILDRESULTSUMMARY.BUILD_KEY = 'BLA-BLUBB10-SON')

Ini memperlambat sistem dan memaksa kami untuk meningkatkan innodb_lock_wait_timeout.

Saat kami menjalankan MySQL, kami menulis ulang kueri penghapusan untuk menggunakan "delete from join":

delete VARIABLE_SUBSTITUTION from VARIABLE_SUBSTITUTION join BUILDRESULTSUMMARY
   on VARIABLE_SUBSTITUTION.BUILDRESULTSUMMARY_ID = BUILDRESULTSUMMARY.BUILDRESULTSUMMARY_ID
   where BUILDRESULTSUMMARY.BUILD_KEY = "BAM-1";

Ini sedikit lebih mudah dibaca, sayangnya tidak ada SQL standar (sejauh yang saya bisa mengetahuinya), tetapi jauh lebih cepat (0,02 detik atau lebih) karena menggunakan indeks:

mysql> explain delete VARIABLE_SUBSTITUTION from VARIABLE_SUBSTITUTION join BUILDRESULTSUMMARY
    ->    on VARIABLE_SUBSTITUTION.BUILDRESULTSUMMARY_ID = BUILDRESULTSUMMARY.BUILDRESULTSUMMARY_ID
    ->    where BUILDRESULTSUMMARY.BUILD_KEY = "BAM-1";
| id | select_type | table                 | type | possible_keys                    | key                      | key_len | ref                                                    | rows | Extra                    |
+----+-------------+-----------------------+------+----------------------------------+--------------------------+---------+--------------------------------------------------------+------+--------------------------+
|  1 | SIMPLE      | BUILDRESULTSUMMARY    | ref  | PRIMARY,key_number_results_index | key_number_results_index | 768     | const                                                  |    1 | Using where; Using index |
|  1 | SIMPLE      | VARIABLE_SUBSTITUTION | ref  | var_subst_result_idx             | var_subst_result_idx     | 8       | bamboo_latest.BUILDRESULTSUMMARY.BUILDRESULTSUMMARY_ID |   26 | NULL                     |

Informasi tambahan:

mysql> SHOW CREATE TABLE VARIABLE_SUBSTITUTION;
| Table                 | Create Table |
| VARIABLE_SUBSTITUTION | CREATE TABLE `VARIABLE_SUBSTITUTION` (
  `VARIABLE_SUBSTITUTION_ID` bigint(20) NOT NULL,
  `VARIABLE_KEY` varchar(255) COLLATE utf8_bin NOT NULL,
  `VARIABLE_VALUE` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
  `VARIABLE_TYPE` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  `BUILDRESULTSUMMARY_ID` bigint(20) NOT NULL,
  PRIMARY KEY (`VARIABLE_SUBSTITUTION_ID`),
  KEY `var_subst_result_idx` (`BUILDRESULTSUMMARY_ID`),
  KEY `var_subst_type_idx` (`VARIABLE_TYPE`),
  CONSTRAINT `FK684A7BE0A958B29F` FOREIGN KEY (`BUILDRESULTSUMMARY_ID`) REFERENCES `BUILDRESULTSUMMARY` (`BUILDRESULTSUMMARY_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin |

mysql> SHOW CREATE TABLE BUILDRESULTSUMMARY;
| Table              | Create Table |
| BUILDRESULTSUMMARY | CREATE TABLE `BUILDRESULTSUMMARY` (
  `BUILDRESULTSUMMARY_ID` bigint(20) NOT NULL,
....
  `SKIPPED_TEST_COUNT` int(11) DEFAULT NULL,
  PRIMARY KEY (`BUILDRESULTSUMMARY_ID`),
  KEY `FK26506D3B9E6537B` (`CHAIN_RESULT`),
  KEY `FK26506D3BCCACF65` (`MERGERESULT_ID`),
  KEY `key_number_delta_state` (`DELTA_STATE`),
  KEY `brs_build_state_idx` (`BUILD_STATE`),
  KEY `brs_life_cycle_state_idx` (`LIFE_CYCLE_STATE`),
  KEY `brs_deletion_idx` (`MARKED_FOR_DELETION`),
  KEY `brs_stage_result_id_idx` (`STAGERESULT_ID`),
  KEY `key_number_results_index` (`BUILD_KEY`,`BUILD_NUMBER`),
  KEY `brs_agent_idx` (`BUILD_AGENT_ID`),
  KEY `rs_ctx_baseline_idx` (`VARIABLE_CONTEXT_BASELINE_ID`),
  KEY `brs_chain_result_summary_idx` (`CHAIN_RESULT`),
  KEY `brs_log_size_idx` (`LOG_SIZE`),
  CONSTRAINT `FK26506D3B9E6537B` FOREIGN KEY (`CHAIN_RESULT`) REFERENCES `BUILDRESULTSUMMARY` (`BUILDRESULTSUMMARY_ID`),
  CONSTRAINT `FK26506D3BCCACF65` FOREIGN KEY (`MERGERESULT_ID`) REFERENCES `MERGE_RESULT` (`MERGERESULT_ID`),
  CONSTRAINT `FK26506D3BCEDEEF5F` FOREIGN KEY (`STAGERESULT_ID`) REFERENCES `CHAIN_STAGE_RESULT` (`STAGERESULT_ID`),
  CONSTRAINT `FK26506D3BE3B5B062` FOREIGN KEY (`VARIABLE_CONTEXT_BASELINE_ID`) REFERENCES `VARIABLE_CONTEXT_BASELINE` (`VARIABLE_CONTEXT_BASELINE_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin |

(beberapa hal dihilangkan, ini adalah meja yang cukup lebar).

Jadi saya punya beberapa pertanyaan tentang ini:

  • mengapa pengoptimal kueri tidak dapat menggunakan indeks untuk menghapus versi subquery ketika sedang menggunakan versi join?
  • adakah cara (idealnya sesuai standar) untuk mengelabui menggunakan indeks? atau
  • apakah ada cara portabel untuk menulis delete from join? Aplikasi ini mendukung PostgreSQL, MySQL, Oracle dan Microsoft SQL Server, digunakan melalui jdbc dan Hibernate.
  • mengapa penghapusan dari VARIABLE_SUBSTITUTIONpemblokiran menyisipkan ke BUILDRESULTSUMMARY, yang hanya digunakan dalam subselect?
0x89
sumber
Percona Server 5.6.24-72.2-1.jessie resp 5.6.24-72.2-1.wheezy (pada sistem uji).
0x89
Ya, seluruh basis data menggunakan innodb.
0x89
Jadi sepertinya 5.6 tidak mendapat banyak perhatian dalam meningkatkan pengoptimal. Anda harus menunggu 5,7 (tetapi coba MariaDB jika Anda bisa. Peningkatan pengoptimal mereka dilakukan kembali dalam versi 5.3 dan 5.5 mereka.)
ypercubeᵀᴹ
@ypercube AFAIK no fork memiliki peningkatan untuk membuat penghapusan subquery dioptimalkan atau 5.7. Hapus optimisasi berbeda dari pernyataan SELECT.
Morgan Tocker

Jawaban:

7
  • mengapa pengoptimal kueri tidak dapat menggunakan indeks untuk menghapus versi subquery ketika sedang menggunakan versi join?

Karena optimizer agak bodoh dalam hal itu. Tidak hanya untuk DELETEdan UPDATEtetapi untuk SELECTpernyataan juga, hal seperti WHERE column IN (SELECT ...)itu tidak sepenuhnya dioptimalkan. Rencana eksekusi biasanya melibatkan menjalankan subquery untuk setiap baris tabel eksternal ( VARIABLE_SUBSTITUTIONdalam hal ini). Jika meja itu kecil, semuanya baik-baik saja. Jika besar, tidak ada harapan. Dalam versi yang bahkan lebih lama, INsubquery dengan INsub-subquery akan membuat bahkan EXPLAINuntuk berjalan lama.

Apa yang dapat Anda lakukan - jika Anda ingin menyimpan pertanyaan ini - adalah menggunakan versi terbaru yang telah mengimplementasikan beberapa optimasi dan menguji lagi. Versi terbaru artinya: MySQL 5.6 (dan 5.7 saat keluar dari beta) dan MariaDB 5.5 / 10.0

(pembaruan) Anda sudah menggunakan 5.6 yang memiliki peningkatan optimasi, dan yang ini relevan: Mengoptimalkan Subquery dengan Transformasi Semi-Gabung
Saya sarankan menambahkan indeks (BUILD_KEY)sendirian. Ada yang komposit tapi itu tidak terlalu berguna untuk permintaan ini.

  • adakah cara (idealnya sesuai standar) untuk mengelabui menggunakan indeks?

Tidak ada yang bisa saya pikirkan. Menurut pendapat saya, tidak ada banyak artinya dalam mencoba menggunakan SQL standar. Ada begitu banyak perbedaan dan keanehan kecil yang dimiliki oleh masing-masing DBMS ( UPDATEdan DELETEpernyataan adalah contoh bagus dari perbedaan tersebut) sehingga ketika Anda mencoba menggunakan sesuatu yang bekerja di mana-mana, hasilnya adalah subset SQL yang sangat terbatas.

  • apakah ada cara portabel untuk menulis penghapusan dari bergabung? Aplikasi ini mendukung PostgreSQL, MySQL, Oracle dan Microsoft SQL Server, digunakan melalui jdbc dan Hibernate.

Jawaban yang sama seperti pertanyaan sebelumnya.

  • mengapa penghapusan dari VARIABLE_SUBSTITUTION mencekal sisipkan ke BUILDRESULTSUMMARY, yang hanya digunakan pada subselect?

Tidak 100% yakin tapi saya pikir itu ada hubungannya dengan menjalankan subquery beberapa kali dan jenis kunci apa yang diambil di atas meja.

ypercubeᵀᴹ
sumber
"3775190 kunci baris" dari innodb_status (dari transaksi penghapusan) sangat sugestif. Tetapi juga "tabel mysql digunakan 2, terkunci 2" tidak terlihat terlalu bagus untuk saya ..
0x89
2

inilah jawaban untuk dua pertanyaan Anda

  • Pengoptimal tidak dapat menggunakan indeks karena klausa mana berubah untuk setiap baris. Pernyataan hapus akan terlihat seperti ini setelah melewati pengoptimal

    delete from VARIABLE_SUBSTITUTION where EXISTS (
    select BUILDRESULTSUMMARY_ID from BUILDRESULTSUMMARY
    where BUILDRESULTSUMMARY.BUILD_KEY = BUILDRESULTSUMMARY_ID AND BUILDRESULTSUMMARY.BUILD_KEY = "BAM-1");

tetapi ketika Anda melakukan bergabung, Server dapat mengidentifikasi baris yang dapat dihapus.

  • Triknya adalah menggunakan variabel untuk menahan BUILDRESULTSUMMARY_IDdan menggunakan variabel alih-alih kueri. Perhatikan bahwa inisialisasi variabel dan kueri penghapusan harus dijalankan dalam satu sesi. Sesuatu seperti ini.

    SET @ids = (SELECT GROUP_CONCAT(BUILDRESULTSUMMARY_ID) 
            from BUILDRESULTSUMMARY where BUILDRESULTSUMMARY.BUILD_KEY = "BAM-1" ); 
    delete from VARIABLE_SUBSTITUTION where FIND_IN_SET(BUILDRESULTSUMMARY_ID,@ids) > 0;

    Anda mungkin menghadapi masalah dengan ini jika kueri mengembalikan terlalu banyak id dan ini bukan cara standar. Itu hanya solusi.

    Dan saya tidak punya jawaban untuk dua pertanyaan Anda yang lain :)

Masoud
sumber
Oke, Anda melewatkan poin saya. Saya pikir apa yang Anda tidak dipertimbangkan adalah bahwa kedua VARIABLE_SUBSTITUTION dan BUILDRESULTSUMMARY memiliki kolom bernama BUILDRESULTSUMMARY_ID, jadi harus: 'menghapus dari VARIABLE_SUBSTITUTION mana ADA (pilih BUILDRESULTSUMMARY_ID dari BUILDRESULTSUMMARY mana BUILDRESULTSUMMARY.BUILDRESULTSUMMARY_ID = VARIABLE_SUBSTITUTION.BUILDRESULTSUMMARY_ID DAN BUILDRESULTSUMMARY.BUILD_KEY = "BAM -1 "); '. Maka masuk akal, dan kedua pertanyaan melakukan hal yang sama.
0x89
1
ya saya baru saja kehilangan referensi ke tabel luar. Tapi bukan itu intinya. Itu hanya ilustrasi tentang bagaimana ia akan diperlakukan dalam pengoptimal.
Masoud
Dengan sedikit perbedaan, pengoptimal akan menghasilkan kueri yang setara.
0x89