UPDATE kinerja di mana tidak ada data berubah

31

Jika saya memiliki UPDATEpernyataan yang tidak benar-benar mengubah data apa pun (karena data sudah dalam keadaan diperbarui). Apakah ada manfaat kinerja dalam memberikan tanda centang pada WHEREklausa untuk mencegah pembaruan?

Misalnya akan ada perbedaan dalam kecepatan eksekusi antara UPDATE 1 dan UPDATE 2 sebagai berikut:

CREATE TABLE MyTable (ID int PRIMARY KEY, Value int);
INSERT INTO MyTable (ID, Value)
VALUES
    (1, 1),
    (2, 2),
    (3, 3);

-- UPDATE 1
UPDATE MyTable
SET
    Value = 2
WHERE
    ID = 2
    AND Value <> 2;
SELECT @@ROWCOUNT;

-- UPDATE 2
UPDATE MyTable
SET
    Value = 2
WHERE
    ID = 2;
SELECT @@ROWCOUNT;

DROP TABLE MyTable;

Alasan saya bertanya adalah bahwa saya perlu jumlah baris untuk memasukkan baris yang tidak berubah jadi saya tahu apakah akan melakukan insert jika ID tidak ada. Karena itu saya menggunakan formulir UPDATE 2. Jika ada manfaat kinerja untuk menggunakan formulir UPDATE 1, apakah mungkin untuk mendapatkan jumlah baris yang saya butuhkan?

Martin Brown
sumber
Lihat sqlperformance.com/2012/10/t-sql-queries/conditional-updates (walaupun saya tidak membuat profil case di mana tidak ada nilai yang berubah).
Aaron Bertrand

Jawaban:

24

Jika saya memiliki pernyataan UPDATE yang tidak benar-benar mengubah data apa pun (karena data sudah dalam keadaan diperbarui), apakah ada manfaat kinerja dalam menempatkan tanda centang di klausa mana untuk mencegah pembaruan?

Pasti ada karena ada sedikit perbedaan kinerja karena UPDATE 1 :

  • sebenarnya tidak memperbarui baris apa pun (karenanya tidak ada yang ditulis ke disk, bahkan aktivitas log minimal), dan
  • mengeluarkan kunci yang tidak terlalu membatasi daripada yang diperlukan untuk melakukan pembaruan aktual (karenanya lebih baik untuk concurrency) ( Silakan lihat bagian Perbarui menjelang akhir )

Namun, berapa banyak perbedaan yang perlu diukur oleh Anda pada sistem Anda dengan skema Anda, dan data, dan beban sistem. Ada beberapa faktor yang berperan dalam seberapa besar dampak UPDATE yang tidak memperbarui:

  • jumlah pertengkaran di atas meja yang sedang diperbarui
  • jumlah baris yang diperbarui
  • jika ada UPDATE Pemicu pada tabel yang sedang diperbarui (seperti yang dicatat oleh Mark dalam komentar pada Pertanyaan). Jika Anda menjalankan UPDATE TableName SET Field1 = Field1, maka Pemicu Pembaruan akan menyala dan menunjukkan bahwa bidang telah diperbarui (jika Anda memeriksa menggunakan fungsi UPDATE () atau COLUMNS_UPDATED ), dan bidang di keduanya INSERTEDdan DELETEDtabel adalah nilai yang sama.

Juga, bagian ringkasan berikut ini ditemukan dalam artikel Paul White, Dampak Pembaruan yang Tidak Memperbarui (seperti dicatat oleh @spaghettidba dalam komentar atas jawabannya):

