Mana yang lebih cepat: beberapa INSERT tunggal atau satu INSERT beberapa baris?


184

Saya mencoba untuk mengoptimalkan satu bagian dari kode saya yang memasukkan data ke dalam MySQL. Haruskah saya membuat rantai INSERT untuk membuat satu INSERT multi-baris besar atau beberapa INSERT terpisah lebih cepat?

Jawaban:


287

https://dev.mysql.com/doc/refman/8.0/id/insert-optimization.html

Waktu yang diperlukan untuk memasukkan baris ditentukan oleh faktor-faktor berikut, di mana angka-angka menunjukkan perkiraan proporsi:

  • Menghubungkan: (3)
  • Mengirim kueri ke server: (2)
  • Permintaan parsing: (2)
  • Memasukkan baris: (1 × ukuran baris)
  • Memasukkan indeks: (1 × jumlah indeks)
  • Penutup: (1)

Dari sini harus jelas, bahwa mengirim satu pernyataan besar akan menghemat biaya tambahan 7 per pernyataan penyisipan, yang selanjutnya membaca teks juga mengatakan:

Jika Anda memasukkan banyak baris dari klien yang sama secara bersamaan, gunakan pernyataan INSERT dengan beberapa daftar VALUES untuk menyisipkan beberapa baris sekaligus. Ini jauh lebih cepat (beberapa kali lebih cepat dalam beberapa kasus) daripada menggunakan pernyataan INSERT baris tunggal yang terpisah.


27
Bagaimana jawaban ini berlaku jika beberapa INSERT tunggal berada dalam transaksi basis data yang sama?
Pinch

2
Berapa banyak baris yang dapat saya sisipkan sekaligus menggunakan pernyataan insert tunggal. apakah itu memungkinkan saya memasukkan 10.000 baris sekaligus?
Naresh Ramoliya

10
@Pinch Menggunakan transaksi saat melakukan ~ 1.5k uperts (insert / update) mengurangi waktu operasi dari ~ 1,5 detik hingga ~ 0,2 detik. Atau dengan kata lain, itu membuatnya 86% lebih cepat dibandingkan dengan memasukkan satu baris. Sial.
fgblomqvist

1
Catatan: Tampaknya jauh berbeda di MSSQL: stackoverflow.com/questions/8635818/…
marsze

Bagaimana dengan menggunakan Pernyataan Disiapkan untuk memasukkan berulang beberapa sisipan tunggal?
priyabagus

151

Saya tahu saya menjawab pertanyaan ini hampir dua setengah tahun setelah ditanya, tetapi saya hanya ingin memberikan beberapa data keras dari proyek yang sedang saya kerjakan sekarang yang menunjukkan bahwa memang melakukan beberapa blok VALUE per sisipan adalah JAUH lebih cepat daripada laporan VALUE blok INSERT tunggal berurutan.

Kode yang saya tulis untuk tolok ukur ini dalam C # menggunakan ODBC untuk membaca data ke dalam memori dari sumber data MSSQL (~ 19.000 baris, semua dibaca sebelum penulisan dimulai), dan konektor MySql .NET (Mysql.Data. *) Digunakan untuk Masukkan data dari memori ke dalam tabel di server MySQL melalui pernyataan yang disiapkan. Itu ditulis sedemikian rupa sehingga memungkinkan saya untuk secara dinamis menyesuaikan jumlah blok VALUE per INSERT yang disiapkan (yaitu, masukkan n baris pada suatu waktu, di mana saya bisa menyesuaikan nilai n sebelum lari.) Saya juga menjalankan tes beberapa kali untuk setiap n.

Melakukan satu blok VALUE tunggal (mis. 1 baris sekaligus) membutuhkan waktu 5,7 - 5,9 detik untuk dijalankan. Nilai lainnya adalah sebagai berikut:

