Database
 sql >> Teknologi Basis Data >  >> RDS >> Database

T-SQL Selasa #64 :Satu Pemicu atau Banyak?

Itu adalah hari Selasa setiap bulan – Anda tahu, hari dimana pesta blok blogger yang dikenal sebagai T-SQL Tuesday terjadi. Bulan ini dipandu oleh Russ Thomas (@SQLJudo), dan topiknya adalah, "Memanggil Semua Tuner dan Gear Head." Saya akan menangani masalah yang berhubungan dengan kinerja di sini, meskipun saya mohon maaf bahwa itu mungkin tidak sepenuhnya sejalan dengan pedoman yang ditetapkan Russ dalam undangannya (saya tidak akan menggunakan petunjuk, tanda jejak, atau panduan rencana) .

Di SQLBits minggu lalu, saya memberikan presentasi tentang pemicu, dan teman baik saya dan sesama MVP Erland Sommarskog kebetulan hadir. Pada satu titik saya menyarankan bahwa sebelum membuat pemicu baru di tabel, Anda harus memeriksa untuk melihat apakah ada pemicu yang sudah ada, dan pertimbangkan untuk menggabungkan logika daripada menambahkan pemicu tambahan. Alasan saya terutama untuk pemeliharaan kode, tetapi juga untuk kinerja. Erland bertanya apakah saya pernah menguji untuk melihat apakah ada overhead tambahan dalam mengaktifkan beberapa pemicu untuk tindakan yang sama, dan saya harus mengakui bahwa, tidak, saya tidak melakukan sesuatu yang ekstensif. Jadi saya akan melakukannya sekarang.

Di AdventureWorks2014, saya membuat satu set tabel sederhana yang pada dasarnya mewakili sys.all_objects (~2.700 baris) dan sys.all_columns (~ 9.500 baris). Saya ingin mengukur efek beban kerja dari berbagai pendekatan untuk memperbarui kedua tabel – pada dasarnya Anda memiliki pengguna yang memperbarui tabel kolom, dan Anda menggunakan pemicu untuk memperbarui kolom yang berbeda di tabel yang sama, dan beberapa kolom di tabel objek.

  • T1:Dasar :Asumsikan bahwa Anda dapat mengontrol semua akses data melalui prosedur tersimpan; dalam hal ini, pembaruan terhadap kedua tabel dapat dilakukan secara langsung, tanpa memerlukan pemicu. (Ini tidak praktis di dunia nyata, karena Anda tidak dapat secara andal melarang akses langsung ke tabel.)
  • T2:Pemicu tunggal terhadap tabel lain :Asumsikan bahwa Anda dapat mengontrol pernyataan pembaruan terhadap tabel yang terpengaruh dan menambahkan kolom lain, tetapi pembaruan ke tabel sekunder perlu diterapkan dengan pemicu. Kami akan memperbarui ketiga kolom dengan satu pernyataan.
  • T3:Pemicu tunggal terhadap kedua tabel :Dalam hal ini, kami memiliki pemicu dengan dua pernyataan, satu yang memperbarui kolom lainnya di tabel yang terpengaruh, dan satu yang memperbarui ketiga kolom di tabel sekunder.
  • T4:Pemicu tunggal terhadap kedua tabel :Seperti T3, tetapi kali ini, kami memiliki pemicu dengan empat pernyataan, satu yang memperbarui kolom lainnya di tabel yang terpengaruh, dan pernyataan untuk setiap kolom yang diperbarui di tabel sekunder. Ini mungkin cara penanganannya jika persyaratan ditambahkan dari waktu ke waktu dan pernyataan terpisah dianggap lebih aman dalam hal pengujian regresi.
  • T5:Dua pemicu :Satu pemicu hanya memperbarui tabel yang terpengaruh; yang lain menggunakan satu pernyataan untuk memperbarui tiga kolom di tabel sekunder. Ini mungkin cara yang dilakukan jika pemicu lain tidak diketahui atau jika modifikasi dilarang.
  • T6:Empat pemicu :Satu pemicu hanya memperbarui tabel yang terpengaruh; tiga lainnya memperbarui setiap kolom di tabel sekunder. Sekali lagi, ini mungkin cara yang dilakukan jika Anda tidak tahu ada pemicu lain, atau jika Anda takut menyentuh pemicu lain karena masalah regresi.

Berikut adalah sumber data yang kami tangani:

-- sys.all_objects:
SELECT * INTO dbo.src FROM sys.all_objects;
CREATE UNIQUE CLUSTERED INDEX x ON dbo.src([object_id]);
GO
 
-- sys.all_columns:
SELECT * INTO dbo.tr1 FROM sys.all_columns;
CREATE UNIQUE CLUSTERED INDEX x ON dbo.tr1([object_id], column_id);
-- repeat 5 times: tr2, tr3, tr4, tr5, tr6

Sekarang, untuk masing-masing dari 6 pengujian, kami akan menjalankan pembaruan kami 1.000 kali, dan mengukur lamanya waktu

