Masukkan Pembaruan proc yang disimpan di SQL Server

104

Saya telah menulis proc yang disimpan yang akan melakukan pembaruan jika ada catatan, jika tidak maka akan melakukan penyisipan. Ini terlihat seperti ini:

update myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
insert into myTable (Col1, Col2) values (@col1, @col2)

Logika saya di balik menulisnya dengan cara ini adalah bahwa pembaruan akan melakukan pemilihan implisit menggunakan klausa where dan jika itu mengembalikan 0 maka penyisipan akan dilakukan.

Alternatif untuk melakukannya dengan cara ini adalah melakukan pemilihan dan kemudian berdasarkan jumlah baris yang dikembalikan, lakukan pembaruan atau penyisipan. Ini saya anggap tidak efisien karena jika Anda melakukan pembaruan itu akan menyebabkan 2 pilihan (panggilan pilih eksplisit pertama dan yang kedua implisit di mana pembaruan). Jika proc melakukan penyisipan maka tidak akan ada perbedaan dalam efisiensi.

Apakah logika saya terdengar di sini? Apakah ini cara Anda menggabungkan penyisipan dan pembaruan ke dalam proc yang disimpan?

Orang
sumber

Jawaban:

61

Asumsi Anda benar, ini adalah cara optimal untuk melakukannya dan disebut upsert / merge .

Pentingnya UPSERT - dari sqlservercentral.com :

Untuk setiap pembaruan dalam kasus yang disebutkan di atas, kami menghapus satu bacaan tambahan dari tabel jika kami menggunakan UPSERT, bukan EXISTS. Sayangnya untuk Sisipan, metode UPSERT dan IF EXISTS menggunakan jumlah pembacaan yang sama di tabel. Oleh karena itu pemeriksaan keberadaan hanya boleh dilakukan jika terdapat alasan yang sangat valid untuk membenarkan I / O tambahan. Cara yang dioptimalkan untuk melakukan sesuatu adalah dengan memastikan bahwa Anda memiliki sedikit bacaan mungkin di DB.

Strategi terbaik adalah mencoba pembaruan. Jika tidak ada baris yang terpengaruh oleh pembaruan, masukkan. Di sebagian besar situasi, baris tersebut sudah ada dan hanya satu I / O yang diperlukan.

Sunting : Silakan lihat jawaban ini dan posting blog yang ditautkan untuk mempelajari tentang masalah dengan pola ini dan bagaimana membuatnya bekerja dengan aman.

binOr
sumber
1
Yah, itu setidaknya menjawab satu pertanyaan, saya pikir. Dan saya tidak menambahkan kode karena kode dalam pertanyaan tersebut sepertinya sudah tepat untuk saya. Meskipun saya akan memasukkannya ke dalam transaksi, saya tidak mempertimbangkan tingkat isolasi untuk pembaruan. Terima kasih telah menunjukkannya dalam jawaban Anda!
bin Atau
54

Silakan baca postingan di blog saya untuk mengetahui pola yang baik dan aman yang dapat Anda gunakan. Ada banyak pertimbangan, dan jawaban yang diterima untuk pertanyaan ini jauh dari aman.

Untuk jawaban cepat, coba pola berikut. Ini akan bekerja dengan baik pada SQL 2000 dan yang lebih baru. SQL 2005 memberi Anda penanganan kesalahan yang membuka opsi lain dan SQL 2008 memberi Anda perintah MERGE.

begin tran
   update t with (serializable)
   set hitCount = hitCount + 1
   where pk = @id
   if @@rowcount = 0
   begin
      insert t (pk, hitCount)
      values (@id,1)
   end
commit tran
Sam Saffron
sumber
1
Dalam posting blog Anda, Anda menyimpulkan dengan menggunakan petunjuk WITH (updlock, serializable) dalam pemeriksaan keberadaan. Namun, membaca MSDN menyatakan: "UPDLOCK - Menentukan bahwa kunci pembaruan harus diambil dan ditahan sampai transaksi selesai." Apakah ini berarti petunjuk yang dapat diserialkan tidak berguna karena kunci pembaruan akan tetap ditahan selama sisa transaksi, atau apakah saya salah paham?
Dan Def
10