2 baris sekaligus: 3.5 - 3.5 detik
5 baris sekaligus: 2.2 - 2.2 detik
10 baris sekaligus: 1.7 - 1.7 detik
50 baris sekaligus: 1.17 - 1.18 detik
100 baris sekaligus: 1.1 - 1.4 detik
500 baris sekaligus: 1.1 - 1.2 detik
1000 baris sekaligus: 1.17 - 1.17 detik

Jadi ya, bahkan hanya bundling 2 atau 3 menulis bersama memberikan peningkatan dramatis dalam kecepatan (runtime dipotong oleh faktor n), sampai Anda sampai di suatu tempat antara n = 5 dan n = 10, di mana titik peningkatan menurun secara nyata, dan di suatu tempat dalam kisaran n = 10 hingga n = 50 peningkatan menjadi diabaikan.

Harapan yang membantu orang memutuskan (a) apakah akan menggunakan ide multiprepare, dan (b) berapa banyak blok VALUE untuk dibuat per pernyataan (dengan asumsi Anda ingin bekerja dengan data yang mungkin cukup besar untuk mendorong kueri melewati ukuran kueri maks. untuk MySQL, yang saya percaya adalah 16MB secara default di banyak tempat, mungkin lebih besar atau lebih kecil tergantung pada nilai set max_allowed_packet di server.)


1
Permintaan klarifikasi: adalah "detik per baris" waktu Anda atau "total detik".
EngrStudent

3
Total detik - jadi detik per baris adalah yang dibagi dengan ~ 19.000 baris. Meskipun itu angka yang kecil, jadi mungkin baris / detik adalah metrik yang lebih baik jika Anda mencari angka yang mudah dibandingkan.
Jon Kloske

Kebetulan, ada beberapa contoh .NET code untuk pendekatan yang saya jelaskan di atas tentang jawaban saya yang terkait: stackoverflow.com/questions/25377357/…
Jon Kloske

18

Faktor utama adalah apakah Anda menggunakan mesin transaksional dan apakah Anda memiliki autocommit.

Autocommit aktif secara default dan Anda mungkin ingin membiarkannya; Oleh karena itu, setiap sisipan yang Anda lakukan melakukan transaksi sendiri. Ini berarti bahwa jika Anda melakukan satu memasukkan per baris, Anda akan melakukan transaksi untuk setiap baris.

Dengan asumsi satu utas, itu berarti bahwa server perlu menyinkronkan beberapa data ke disk untuk SETIAP BARIS. Perlu menunggu data untuk mencapai lokasi penyimpanan yang persisten (mudah-mudahan ram yang didukung baterai di controller RAID Anda). Ini secara inheren agak lambat dan mungkin akan menjadi faktor pembatas dalam kasus ini.

Saya tentu saja mengasumsikan bahwa Anda menggunakan mesin transaksional (biasanya innodb) DAN bahwa Anda belum mengubah pengaturan untuk mengurangi daya tahan.

Saya juga berasumsi bahwa Anda menggunakan utas tunggal untuk melakukan sisipan ini. Menggunakan beberapa thread muddies hal-hal sedikit karena beberapa versi MySQL memiliki kelompok kerja-komit di innodb - ini berarti bahwa beberapa thread melakukan komitmen mereka sendiri dapat berbagi satu tulis ke log transaksi, yang baik karena berarti lebih sedikit sinkronisasi untuk penyimpanan persisten .

Di sisi lain, hasilnya adalah, bahwa Anda BENAR-BENAR INGIN MENGGUNAKAN sisipan multi-baris.

Ada batas di mana ia menjadi kontra-produktif, tetapi dalam kebanyakan kasus itu setidaknya 10.000 baris. Jadi, jika Anda mengelompokkannya hingga 1.000 baris, Anda mungkin aman.

Jika Anda menggunakan MyISAM, ada banyak hal lain, tetapi saya tidak akan membuat Anda bosan dengan itu. Perdamaian.


1
Apakah ada alasan mengapa alat tersebut menjadi tidak produktif setelah satu titik? Saya pernah melihat itu terjadi sebelumnya, tetapi tidak yakin mengapa.
Dhruv Gairola