T1:Dasar

Ini adalah skenario di mana kita cukup beruntung untuk menghindari pemicu (sekali lagi, tidak terlalu realistis). Dalam hal ini, kami akan mengukur pembacaan dan durasi batch ini. Saya memasukkan /*real*/ ke dalam teks kueri sehingga saya dapat dengan mudah menarik statistik hanya untuk pernyataan ini, dan bukan pernyataan apa pun dari dalam pemicu, karena pada akhirnya metrik digabungkan ke pernyataan yang memanggil pemicu. Perhatikan juga bahwa pembaruan aktual yang saya buat tidak terlalu masuk akal, jadi abaikan bahwa saya sedang mengatur susunan ke nama server/instance dan principal_id objek ke session_id sesi saat ini .

UPDATE /*real*/ dbo.tr1 SET name += N'',
  collation_name = @@SERVERNAME
  WHERE name LIKE '%s%';
 
UPDATE /*real*/ s SET modify_date = GETDATE(), is_ms_shipped = 0, principal_id = @@SPID
  FROM dbo.src AS s
  INNER JOIN dbo.tr1 AS t
  ON s.[object_id] = t.[object_id]
  WHERE t.name LIKE '%s%';
 
GO 1000

T2:Pemicu Tunggal

Untuk ini kita memerlukan pemicu sederhana berikut, yang hanya memperbarui dbo.src :

CREATE TRIGGER dbo.tr_tr2
ON dbo.tr2
AFTER UPDATE
AS
BEGIN
  SET NOCOUNT ON;
  UPDATE s SET modify_date = GETDATE(), is_ms_shipped = 0, principal_id = SUSER_ID()
    FROM dbo.src AS s 
	INNER JOIN inserted AS i
	ON s.[object_id] = i.[object_id];
END
GO

Kemudian batch kami hanya perlu memperbarui dua kolom di tabel utama:

UPDATE /*real*/ dbo.tr2 SET name += N'', collation_name = @@SERVERNAME
  WHERE name LIKE '%s%';
GO 1000

T3:Pemicu tunggal terhadap kedua tabel

Untuk pengujian ini, pemicu kami terlihat seperti ini:

CREATE TRIGGER dbo.tr_tr3
ON dbo.tr3
AFTER UPDATE
AS
BEGIN
  SET NOCOUNT ON;
  UPDATE t SET collation_name = @@SERVERNAME
    FROM dbo.tr3 AS t
	INNER JOIN inserted AS i
	ON t.[object_id] = i.[object_id];
 
  UPDATE s SET modify_date = GETDATE(), is_ms_shipped = 0, principal_id = @@SPID
    FROM dbo.src AS s
    INNER JOIN inserted AS i
    ON s.[object_id] = i.[object_id];
END
GO

Dan sekarang kumpulan yang kami uji hanya perlu memperbarui kolom asli di tabel utama; yang lainnya ditangani oleh pemicu:

UPDATE /*real*/ dbo.tr3 SET name += N''
  WHERE name LIKE '%s%';
GO 1000

T4:Pemicu tunggal terhadap kedua tabel

Ini seperti T3, tetapi sekarang pemicunya memiliki empat pernyataan:

CREATE TRIGGER dbo.tr_tr4
ON dbo.tr4
AFTER UPDATE
AS
BEGIN
  SET NOCOUNT ON;
  UPDATE t SET collation_name = @@SERVERNAME
    FROM dbo.tr4 AS t
	INNER JOIN inserted AS i
	ON t.[object_id] = i.[object_id];
 
  UPDATE s SET modify_date = GETDATE()
    FROM dbo.src AS s
    INNER JOIN inserted AS i
    ON s.[object_id] = i.[object_id];
 
  UPDATE s SET is_ms_shipped = 0
    FROM dbo.src AS s
    INNER JOIN inserted AS i
    ON s.[object_id] = i.[object_id];
 
  UPDATE s SET principal_id = @@SPID
    FROM dbo.src AS s
    INNER JOIN inserted AS i
    ON s.[object_id] = i.[object_id];
END
GO

Batch tes tidak berubah:

UPDATE /*real*/ dbo.tr4 SET name += N''
  WHERE name LIKE '%s%';
GO 1000

T5:Dua pemicu

Di sini kita memiliki satu pemicu untuk memperbarui tabel utama, dan satu pemicu untuk memperbarui tabel sekunder:

CREATE TRIGGER dbo.tr_tr5_1
ON dbo.tr5
AFTER UPDATE
AS
BEGIN
  SET NOCOUNT ON;
  UPDATE t SET collation_name = @@SERVERNAME
    FROM dbo.tr5 AS t
	INNER JOIN inserted AS i
	ON t.[object_id] = i.[object_id];
END
GO
 
