PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Bagaimana cara melakukan pembaruan non-pemblokiran besar di PostgreSQL?

Kolom / Baris

... Saya tidak memerlukan integritas transaksional untuk dipertahankan di seluruh operasi, karena saya tahu bahwa kolom yang saya ubah tidak akan ditulis atau dibaca selama pembaruan.

UPDATE dalam model MVCC PostgreSQL menulis versi baru seluruh baris . Jika transaksi bersamaan mengubah apa saja kolom dari baris yang sama, masalah konkurensi yang memakan waktu muncul. Detail dalam manual. Mengetahui kolom yang sama tidak akan tersentuh oleh transaksi bersamaan menghindari beberapa kemungkinan komplikasi, tetapi tidak yang lain.

Indeks

Untuk menghindari dialihkan ke diskusi di luar topik, mari kita asumsikan bahwa semua nilai status untuk 35 juta kolom saat ini disetel ke nilai yang sama (bukan nol), sehingga menjadikan indeks tidak berguna.

Saat memperbarui seluruh tabel (atau sebagian besar) Postgres tidak pernah menggunakan indeks . Pemindaian berurutan lebih cepat ketika semua atau sebagian besar baris harus dibaca. Sebaliknya:Pemeliharaan indeks berarti biaya tambahan untuk UPDATE .

Kinerja

Misalnya, katakanlah saya memiliki tabel bernama "pesanan" dengan 35 juta baris, dan saya ingin melakukan ini:

UPDATE orders SET status = null;

Saya mengerti Anda bertujuan untuk solusi yang lebih umum (lihat di bawah). Tapi untuk menjawab pertanyaan sebenarnya ditanya:Ini dapat ditangani dalam dalam hitungan milidetik , berapa pun ukuran tabelnya:

ALTER TABLE orders DROP column status
                 , ADD  column status text;

Manual (hingga Postgres 10):

Ketika kolom ditambahkan dengan ADD COLUMN , semua baris yang ada dalam tabel diinisialisasi dengan nilai default kolom (NULL jika tidak ada DEFAULT klausa ditentukan). Jika tidak ada DEFAULT klausa, ini hanyalah perubahan metadata [...]

Manual (sejak Postgres 11):

Ketika kolom ditambahkan dengan ADD COLUMN dan DEFAULT yang tidak mudah menguap ditentukan, default dievaluasi pada saat pernyataan dan hasilnya disimpan dalam metadata tabel. Nilai itu akan digunakan untuk kolom untuk semua baris yang ada. Jika tidak ada DEFAULT ditentukan, NULL digunakan. Dalam kedua kasus tersebut, penulisan ulang tabel tidak diperlukan.

Menambahkan kolom dengan DEFAULT yang mudah menguap atau mengubah tipe kolom yang ada akan membutuhkan seluruh tabel dan indeksnya untuk ditulis ulang. [...]

Dan:

DROP COLUMN form tidak secara fisik menghapus kolom, tetapi hanya membuatnya tidak terlihat oleh operasi SQL. Operasi penyisipan dan pembaruan berikutnya dalam tabel akan menyimpan nilai nol untuk kolom tersebut. Dengan demikian, menghapus kolom dengan cepat tetapi tidak akan segera mengurangi ukuran pada disk tabel Anda, karena ruang yang ditempati oleh kolom yang dijatuhkan tidak diambil kembali. Ruang akan direklamasi seiring waktu karena baris yang ada diperbarui.

Pastikan Anda tidak memiliki objek yang bergantung pada kolom (batasan kunci asing, indeks, tampilan, ...). Anda harus menjatuhkan/membuat ulang itu. Kecuali itu, operasi kecil pada tabel katalog sistem pg_attribute melakukan pekerjaan. Memerlukan kunci eksklusif di atas meja yang mungkin menjadi masalah untuk beban bersamaan yang berat. (Seperti yang ditekankan Buurman dalam komentarnya.) Selain itu, operasinya hanya dalam hitungan milidetik.

Jika Anda memiliki kolom default yang ingin Anda pertahankan, tambahkan kembali dalam perintah terpisah . Melakukannya dalam perintah yang sama segera menerapkannya ke semua baris. Lihat:

  • Tambahkan kolom baru tanpa kunci tabel?

Untuk benar-benar menerapkan default, pertimbangkan untuk melakukannya secara berkelompok:

  • Apakah PostgreSQL mengoptimalkan penambahan kolom dengan DEFAULT non-NULL?

Solusi umum