1
Apakah Anda tahu jika ada gunanya sama sekali dalam memasukkan MySQL ketika menggunakan transaksi . Saya hanya ingin tahu apakah saya dapat menyelamatkan diri dari kesulitan untuk menghasilkan perintah SQL multi-nilai jika pustaka yang mendasari saya (Java JDBC - mysql-connector-java-5.1.30) tidak benar-benar melakukan sampai saya mengatakannya.
RTF

@RTF Saya pikir Anda perlu melakukan tes kecil untuk menentukan perilaku dalam situasi Anda karena itu perilaku implementasi yang sangat spesifik, tetapi dalam banyak kasus ya transaksi harus memberikan keuntungan kinerja yang serupa.
Jasmine Hegman

9

Kirim sisipan sebanyak-banyaknya pada satu waktu sekaligus. Kecepatan memasukkan yang sebenarnya harus sama, tetapi Anda akan melihat peningkatan kinerja dari pengurangan overhead jaringan.


7

Secara umum semakin sedikit jumlah panggilan ke basis data semakin baik (artinya lebih cepat, lebih efisien), jadi cobalah untuk membuat kode sisipan sedemikian rupa sehingga meminimalkan akses basis data. Ingat, kecuali Anda menggunakan kumpulan koneksi, setiap akses databse harus membuat koneksi, jalankan sql, dan kemudian hancurkan koneksi. Sedikit overhead!


bagaimana jika koneksi persisten digunakan?
dusoft

6
Masih ada overhead. Waktu transit saja (ke dan dari untuk setiap sisipan terpisah) akan dengan cepat dapat dipahami jika Anda melakukan ribuan sisipan.
RC.

4

Anda mungkin ingin :

  • Pastikan komit otomatis mati
  • Buka Koneksi
  • Kirim beberapa kumpulan sisipan dalam satu transaksi (ukuran sekitar 4000-10000 baris? Anda tahu)
  • Tutup koneksi

Bergantung pada seberapa baik skala server Anda (secara definitif ok dengan PostgreSQl, Oracledan MSSQL), lakukan hal di atas dengan beberapa utas dan banyak koneksi.


3

Secara umum, beberapa sisipan akan lebih lambat karena overhead koneksi. Melakukan banyak insert sekaligus akan mengurangi biaya overhead per insert.

Bergantung pada bahasa yang Anda gunakan, Anda mungkin dapat membuat batch dalam bahasa pemrograman / scripting Anda sebelum pergi ke db dan menambahkan setiap sisipan ke batch. Maka Anda akan dapat menjalankan batch besar menggunakan satu operasi terhubung. Berikut ini contohnya di Jawa.


3

MYSQL 5.5 Satu pernyataan memasukkan sql mengambil ~ 300 hingga ~ 450ms. sedangkan statistik di bawah ini adalah untuk statemen penyisipan banyak inline.

(25492 row(s) affected)
Execution Time : 00:00:03:343
Transfer Time  : 00:00:00:000
Total Time     : 00:00:03:343

Saya akan mengatakan inline adalah cara untuk pergi :)


0

Sungguh konyol betapa buruknya Mysql dan MariaDB dioptimalkan ketika datang ke sisipan. Saya menguji mysql 5.7 dan mariadb 10.3, tidak ada perbedaan nyata pada mereka.

Saya sudah menguji ini pada server dengan disk NVME, 70.000 IOPS, throughput seq 1,1 GB / detik dan itu mungkin dupleks penuh (baca dan tulis).
Server adalah server berkinerja tinggi juga.
Berikan 20 GB ram.
Basis data benar-benar kosong.

Kecepatan yang saya terima adalah 5.000 sisipan per detik saat melakukan sisipan multi baris (mencobanya dengan potongan data 1MB hingga 10MB)

Sekarang petunjuknya:
Jika saya menambahkan utas lain dan memasukkan ke dalam tabel SAMA saya tiba-tiba memiliki 2x5000 / detik. Satu utas lagi dan saya memiliki total 15000 / detik