Jika akan digunakan dengan SQL Server 2000/2005, kode asli harus disertakan dalam transaksi untuk memastikan bahwa data tetap konsisten dalam skenario bersamaan.

BEGIN TRANSACTION Upsert
update myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
insert into myTable (Col1, Col2) values (@col1, @col2)
COMMIT TRANSACTION Upsert

Ini akan menimbulkan biaya kinerja tambahan, tetapi akan memastikan integritas data.

Tambahkan, seperti yang telah disarankan, MERGE harus digunakan jika tersedia.

Dima Malenko
sumber
8

MERGE adalah salah satu fitur baru di SQL Server 2008.

Jon Galloway
sumber
dan Anda harus benar-benar menggunakannya, bukan omong kosong homebrew yang sulit dibaca. Contoh yang bagus ada di sini - mssqltips.com/sqlservertip/1704/…
Rich Bryant
6

Anda tidak hanya perlu menjalankannya dalam bertransaksi, tetapi juga membutuhkan tingkat isolasi yang tinggi. Sebenarnya tingkat isolasi default adalah Baca Berkomitmen dan kode ini perlu Serializable.

SET transaction isolation level SERIALIZABLE
BEGIN TRANSACTION Upsert
UPDATE myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
  begin
    INSERT into myTable (ID, Col1, Col2) values (@ID @col1, @col2)
  end
COMMIT TRANSACTION Upsert

Mungkin menambahkan juga pemeriksaan kesalahan @@ dan rollback bisa menjadi ide yang bagus.

Tomas Tintera
sumber
@Munish Goyal Karena dalam database banyak perintah dan perintah yang dijalankan secara paralel. Kemudian utas lain dapat menyisipkan baris tepat setelah pembaruan dijalankan dan sebelum penyisipan dijalankan.
Tomas Tintera
5

Jika Anda tidak melakukan penggabungan di SQL 2008, Anda harus mengubahnya menjadi:

jika @@ rowcount = 0 dan @@ error = 0

jika tidak, jika pembaruan gagal karena suatu alasan maka itu akan mencoba dan memasukkan setelah itu karena rowcount pada pernyataan gagal adalah 0

Simon Munro
sumber
3

Penggemar berat UPSERT, benar-benar menghemat kode untuk dikelola. Berikut cara lain saya melakukannya: Salah satu parameter input adalah ID, jika ID adalah NULL atau 0, Anda tahu itu adalah INSERT, jika tidak maka update. Mengasumsikan aplikasi mengetahui jika ada ID, jadi tidak akan berfungsi dalam semua situasi, tetapi akan memotong setengah eksekusi jika Anda melakukannya.

Natron
sumber
2

Modifikasi pos Dima Malenko:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 

BEGIN TRANSACTION UPSERT 

UPDATE MYTABLE 
SET    COL1 = @col1, 
       COL2 = @col2 
WHERE  ID = @ID 

IF @@rowcount = 0 
  BEGIN 
      INSERT INTO MYTABLE 
                  (ID, 
                   COL1, 
                   COL2) 
      VALUES      (@ID, 
                   @col1, 
                   @col2) 
  END 

IF @@Error > 0 
  BEGIN 
      INSERT INTO MYERRORTABLE 
                  (ID, 
                   COL1, 
                   COL2) 
      VALUES      (@ID, 
                   @col1, 
                   @col2) 
  END 

COMMIT TRANSACTION UPSERT 

Anda dapat menjebak kesalahan dan mengirim rekaman ke tabel penyisipan yang gagal.
Saya perlu melakukan ini karena kami mengambil data apa pun yang dikirim melalui WSDL dan jika mungkin memperbaikinya secara internal.

thughes78013
sumber
1

Logika Anda tampaknya terdengar, tetapi Anda mungkin ingin mempertimbangkan untuk menambahkan beberapa kode untuk mencegah penyisipan jika Anda telah melewati kunci utama tertentu.

Sebaliknya, jika Anda selalu melakukan penyisipan jika pembaruan tidak memengaruhi rekaman apa pun, apa yang terjadi ketika seseorang menghapus rekaman sebelum Anda menjalankan "UPSERT"? Sekarang catatan yang Anda coba perbarui tidak ada, jadi itu akan membuat catatan sebagai gantinya. Itu mungkin bukan perilaku yang Anda cari.

Kevin Fairchild
sumber