SQL Server berisi sejumlah optimisasi untuk menghindari pencatatan atau pembilasan halaman yang tidak perlu saat memproses operasi UPDATE yang tidak akan menghasilkan perubahan apa pun ke database persisten.

  • Pembaruan yang tidak diperbarui ke tabel berkerumun umumnya menghindari logging tambahan dan pembilasan halaman, kecuali kolom yang membentuk (bagian dari) kunci kluster dipengaruhi oleh operasi pembaruan.
  • Jika ada bagian dari kunci kluster yang 'diperbarui' dengan nilai yang sama, operasi dicatat seolah-olah data telah berubah, dan halaman yang terpengaruh ditandai sebagai kotor di kumpulan buffer. Ini adalah konsekuensi dari konversi UPDATE ke operasi delete-then-insert.
  • Heap tables berperilaku sama dengan tabel berkerumun, kecuali mereka tidak memiliki kunci cluster untuk menyebabkan pembalakan tambahan atau pembilasan halaman. Ini tetap terjadi bahkan di mana kunci primer non-cluster ada di heap. Pembaruan yang tidak diperbarui ke tumpukan karena itu umumnya menghindari logging tambahan dan pembilasan (tetapi lihat di bawah).
  • Baik tumpukan dan tabel berkerumun akan mengalami pencatatan dan pembilasan ekstra untuk setiap baris di mana kolom LOB yang berisi lebih dari 8000 byte data diperbarui ke nilai yang sama menggunakan sintaksis lain selain 'SET column_name = column_name'.
  • Cukup mengaktifkan kedua jenis tingkat isolasi versi baris pada database selalu menyebabkan logging ekstra dan pembilasan. Ini terjadi terlepas dari tingkat isolasi yang berlaku untuk transaksi pembaruan.

Harap diingat (terutama jika Anda tidak mengikuti tautan untuk melihat artikel lengkap Paul), dua hal berikut:

  1. Pembaruan yang tidak memperbarui masih memiliki beberapa aktivitas log, menunjukkan bahwa suatu transaksi dimulai dan berakhir. Hanya saja tidak ada modifikasi data yang terjadi (yang masih merupakan penghematan yang baik).

  2. Seperti yang saya nyatakan di atas, Anda perlu menguji pada sistem Anda. Gunakan pertanyaan penelitian yang sama yang digunakan Paulus dan lihat apakah Anda mendapatkan hasil yang sama. Saya melihat hasil yang sedikit berbeda pada sistem saya daripada apa yang ditampilkan dalam artikel. Masih tidak ada halaman kotor untuk ditulis, tetapi sedikit lebih banyak aktivitas log.


... Saya perlu jumlah baris untuk memasukkan baris yang tidak berubah, jadi saya tahu apakah harus memasukkan jika ID tidak ada. ... apakah mungkin untuk mendapatkan jumlah baris yang saya butuhkan?

Secara sederhana, jika Anda hanya berurusan dengan satu baris, Anda dapat melakukan hal berikut:

UPDATE MyTable
SET    Value = 2
WHERE  ID = 2
AND Value <> 2;

IF (@@ROWCOUNT = 0)
BEGIN
  IF (NOT EXISTS(
                 SELECT *
                 FROM   MyTable
                 WHERE  ID = 2 -- or Value = 2 depending on the scenario
                )
     )
  BEGIN
     INSERT INTO MyTable (ID, Value) -- or leave out ID if it is an IDENTITY
     VALUES (2, 2);
  END;
END;

Untuk beberapa baris, Anda bisa mendapatkan informasi yang diperlukan untuk membuat keputusan dengan menggunakan OUTPUTklausa. Dengan menangkap secara tepat baris apa yang diperbarui, maka Anda dapat mempersempit item untuk mencari tahu perbedaan antara tidak memperbarui baris yang tidak ada dan tidak memperbarui baris yang ada tetapi tidak perlu pembaruan.

Saya menunjukkan implementasi dasar dalam jawaban berikut:

Bagaimana cara menghindari menggunakan permintaan Gabung saat memasang beberapa data menggunakan parameter xml?

Metode yang ditunjukkan dalam jawaban itu tidak menyaring baris yang ada namun tidak perlu diperbarui. Bagian itu dapat ditambahkan, tetapi pertama-tama Anda harus menunjukkan dengan tepat di mana Anda mendapatkan dataset yang Anda gabungkan MyTable. Apakah mereka datang dari meja sementara? Parameter bernilai tabel (TVP)?


PEMBARUAN 1:

Saya akhirnya dapat melakukan beberapa pengujian dan inilah yang saya temukan mengenai log transaksi dan penguncian. Pertama, skema untuk tabel:

CREATE TABLE [dbo].[Test]
(
  [ID] [int] NOT NULL CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED,
  [StringField] [varchar](500) NULL
);

Selanjutnya, tes memperbarui bidang ke nilai yang sudah dimilikinya:

UPDATE rt
SET    rt.StringField = '04CF508B-B78E-4264-B9EE-E87DC4AD237A'
FROM   dbo.Test rt
WHERE  rt.ID = 4082117

Hasil:

-- Transaction Log (2 entries):
Operation
----------------------------
LOP_BEGIN_XACT
LOP_COMMIT_XACT