Pertimbangkan ini: Ketika melakukan SATU utas menyisipkan itu berarti Anda dapat menulis secara berurutan ke disk (dengan pengecualian indeks). Saat menggunakan utas, Anda sebenarnya menurunkan kinerja yang mungkin karena sekarang perlu melakukan lebih banyak akses acak. Tetapi kenyataan menunjukkan mysql sangat dioptimalkan sehingga utas banyak membantu.

Performa nyata yang mungkin terjadi dengan server seperti itu mungkin jutaan per detik, CPU menganggur, disk menganggur.
Alasannya cukup jelas bahwa mariadb seperti halnya mysql memiliki penundaan internal.


@Craftables Anda membutuhkan pengembangan eksternal, itu tidak dapat dilakukan dalam mysql. Utas berarti bahwa Anda menggunakan beberapa koneksi ke server, Anda membagi kueri menjadi beberapa potongan (misalnya dengan membaginya menjadi bagian genap dengan kunci primer). Saya berhasil mendapatkan hingga 10.000 kali kinerja menggunakan metode ini pada tabel yang sangat besar. Kueri yang akan berjalan selama 40.000 detik dapat selesai dalam 2-3 menit JIKA Anda menggunakan banyak utas dan mysql Anda sangat dioptimalkan.
John

@ John Menarik dan mungkin memiliki beberapa aplikasi yang sangat bagus ... tapi ... Jika Anda membagi kueri menjadi beberapa bagian, bagaimana Anda menangani transaksi? Dan pertimbangkan juga skenario berikut: Tabel x memiliki kolom 'parent_id' yang terkait dengan tabel 'id' yang sama. Di suatu tempat di dalam data Anda, Anda memiliki INSERT INTO x ( id, parent_id) VALUES (1, NULL). Salah satu dari kumpulan nilai berikutnya menghubungkan ke baris itu. Jika Anda membagi dalam potongan dan set yang sampai ke potongan lain, itu mungkin diproses sebelum yang pertama, gagal seluruh proses. Adakah cara mengatasinya?
zozo

@zozo ini berguna untuk memasukkan massal dan permintaan massal. Bagaimanapun, transaksi akan merusak kinerja karena mencakup banyak buffering data. Tetapi Anda juga dapat menggunakan transaksi dalam sisipan atau kueri multi-ulir.
John

-2

beberapa sisipan lebih cepat tetapi harus dilakukan. thrik lain sedang menonaktifkan memeriksa kendala membuat insert jauh lebih cepat. Tidak masalah meja Anda memilikinya atau tidak. Misalnya, coba nonaktifkan kunci asing dan nikmati kecepatannya:

SET FOREIGN_KEY_CHECKS=0;

Anda harus mengaktifkannya kembali setelah sisipan dengan:

SET FOREIGN_KEY_CHECKS=1;

ini adalah cara umum untuk memasukkan data besar. integritas data mungkin rusak sehingga Anda harus mengatasinya sebelum menonaktifkan pemeriksaan kunci asing.


1
Tidak tahu mengapa pv meng-upgrade ini karena dua alasan: 1. Tidak ada hubungannya dengan pertanyaan 2. Ini adalah ide yang sangat buruk (dengan beberapa pengecualian - seperti dump atau perubahan temp struktural -, tetapi buruk secara umum). Cek ada karena suatu alasan: Mereka ada untuk memastikan konsistensi data. Mereka memperlambatnya karena mereka memastikan Anda tidak memasukkan atau mengubah data yang seharusnya tidak Anda masukkan. Cobalah untuk mengoptimalkan kueri dengan cara yang benar; dalam lingkungan kritis bisnis apa pun, ini berarti kematian aplikasi karena terlepas dari seberapa hati-hati Anda, beberapa hal akan gagal.
zozo

1
mungkin tetapi opsi ini sangat efektif dalam mengimpor tabel besar dan sangat praktis dan itu bisa memberi beberapa orang gambaran bagaimana mereka dapat membuat penyisipan data lebih cepat.
MSS
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.