HAPUS sangat lambat di PostgreSQL, solusinya?

30

Saya memiliki database PostgreSQL 9.2 yang memiliki skema utama dengan sekitar 70 tabel dan sejumlah variabel skema per-klien yang terstruktur identik masing-masing 30 tabel. Skema klien memiliki kunci asing yang merujuk pada skema utama dan bukan sebaliknya.

Saya baru saja mulai mengisi database dengan beberapa data nyata yang diambil dari versi sebelumnya. DB telah mencapai sekitar 1,5 GB (diharapkan tumbuh beberapa GB 10s dalam beberapa minggu) ketika saya harus melakukan penghapusan massal di tabel yang sangat sentral dalam skema utama. Semua kunci asing yang bersangkutan ditandai ON DELETE CASCADE.

Tidak mengherankan bahwa ini akan memakan waktu lama tetapi setelah 12 jam menjadi jelas bahwa saya lebih baik memulai dari awal, menjatuhkan DB dan meluncurkan migrasi lagi. Tetapi bagaimana jika saya perlu mengulangi operasi ini nanti ketika DB hidup dan jauh lebih besar? Adakah metode alternatif dan lebih cepat?

Apakah akan jauh lebih cepat jika saya menulis skrip yang akan menelusuri tabel dependen, mulai dari tabel terjauh dari tabel pusat, menghapus tabel baris dependen demi tabel?

Detail penting adalah bahwa ada pemicu pada beberapa tabel.

jd.
sumber
4
Setelah 5 tahun, saya mengubah jawaban yang diterima. DELETE lambat hampir selalu disebabkan oleh indeks yang hilang pada kunci asing yang secara langsung atau tidak langsung merujuk tabel yang sedang dihapus. Pemicu yang mengaktifkan pernyataan DELETE juga dapat memperlambat segalanya, meskipun solusinya hampir selalu membuat mereka berjalan lebih cepat (misalnya dengan menambahkan indeks yang hilang) dan hampir tidak pernah menonaktifkan semua pemicu.
jd.

Jawaban:

30

Saya punya masalah serupa. Ternyata, ON DELETE CASCADEpemicu itu memperlambat sedikit, karena penghapusan bertingkat sangat lambat.

Saya memecahkan masalah dengan membuat indeks pada bidang kunci asing pada tabel referensi, dan saya beralih dari mengambil banyak jam untuk penghapusan menjadi beberapa detik.

ailnlv
sumber
Wow, ini membantu saya menghapus 8 juta catatan dalam beberapa menit. Tapi yang saya tidak mengerti adalah bahwa meja saya hanya menyimpan referensi ke tabel lain, tidak ada tabel lain yang menyimpan referensi ke tabel saya. Jadi apa sebenarnya pengaruhnya di sini? (Saya tidak menggunakan ON DELETE CASCADE)
msrd0
2
Ini menyelesaikannya untuk saya juga. Bagi siapa pun yang mencoba ini, Anda dapat melakukan EXPLAIN (ANALYZE, BUFFERS)kueri pada satu baris penghapusan dan itu akan menunjukkan kepada Anda batasan kunci asing mana yang paling lama (paling tidak bagi saya).
Justin Workman
Sama, harus menghapus pada baris 600k cascade dan pada awalnya dibutuhkan antara 2-10 per operasi dengan penggunaan CPU 100%. Sekarang hanya butuh beberapa menit untuk menghapus semuanya dengan penggunaan CPU 80%.
fillobotto
Penting untuk diperhatikan bahwa jika Anda memiliki referensi asing ke mana saja, kolom sumber harus memiliki indeks nyata atau kinerjanya akan terganggu. Saya tidak yakin apakah PRIMARYindeks cukup tetapi UNIQUEindeks jelas tidak cukup baik untuk tujuan ini.
Mikko Rantalainen
26

Anda punya beberapa pilihan. Opsi terbaik adalah menjalankan penghapusan batch sehingga pemicu tidak terkena. Nonaktifkan pemicu sebelum menghapus, lalu aktifkan kembali. Ini menghemat waktu Anda yang sangat besar. Sebagai contoh:

ALTER TABLE tablename DISABLE TRIGGER ALL; 
DELETE ...; 
ALTER TABLE tablename ENABLE TRIGGER ALL;

Kunci utama di sini adalah Anda ingin meminimalkan kedalaman subquery. Dalam hal ini Anda mungkin ingin mengatur tabel temp untuk menyimpan informasi yang relevan sehingga Anda dapat menghindari subkueri yang dalam pada penghapusan Anda.

Chris Travers
sumber
Dalam kasus saya, saya memulai perintah DELETE FROM sebelum tidur dan itu masih belum selesai ketika saya kembali ke komputer saya keesokan harinya. 100% penggunaan CPU pada satu inti sepanjang waktu. Setelah menonaktifkan pemicu dan mencoba lagi butuh 3 detik untuk menghapus 200 ribu catatan. Terima kasih!
Nick Woodhams
13