-- SQL Profiler (3 Lock:Acquired events):
Mode            Type
--------------------------------------
8 - IX          5 - OBJECT
8 - IX          6 - PAGE
5 - X           7 - KEY

Akhirnya, tes yang memfilter pembaruan karena nilai tidak berubah:

UPDATE rt
SET    rt.StringField = '04CF508B-B78E-4264-B9EE-E87DC4AD237A'
FROM   dbo.Test rt
WHERE  rt.ID = 4082117
AND    rt.StringField <> '04CF508B-B78E-4264-B9EE-E87DC4AD237A';

Hasil:

-- Transaction Log (0 entries):
Operation
----------------------------


-- SQL Profiler (3 Lock:Acquired events):
Mode            Type
--------------------------------------
8 - IX          5 - OBJECT
7 - IU          6 - PAGE
4 - U           7 - KEY

Seperti yang Anda lihat, tidak ada yang tertulis pada Log Transaksi ketika memfilter baris, sebagai lawan dari dua entri yang menandai awal dan akhir dari Transaksi. Dan meskipun benar bahwa kedua entri itu hampir tidak ada, mereka tetap sesuatu.

Juga, mengunci PAGE dan sumber daya KUNCI kurang membatasi ketika menyaring baris yang belum berubah. Jika tidak ada proses lain yang berinteraksi dengan tabel ini maka itu mungkin bukan masalah (tapi seberapa besar kemungkinannya, benar-benar?). Perlu diingat bahwa pengujian yang ditampilkan di salah satu blog tertaut (dan bahkan pengujian saya) secara implisit mengasumsikan bahwa tidak ada pertengkaran di atas meja karena tidak pernah menjadi bagian dari pengujian. Mengatakan bahwa pembaruan yang tidak diperbarui begitu ringan sehingga tidak perlu melakukan penyaringan perlu diambil dengan sebutir garam karena pengujian telah dilakukan, lebih atau kurang, dalam ruang hampa udara. Namun dalam Produksi, tabel ini kemungkinan besar tidak terisolasi. Tentu saja, bisa jadi sedikit penebangan dan kunci yang lebih ketat tidak menghasilkan efisiensi yang kurang. Jadi sumber informasi yang paling dapat diandalkan untuk menjawab pertanyaan ini? SQL Server. Secara khusus:Anda SQL Server. Ini akan menunjukkan kepada Anda metode mana yang lebih baik untuk sistem Anda :-).


PEMBARUAN 2:

Jika operasi di mana nilai baru sama dengan nilai saat ini (yaitu tidak ada pembaruan) keluar nomor operasi di mana nilai baru berbeda dan pembaruan diperlukan, maka pola berikut mungkin terbukti lebih baik, terutama jika ada banyak pertengkaran di atas meja. Idenya adalah melakukan yang sederhana SELECTdulu untuk mendapatkan nilai saat ini. Jika Anda tidak mendapatkan nilai, maka Anda memiliki jawaban tentang INSERT. Jika Anda memiliki nilai, Anda dapat melakukan yang sederhana IFdan UPDATE hanya mengeluarkannya jika diperlukan.

DECLARE @CurrentValue VARCHAR(500) = NULL,
        @NewValue VARCHAR(500) = '04CF508B-B78E-4264-B9EE-E87DC4AD237A',
        @ID INT = 4082117;

SELECT @CurrentValue = rt.StringField
FROM   dbo.Test rt
WHERE  rt.ID = @ID;

IF (@CurrentValue IS NULL) -- if NULL is valid, use @@ROWCOUNT = 0
BEGIN
  -- row does not exist
  INSERT INTO dbo.Test (ID, StringField)
  VALUES (@ID, @NewValue);
END;
ELSE
BEGIN
  -- row exists, so check value to see if it is different
  IF (@CurrentValue <> @NewValue)
  BEGIN
    -- value is different, so do the update
    UPDATE rt
    SET    rt.StringField = @NewValue
    FROM   dbo.Test rt
    WHERE  rt.ID = @ID;
  END;
END;

Hasil:

-- Transaction Log (0 entries):
Operation
----------------------------


-- SQL Profiler (2 Lock:Acquired events):
Mode            Type
--------------------------------------
6 - IS          5 - OBJECT
6 - IS          6 - PAGE

