Kami memiliki tabel 2,2 GB di Postgres dengan 7.801.611 baris di dalamnya. Kami menambahkan kolom uuid / panduan ke dalamnya dan saya bertanya-tanya apa cara terbaik untuk mengisi kolom itu (karena kami ingin menambahkan NOT NULL
batasan untuknya).
Jika saya mengerti Postgres dengan benar, pembaruan secara teknis adalah delete dan insert jadi ini pada dasarnya membangun kembali seluruh tabel 2.2 gb. Kami juga memiliki budak yang berjalan sehingga kami tidak ingin itu ketinggalan.
Apakah ada cara yang lebih baik daripada menulis naskah yang perlahan-lahan mengisi waktu?
postgresql
storage
ddl
Collin Peters
sumber
sumber
ALTER TABLE .. ADD COLUMN ...
atau bagian itu yang harus dijawab juga?Jawaban:
Ini sangat tergantung pada detail kebutuhan Anda.
Jika Anda memiliki ruang kosong yang cukup (setidaknya 110% dari
pg_size_pretty((pg_total_relation_size(tbl))
) pada disk dan dapat membeli kunci saham untuk beberapa waktu dan kunci eksklusif untuk waktu yang sangat singkat , maka buat tabel baru termasukuuid
kolom yang digunakanCREATE TABLE AS
. Mengapa?Kode di bawah ini menggunakan fungsi dari
uuid-oss
modul tambahan .Kunci tabel terhadap perubahan
SHARE
mode bersamaan (masih memungkinkan pembacaan bersamaan). Upaya untuk menulis ke tabel akan menunggu dan akhirnya gagal. Lihat di bawah.Salin seluruh tabel sambil mengisi kolom baru dengan cepat - mungkin memesan baris yang menguntungkan saat berada di dalamnya.
Jika Anda akan memesan ulang baris, pastikan untuk mengatur
work_mem
setinggi yang Anda mampu (hanya untuk sesi Anda, bukan secara global).Kemudian tambahkan batasan, kunci asing, indeks, pemicu dll ke tabel baru. Saat memperbarui sebagian besar tabel, lebih cepat membuat indeks dari awal daripada menambahkan baris secara berulang.
Saat tabel baru siap, jatuhkan yang lama dan ganti nama yang baru untuk menjadikannya pengganti drop-in. Hanya langkah terakhir ini yang mendapatkan kunci eksklusif di meja lama untuk sisa transaksi - yang seharusnya sangat singkat sekarang.
Ini juga mengharuskan Anda menghapus objek apa pun tergantung pada tipe tabel (tampilan, fungsi menggunakan tipe tabel dalam tanda tangan, ...) dan membuatnya kembali sesudahnya.
Lakukan semuanya dalam satu transaksi untuk menghindari kondisi yang tidak lengkap.
Ini harus tercepat. Metode pembaruan lainnya yang ada harus menulis ulang seluruh tabel juga, hanya dengan cara yang lebih mahal. Anda hanya akan pergi rute itu jika Anda tidak memiliki cukup ruang kosong pada disk atau tidak mampu mengunci seluruh tabel atau menghasilkan kesalahan untuk upaya penulisan bersamaan.
Apa yang terjadi pada penulisan bersamaan?
Transaksi lain (dalam sesi lain) mencoba
INSERT
/UPDATE
/DELETE
dalam tabel yang sama setelah transaksi Anda mengambilSHARE
kunci, akan menunggu sampai kunci dilepaskan atau waktu habis menendang, mana yang lebih dulu. Mereka akan gagal , karena tabel yang mereka coba tulis telah dihapus dari bawah mereka.Tabel baru memiliki OID tabel baru, tetapi transaksi bersamaan telah menyelesaikan nama tabel ke OID dari tabel sebelumnya . Ketika kunci akhirnya dilepaskan, mereka mencoba mengunci meja sendiri sebelum menulis dan menemukan bahwa itu hilang. Postgres akan menjawab:
Di mana
123456
OID dari tabel lama. Anda perlu menangkap pengecualian itu dan mencoba lagi kueri dalam kode aplikasi Anda untuk menghindarinya.Jika Anda tidak mampu untuk itu terjadi, Anda harus menjaga meja asli Anda.
Dua alternatif menjaga tabel yang ada
Perbarui di tempat (mungkin menjalankan pembaruan pada segmen kecil sekaligus) sebelum Anda menambahkan
NOT NULL
kendala. Menambahkan kolom baru dengan nilai NULL dan tanpaNOT NULL
kendala itu murah.Sejak Postgres 9.2 Anda juga dapat membuat
CHECK
batasan denganNOT VALID
:Itu memungkinkan Anda untuk memperbarui baris peu à peu - dalam beberapa transaksi terpisah . Ini menghindari menjaga kunci baris terlalu lama dan juga memungkinkan baris mati digunakan kembali. (Anda harus menjalankan
VACUUM
secara manual jika tidak ada cukup waktu di antara autovacuum untuk memulai.) Akhirnya, tambahkanNOT NULL
kendala dan hapusNOT VALID CHECK
kendala:Jawaban terkait membahas
NOT VALID
lebih detail:Siapkan negara baru di tabel sementara ,
TRUNCATE
asli dan isi ulang dari tabel temp. Semua dalam satu transaksi . Anda masih perlu mengambilSHARE
kunci sebelum menyiapkan tabel baru untuk mencegah kehilangan penulisan bersamaan.Detail dalam jawaban terkait ini di SO:
sumber
LOCK
dan tidak termasukDROP
. Saya hanya bisa mengucapkan dugaan liar dan tidak berguna. Adapun 2., harap pertimbangkan addendum untuk jawaban saya.Saya tidak punya jawaban "terbaik", tetapi saya punya jawaban "paling tidak buruk" yang mungkin membuat Anda menyelesaikan sesuatu dengan cukup cepat.
Tabel saya memiliki baris 2MM dan kinerja pembaruan menanjak ketika saya mencoba menambahkan kolom cap waktu sekunder yang default ke yang pertama.
Setelah menggantung selama 40 menit, saya mencoba ini dalam jumlah kecil untuk mendapatkan gambaran berapa lama ini - perkiraannya sekitar 8 jam.
Jawaban yang diterima jelas lebih baik - tetapi tabel ini banyak digunakan dalam database saya. Ada beberapa lusin tabel yang FKEY ke atasnya; Saya ingin menghindari beralih KUNCI ASING pada banyak tabel. Dan kemudian ada pandangan.
Sedikit mencari dokumen, studi kasus dan StackOverflow, dan aku punya "A-Ha!" saat. Saluran tidak pada UPDATE inti, tetapi pada semua operasi INDEX. Tabel saya memiliki 12 indeks di atasnya - beberapa untuk kendala unik, beberapa untuk mempercepat perencana kueri, dan beberapa untuk pencarian teks lengkap.
Setiap baris yang DIPERBARUI tidak hanya bekerja pada DELETE / INSERT, tetapi juga overhead untuk mengubah setiap indeks dan memeriksa kendala.
Solusi saya adalah dengan menghapus setiap indeks dan kendala, memperbarui tabel, lalu menambahkan semua indeks / kendala kembali.
Butuh sekitar 3 menit untuk menulis transaksi SQL yang melakukan hal berikut:
Skrip membutuhkan waktu 7 menit untuk dijalankan.
Jawaban yang diterima jelas lebih baik dan lebih tepat ... dan secara virtual menghilangkan kebutuhan akan waktu henti. Namun, dalam kasus saya, akan diperlukan lebih banyak pekerjaan "Pengembang" untuk menggunakan solusi itu dan kami memiliki jendela 30 menit dari waktu henti yang dijadwalkan yang dapat diselesaikan. Solusi kami mengatasinya dalam 10.
sumber