Apakah memperbarui baris dengan nilai yang sama sebenarnya memperbarui baris?

28

Saya memiliki pertanyaan terkait kinerja. Katakanlah saya memiliki pengguna dengan nama depan Michael. Ambil kueri berikut:

UPDATE users
SET first_name = 'Michael'
WHERE users.id = 123

Apakah kueri benar-benar menjalankan pembaruan, meskipun sedang diperbarui dengan nilai yang sama? Jika demikian, bagaimana saya mencegahnya terjadi?

OneSneakyMofo
sumber
1
Mengapa Anda menjalankan pernyataan dan secara bersamaan mengharapkannya untuk tidak mengeksekusi?
Max Vernon
@MaxVernon Ruby on Rails 'ORM tidak memperbarui catatan, jadi saya ingin tahu apakah PostgreSQL melakukan hal yang sama.
OneSneakyMofo
1
Saya menyarankan jika Ruby on Rails melakukan itu, mungkin itu adalah melakukan pemilihan pertama untuk melihat apakah baris perlu pembaruan.
Max Vernon
x-diposting ke SO: stackoverflow.com/q/33156712/939860
Erwin Brandstetter

Jawaban:

35

Karena model MVCC Postgres, dan sesuai dengan aturan SQL, sebuah UPDATEmenulis versi baris baru untuk setiap baris yang tidak dikecualikan dalam WHEREklausa.

Ini memang memiliki dampak yang lebih atau kurang substansial pada kinerja, secara langsung dan tidak langsung. "Pembaruan kosong" memiliki biaya per baris yang sama dengan pembaruan lainnya. Mereka mengaktifkan pemicu (jika ada) seperti pembaruan lainnya, mereka harus log-WAL dan mereka menghasilkan baris mati membengkak tabel dan menyebabkan lebih banyak pekerjaan untuk VACUUMnanti seperti pembaruan lainnya.

Entri indeks dan kolom TOASTed di mana tidak ada kolom yang terlibat yang diubah dapat tetap sama, tetapi itu berlaku untuk setiap baris yang diperbarui. Terkait:

Ini hampir selalu merupakan ide yang baik untuk mengecualikan pembaruan kosong tersebut (ketika ada kemungkinan itu terjadi). Anda tidak memberikan definisi tabel dalam pertanyaan Anda (yang selalu merupakan ide bagus). Kita harus menganggap first_namebisa NULL (yang tidak akan mengejutkan untuk "nama depan"), oleh karena itu kueri harus menggunakan perbandingan NULL-aman :

UPDATE users
SET    first_name = 'Michael'
WHERE  id = 123
AND   first_name IS DISTINCT FROM 'Michael';

Jika first_name IS NULLsebelum pembaruan, pengujian dengan hanya first_name <> 'Michael'akan mengevaluasi ke NULL dan dengan demikian mengecualikan baris dari pembaruan. Kesalahan licik. Jika kolom didefinisikanNOT NULL , gunakan pemeriksaan kesetaraan sederhana, karena itu sedikit lebih murah.

Terkait:

Erwin Brandstetter
sumber
1
Indexes entries and TOASTed columns where none of the involved columns are changed can stay the sameTetapi bukankah mereka harus diperbarui untuk menunjuk ke lokasi barisan yang baru?
dvtan
1
@ dtgq: Tidak dengan pembaruan HOT, di mana indeks dapat tetap menunjuk ke lokasi lama, dan heap fetches harus melintasi rantai HOT untuk mendapatkan tuple langsung. Saya menambahkan tautan ke penjelasan lebih lanjut di atas.
Erwin Brandstetter
1
Bagaimana dengan panggilan MVCC untuk pembaruan noop untuk menulis tuple baru?
jberryman
@jberryman: Tidak yakin saya mengerti. Apa pun caranya, tanyakan pertanyaan Anda sebagai pertanyaan baru . Anda selalu dapat menautkan ini untuk konteks. Dan Anda dapat meninggalkan komentar di sini untuk menautkan kembali (dan dapatkan perhatian saya).
Erwin Brandstetter
2
@jberryman: Saya sebenarnya tidak tahu alasan mengapa proyek berjalan seperti ini. Itu sudah lama didirikan. Tapi saya berasumsi akan mahal untuk memeriksa setiap baris untuk kesetaraan dan memiliki jalur kode terpisah untuk baris yang tidak berubah. Penanganan ID transaksi akan lebih rumit - casing khusus untuk rollback, penanganan snapshot, manajemen kunci, WAL, dan apa yang tidak ...
Erwin Brandstetter
4