Metode termudah untuk memecahkan masalah adalah untuk query waktu rinci dari PostgreSQL: EXPLAIN. Untuk ini, Anda perlu menemukan setidaknya satu permintaan yang lengkap tetapi membutuhkan waktu lebih lama dari yang diharapkan. Katakanlah garis ini akan terlihat seperti

delete from mydata where id='897b4dde-6a0d-4159-91e6-88e84519e6b6';

Alih-alih benar-benar menjalankan perintah itu bisa Anda lakukan

begin;
explain (analyze,buffers,timing) delete from mydata where id='897b4dde-6a0d-4159-91e6-88e84519e6b6';
rollback;

Kembalikan pada akhirnya memungkinkan menjalankan ini tanpa benar-benar memodifikasi database tetapi Anda masih mendapatkan waktu yang terperinci dari berapa banyak. Setelah menjalankan itu, Anda mungkin menemukan dalam output bahwa beberapa pemicu menyebabkan penundaan besar:

...
Trigger for constraint XYZ123: time=12311.292 calls=1
...

Ini timedalam ms (milidetik) sehingga memeriksa kendala ini memakan waktu sekitar 12,3 detik. Anda perlu menambahkan yang baru di INDEXatas kolom yang diperlukan sehingga pemicu ini dapat dihitung secara efektif. Untuk referensi kunci asing, kolom yang merujuk ke tabel lain harus diindeks (yaitu, kolom sumber, bukan kolom target). PostgreSQL tidak secara otomatis membuat indeks untuk Anda dan DELETEmerupakan satu-satunya permintaan umum di mana Anda benar-benar membutuhkan indeks itu. Akibatnya, Anda mungkin telah mengakumulasikan data selama bertahun-tahun hingga Anda menemukan kasus DELETEyang terlalu lambat karena tidak ada indeks.

Setelah Anda memperbaiki kinerja kendala itu (atau hal lain yang memakan waktu terlalu lama), ulangi perintah di begin/ rollbackblock sehingga Anda dapat membandingkan waktu eksekusi yang baru dengan yang sebelumnya. Lanjutkan sampai Anda puas dengan waktu respons penghapusan satu baris (saya mendapatkan satu kueri mulai dari 25,6 detik hingga 15 ms hanya dengan menambahkan indeks yang berbeda). Kemudian Anda dapat melanjutkan untuk menyelesaikan penghapusan penuh Anda tanpa ada peretasan.

(Catatan yang EXPLAINmembutuhkan kueri yang dapat diselesaikan dengan sukses. Saya pernah punya masalah di mana PostgreSQL butuh waktu terlalu lama untuk mencari tahu bahwa satu penghapusan akan melanggar batasan kunci asing dan dalam kasus itu EXPLAINtidak dapat digunakan karena tidak akan memancarkan waktu untuk gagal pertanyaan. Saya tidak tahu cara mudah untuk men-debug masalah kinerja dalam kasus seperti itu.)

Mikko Rantalainen
sumber
8

Menonaktifkan pemicu dapat menjadi ancaman bagi integritas DB dan tidak dapat direkomendasikan; namun jika Anda yakin operasi Anda kendala-kegagalan-bukti, Anda dapat menonaktifkan pemicu, dengan berikut:SET session_replication_role = replica;

Jalankan DELETE sini.

Untuk mengembalikan pemicu, jalankan: SET session_replication_role = DEFAULT;

Sumber di sini.

Pinimo
sumber
0

Jika Anda memiliki pemicu ON DELETE CASCADE, mereka mudah-mudahan ada karena suatu alasan, dan karenanya tidak boleh dinonaktifkan. Trik lain (masih menambahkan indeks Anda) yang berfungsi untuk saya adalah membuat fungsi hapus yang secara manual menghapus data yang dimulai dengan tabel di akhir kaskade, dan bekerja menuju tabel utama. (Ini sama dengan yang harus Anda lakukan jika Anda memiliki pemicu ON DELETE RESTRICT)

CREATE TABLE tablea (
    tablea_uid integer
);

CREATE TABLE tableb (
    tableb_uid integer,
    tablea_rid integer REFERENCES tablea(tablea_uid)
);

CREATE TABLE tablec (
    tablec_uid integer,
    tableb_rid integer REFERENCES tableb(tableb_uid)
);

Dalam hal ini hapus data dalam tablec lalu tableb lalu tablea

CREATE OR REPLACE FUNCTION delete_in_order()
 RETURNS void AS $$

    DELETE FROM tablec;
    DELETE FROM tableb;
    DELETE FROM tablea;

$$ LANGUAGE SQL;
blindguy
sumber