CREATE TRIGGER dbo.tr_tr5_2
ON dbo.tr5
AFTER UPDATE
AS
BEGIN
  SET NOCOUNT ON;
  UPDATE s SET modify_date = GETDATE(), is_ms_shipped = 0, principal_id = @@SPID
    FROM dbo.src AS s
    INNER JOIN inserted AS i
    ON s.[object_id] = i.[object_id];
END
GO

Kumpulan tes sekali lagi sangat mendasar:

UPDATE /*real*/ dbo.tr5 SET name += N''
  WHERE name LIKE '%s%';
GO 1000

T6:Empat pemicu

Kali ini kami memiliki pemicu untuk setiap kolom yang terpengaruh; satu di tabel utama, dan tiga di tabel sekunder.

CREATE TRIGGER dbo.tr_tr6_1
ON dbo.tr6
AFTER UPDATE
AS
BEGIN
  SET NOCOUNT ON;
  UPDATE t SET collation_name = @@SERVERNAME
    FROM dbo.tr6 AS t
    INNER JOIN inserted AS i
    ON t.[object_id] = i.[object_id];
END
GO
 
CREATE TRIGGER dbo.tr_tr6_2
ON dbo.tr6
AFTER UPDATE
AS
BEGIN
  SET NOCOUNT ON;
  UPDATE s SET modify_date = GETDATE()
    FROM dbo.src AS s
    INNER JOIN inserted AS i
    ON s.[object_id] = i.[object_id];
END
GO
 
CREATE TRIGGER dbo.tr_tr6_3
ON dbo.tr6
AFTER UPDATE
AS
BEGIN
  SET NOCOUNT ON;
  UPDATE s SET is_ms_shipped = 0
    FROM dbo.src AS s
    INNER JOIN inserted AS i
    ON s.[object_id] = i.[object_id];
END
GO
 
CREATE TRIGGER dbo.tr_tr6_4
ON dbo.tr6
AFTER UPDATE
AS
BEGIN
  SET NOCOUNT ON;
  UPDATE s SET principal_id = @@SPID
    FROM dbo.src AS s
    INNER JOIN inserted AS i
    ON s.[object_id] = i.[object_id];
END
GO

Dan kumpulan tes:

UPDATE /*real*/ dbo.tr6 SET name += N''
  WHERE name LIKE '%s%';
GO 1000

Mengukur dampak beban kerja

Akhirnya, saya menulis kueri sederhana terhadap sys.dm_exec_query_stats untuk mengukur pembacaan dan durasi untuk setiap tes:

SELECT 
  [cmd] = SUBSTRING(t.text, CHARINDEX(N'U', t.text), 23), 
  avg_elapsed_time = total_elapsed_time / execution_count * 1.0,
  total_logical_reads
FROM sys.dm_exec_query_stats AS s 
CROSS APPLY sys.dm_exec_sql_text(s.sql_handle) AS t
WHERE t.text LIKE N'%UPDATE /*real*/%'
ORDER BY cmd;

Hasil

Saya menjalankan tes 10 kali, mengumpulkan hasilnya, dan rata-rata semuanya. Begini cara rusaknya:

Uji/Batch Durasi Rata-rata
(mikrodetik)
Total Dibaca
(8K halaman)
T1 :UPDATE /*real*/ dbo.tr1 … 22.608 205,134
T2 :UPDATE /*real*/ dbo.tr2 … 32,749 11,331,628
T3 :UPDATE /*real*/ dbo.tr3 … 72.899 22.838.308
T4 :UPDATE /*real*/ dbo.tr4 … 78.372 44.463.275
T5 :UPDATE /*real*/ dbo.tr5 … 88,563 41.514.778
T6 :UPDATE /*real*/ dbo.tr6 … 127.079 100.330.753


Dan ini adalah representasi grafis dari durasinya:

Kesimpulan

Jelas bahwa, dalam kasus ini, ada beberapa overhead substansial untuk setiap pemicu yang dipanggil – semua kumpulan ini pada akhirnya memengaruhi jumlah baris yang sama, tetapi dalam beberapa kasus, baris yang sama disentuh beberapa kali. Saya mungkin akan melakukan pengujian lanjutan lebih lanjut untuk mengukur perbedaan ketika baris yang sama tidak pernah disentuh lebih dari sekali – skema yang lebih rumit, mungkin, di mana 5 atau 10 tabel lain harus disentuh setiap kali, dan pernyataan yang berbeda ini bisa jadi dalam satu pemicu atau dalam beberapa. Dugaan saya adalah bahwa perbedaan overhead akan lebih didorong oleh hal-hal seperti konkurensi dan jumlah baris yang terpengaruh daripada oleh overhead pemicu itu sendiri – tetapi kita akan lihat.

Ingin mencoba demo sendiri? Unduh skripnya di sini.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Bug Estimasi Kardinalitas Subquery

  2. Logging Minimal dengan INSERT…SELECT dan Fast Load Context

  3. Mencadangkan Database SQL dengan VDP Advanced SQL Agent

  4. Skema Bintang

  5. ETL vs ELT:Kami Memposisikan, Anda Menilai