ORM seperti Ruby on Rail's menawarkan eksekusi yang ditangguhkan yang menandai catatan sebagai diubah (atau tidak) dan kemudian ketika diperlukan atau dipanggil, kemudian kirimkan perubahan ke database.

PostgreSQL adalah database dan bukan ORM. Ini akan menurunkan kinerja jika perlu waktu untuk memeriksa apakah nilai baru sama dengan nilai yang diperbarui dalam kueri Anda.

Karena itu akan memperbarui nilai terlepas dari apakah itu sama dengan nilai baru atau tidak.

Jika Anda ingin mencegah ini, Anda bisa menggunakan kode seperti yang disarankan Max Vernon dalam jawabannya.

Thronk
sumber
2

Anda cukup menambahkan whereklausa:

UPDATE users
SET first_name = 'Michael'
WHERE users.id = 123
    AND (first_name <> 'Michael' OR first_name IS NULL);

Jika first_namedidefinisikan sebagai NOT NULL, OR first_name IS NULLbagian dapat dihapus.

Kondisi:

(first_name <> 'Michael' OR first_name IS NULL)

dapat juga ditulis lebih elegan seperti (dalam jawaban Erwin):

first_name IS DISTINCT FROM 'Michael'
Max Vernon
sumber
Tidak tahu apakah kolom tersebut bisa NULL, itu mungkin memperkenalkan bug yang licik.
Erwin Brandstetter
1
@ ErwinBrandstetter Saya memperbarui jawabannya - lalu saya melihat komentar dan jawaban Anda!
ypercubeᵀᴹ
terima kasih untuk hasil editnya, @ypercube - dan untuk komentar tentang NULL@erwin
Max Vernon
1

Dari sudut pandang basis data

Jawaban atas pertanyaan Anda adalah ya. Pembaruan akan berlangsung. Basis data tidak memeriksa nilai sebelumnya, hanya menetapkan nilai baru.

Karena ini terjadi dalam memori (dan hanya akan ditulis ke file data setelah komit dikeluarkan) kinerja tidak akan menjadi masalah.

Dari perspektif ORM

Biasanya Anda akan memiliki Obyek yang mewakili satu baris database (bisa jauh lebih kompleks dari itu, tetapi mari kita tetap sederhana). Objek ini dikelola dalam memori (pada tingkat server aplikasi) dan hanya versi terbaru dari objek yang akan benar-benar membuatnya ke database pada titik tertentu.

Itu mungkin menjelaskan perilaku yang berbeda.

Sekarang, jangan membandingkan kapal kargo dengan printer 3D. Fakta bahwa Anda dapat mengirim printer 3D menggunakan kapal barang tidak berarti bahwa mungkin ada jenis perbandingan di antara mereka.

Nikmati!

Saya harap ini menjelaskan beberapa konsep.

Silvarion
sumber
4
Kinerja adalah dan masalah. Setiap pembaruan harus ditulis di disk (log dan tabel).
ypercubeᵀᴹ
Ini akan tergantung pada RDBMS aktual yang Anda gunakan. Tetapi kebanyakan dari mereka tidak melakukan setiap pembaruan tunggal, tetapi hanya blok komit terakhir yang mereka miliki dalam memori. Anda tidak pernah membaca atau menulis satu baris pun dalam database. Anda membaca / menulis blok dan menyimpannya dalam memori sampai Anda harus membuangnya untuk meletakkan blok baru di tempat yang sama. Sementara di memori, tidak setiap perubahan dalam satu baris akan ditulis ke disk, tetapi hanya isi blok ketika proses "penulis database" ditandai untuk membuang blok memori itu ke dalam datafile. Jadi, tidak ... Tidak masalah kecuali aplikasi Anda menahan blok terlalu lama.
Silvarion
1
pertanyaannya adalah tentang Postgres, bukan tentang DBMS yang sewenang-wenang. Dan sementara pembaruan tidak semua harus ditulis satu per satu, setiap penulisan pada database harus ditulis ke log. Jika perubahan tidak ditulis pada penyimpanan persisten, bagaimana DBMS akan selamat dari kerusakan sistem?
ypercubeᵀᴹ
Ya, ia menulis di log, dari memori juga selama pos-pos pemeriksaan. Kecuali jika Anda memiliki jumlah pengguna bersamaan yang sangat besar, itu seharusnya tidak menjadi masalah sama sekali. Log juga ditulis dalam batch. Saya pikir kita sedang berbicara tentang server. Jika Anda berbicara tentang database Postgres di laptop dengan 5400RPM HDD, ya ... Anda akan selalu memiliki masalah kinerja. Jadi, jawaban terakhir akan menjadi yang pertama ... Itu tergantung pada banyak hal.
Silvarion