dblink telah disebutkan dalam jawaban lain. Ini memungkinkan akses ke database Postgres "jarak jauh" dalam koneksi terpisah yang implisit. Basis data "jarak jauh" dapat menjadi basis data saat ini, sehingga mencapai "transaksi otonom" :apa yang ditulis fungsi di db "jarak jauh" telah dikomit dan tidak dapat dibatalkan.

Ini memungkinkan untuk menjalankan satu fungsi yang memperbarui tabel besar di bagian yang lebih kecil dan setiap bagian dilakukan secara terpisah. Menghindari membangun overhead transaksi untuk jumlah baris yang sangat besar dan, yang lebih penting, melepaskan kunci setelah setiap bagian. Hal ini memungkinkan operasi serentak berjalan tanpa banyak penundaan dan memperkecil kemungkinan terjadinya deadlock.

Jika Anda tidak memiliki akses bersamaan, ini hampir tidak berguna - kecuali untuk menghindari ROLLBACK setelah pengecualian. Pertimbangkan juga SAVEPOINT untuk kasus itu.

Penafian

Pertama-tama, banyak transaksi kecil sebenarnya lebih mahal. Ini hanya masuk akal untuk tabel besar . Sweet spot tergantung pada banyak faktor.

Jika Anda tidak yakin dengan apa yang Anda lakukan:satu transaksi adalah metode yang aman . Agar ini berfungsi dengan baik, operasi bersamaan di atas meja harus dimainkan bersama. Misalnya:menulis concurrent bersamaan dapat memindahkan baris ke partisi yang seharusnya sudah diproses. Atau pembacaan bersamaan dapat melihat status perantara yang tidak konsisten. Anda telah diperingatkan.

Petunjuk langkah demi langkah

Modul tambahan dblink perlu diinstal terlebih dahulu:

  • Bagaimana cara menggunakan (menginstal) dblink di PostgreSQL?

Menyiapkan koneksi dengan dblink sangat bergantung pada penyiapan klaster DB Anda dan kebijakan keamanan yang diterapkan. Ini bisa rumit. Terkait jawaban nanti dengan lebih cara terhubung dengan dblink :

  • Penyisipan persisten dalam UDF meskipun fungsi dibatalkan

Buat FOREIGN SERVER dan USER MAPPING seperti yang diinstruksikan di sana untuk menyederhanakan dan merampingkan koneksi (kecuali Anda sudah memilikinya).
Dengan asumsi serial PRIMARY KEY dengan atau tanpa beberapa celah.

CREATE OR REPLACE FUNCTION f_update_in_steps()
  RETURNS void AS
$func$
DECLARE
   _step int;   -- size of step
   _cur  int;   -- current ID (starting with minimum)
   _max  int;   -- maximum ID
BEGIN
   SELECT INTO _cur, _max  min(order_id), max(order_id) FROM orders;
                                        -- 100 slices (steps) hard coded
   _step := ((_max - _cur) / 100) + 1;  -- rounded, possibly a bit too small
                                        -- +1 to avoid endless loop for 0
   PERFORM dblink_connect('myserver');  -- your foreign server as instructed above

   FOR i IN 0..200 LOOP                 -- 200 >> 100 to make sure we exceed _max
      PERFORM dblink_exec(
       $$UPDATE public.orders
         SET    status = 'foo'
         WHERE  order_id >= $$ || _cur || $$
         AND    order_id <  $$ || _cur + _step || $$
         AND    status IS DISTINCT FROM 'foo'$$);  -- avoid empty update

      _cur := _cur + _step;

      EXIT WHEN _cur > _max;            -- stop when done (never loop till 200)
   END LOOP;

   PERFORM dblink_disconnect();
END
$func$  LANGUAGE plpgsql;

Telepon:

SELECT f_update_in_steps();

Anda dapat membuat parameter bagian mana pun sesuai dengan kebutuhan Anda:nama tabel, nama kolom, nilai, ... pastikan untuk membersihkan pengidentifikasi untuk menghindari injeksi SQL:

  • Nama tabel sebagai parameter fungsi PostgreSQL

Hindari UPDATE kosong:

  • Bagaimana cara (atau dapatkah saya) PILIH BERBEDA pada beberapa kolom?


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Postgres baru saja berhenti bekerja secara acak (Rails, PGSQL.5432)

  2. Bagaimana cara mengakses indeks internal array dengan postgreSQL?

  3. Bagaimana cara mengubah skema beberapa tabel PostgreSQL dalam satu operasi?

  4. Bagaimana menangani evolusi basis data Play Framework 2 dalam produksi

  5. Bagaimana Atan2d() Bekerja di PostgreSQL