Jadi hanya ada 2 kunci yang diperoleh alih-alih 3, dan kedua kunci ini adalah Intent Shared, bukan Intent eXclusive atau Intent Update ( Kompatibilitas Kunci ). Perlu diingat bahwa setiap kunci yang diperoleh juga akan dilepaskan, setiap kunci benar-benar 2 operasi, jadi metode baru ini total 4 operasi, bukan 6 operasi dalam metode yang diusulkan sebelumnya. Mempertimbangkan operasi ini berjalan sekali setiap 15 ms (kira-kira, seperti yang dinyatakan oleh OP), yaitu sekitar 66 kali per detik. Jadi proposal asli berjumlah 396 operasi kunci / buka per detik, sementara metode baru ini hanya berjumlah 264 operasi kunci / buka kunci per detik dari kunci yang bahkan lebih ringan. Ini bukan jaminan kinerja yang luar biasa, tetapi tentu saja layak diuji :-).

Solomon Rutzky
sumber
14

Perkecil sedikit dan pikirkan gambar yang lebih besar. Di dunia nyata, apakah pernyataan pembaruan Anda benar-benar akan terlihat seperti ini:

UPDATE MyTable
  SET Value = 2
WHERE
     ID = 2
     AND Value <> 2;

Atau akan terlihat seperti ini:

UPDATE Customers
  SET AddressLine1 = '123 Main St',
      AddressLine2 = 'Apt 24',
      City = 'Chicago',
      State = 'IL',
      (and a couple dozen more fields)
WHERE
     ID = 2
     AND (AddressLine1 <> '123 Main St'
     OR AddressLine2 <> 'Apt 24'
     OR City <> 'Chicago'
     OR State <> 'IL'
      (and a couple dozen more fields))

Karena di dunia nyata, tabel memiliki banyak kolom. Itu berarti Anda harus membuat banyak logika aplikasi dinamis yang kompleks untuk membangun string dinamis, ATAU Anda harus menentukan konten sebelum dan sesudah setiap bidang, setiap saat.

Jika Anda membuat pernyataan pembaruan ini secara dinamis untuk setiap tabel, hanya melewati bidang yang sedang diperbarui, Anda dapat dengan cepat mengalami masalah polusi cache paket yang mirip dengan masalah ukuran parameter NHibernate dari beberapa tahun yang lalu. Lebih buruk lagi, jika Anda membuat pernyataan pembaruan dalam SQL Server (seperti dalam prosedur tersimpan), maka Anda akan membakar siklus CPU yang berharga karena SQL Server tidak terlalu efisien untuk menggabungkan string bersama dalam skala.

Karena kerumitan itu, biasanya tidak masuk akal untuk melakukan perbandingan baris-per-baris, bidang-per-bidang seperti ini saat Anda melakukan pembaruan. Pikirkan operasi berbasis set sebagai gantinya.

Brent Ozar
sumber
1
Contoh dunia nyata saya sesederhana itu tetapi dipanggil banyak. Perkiraan saya adalah sekali setiap 15 ms pada waktu puncak. Saya bertanya-tanya apakah SQL Server cukup pintar untuk tidak menulis ke disk ketika tidak perlu.
Martin Brown
3

Anda bisa melihat peningkatan kinerja dalam melewatkan baris yang tidak perlu diperbarui hanya ketika jumlah baris besar (lebih sedikit logging, lebih sedikit halaman kotor untuk ditulis ke disk).

Saat berurusan dengan pembaruan satu baris seperti dalam kasus Anda, perbedaan kinerja benar-benar dapat diabaikan. Jika memperbarui baris dalam semua kasus memudahkan Anda, lakukanlah.

Untuk informasi lebih lanjut tentang topik ini, lihat Pembaruan Tidak Memperbarui oleh Paul White

spaghettidba
sumber
3

Anda dapat menggabungkan pembaruan dan menyisipkan ke dalam satu pernyataan. Pada SQL Server, Anda bisa menggunakan pernyataan MERGE untuk melakukan pembaruan dan menyisipkan jika tidak ditemukan. Untuk MySQL, Anda dapat menggunakan INSERT ON DUPLICATE KEY UPDATE .

Russell Harkins
sumber
1

Alih-alih memeriksa nilai semua bidang, tidak bisakah Anda mendapatkan nilai hash menggunakan kolom yang Anda minati lalu membandingkannya dengan hash yang disimpan terhadap baris dalam tabel?

IF EXISTS (Select 1 from Table where ID =@ID AND HashValue=Sha256(column1+column2))
GOTO EXIT
ELSE
Ruchira Liyanagama
sumber