KONSTRA DEFAULT, layak?

19

Saya biasanya mendesain basis data saya mengikuti aturan berikut:

  • Tidak ada orang lain selain db_owner dan sysadmin yang memiliki akses ke tabel database.
  • Peran pengguna dikontrol pada lapisan aplikasi. Saya biasanya menggunakan satu peran db untuk memberikan akses ke pandangan, prosedur dan fungsi yang tersimpan, tetapi dalam beberapa kasus, saya menambahkan aturan kedua untuk melindungi beberapa prosedur yang tersimpan.
  • Saya menggunakan PEMICU awalnya memvalidasi informasi penting.

CREATE TRIGGER <TriggerName>
ON <MyTable>
[BEFORE | AFTER] INSERT
AS
    IF EXISTS (SELECT 1 
               FROM   inserted
               WHERE  Field1 <> <some_initial_value>
               OR     Field2 <> <other_initial_value>)
    BEGIN
        UPDATE MyTable
        SET    Field1 = <some_initial_value>,  
               Field2 = <other_initial_value>  
        ...  
    END
  • DML dijalankan menggunakan prosedur tersimpan:

sp_MyTable_Insert(@Field1, @Field2, @Field3, ...);
sp_MyTable_Delete(@Key1, @Key2, ...);
sp_MyTable_Update(@Key1, @Key2, @Field3, ...);

Apakah Anda berpikir bahwa, dalam skenario ini, layak untuk menggunakan DEFAULT CONSTRAINTs, atau saya menambahkan pekerjaan tambahan dan tidak perlu ke server DB?

Memperbarui

Saya mengerti bahwa dengan menggunakan batasan DEFAULT saya memberikan lebih banyak informasi kepada orang lain yang harus mengelola database. Tapi saya lebih tertarik pada kinerja.

Saya berasumsi bahwa database selalu memeriksa nilai default, bahkan jika saya memberikan nilai yang benar, maka saya melakukan pekerjaan yang sama dua kali.

Sebagai contoh, Apakah ada cara untuk menghindari batasan DEFAULT dalam eksekusi pemicu?

McNets
sumber
1
Jika Anda menggunakan prosedur untuk semua DML Anda, maka saya akan melakukan logika validasi Anda di sana. Gunakan kendala untuk mencegah siapa pun yang memiliki akses ke tabel dasar dari melakukan kesalahan, tetapi pemicu akan memperlambat sisipan secara dramatis.
Jonathan Fite
Validasi apa yang Anda lakukan di pemicu? Bisakah itu dilakukan dengan kendala cek saja?
Martin Smith
@ MartinSmith tergantung, tetapi periksa kendala selalu diterapkan, saya harus memastikan nilai awal, seperti pengguna, waktu saat ini, dll. Lihatlah pertanyaan saya yang lain: dba.stackexchange.com/questions/164678/…
McNets
1
Jika Anda menginginkan jawaban "umum", Anda harus menghapus bagian "kendala". Dalam SQL, batasan memvalidasi nilai input. Mereka tidak mendefinisikan nilai default . Tidak ada yang namanya "batasan default" dalam SQL. Saya menambahkan tag sql-serverdan tsqlkarena kode dalam pertanyaan Anda sangat spesifik untuk DBMS itu
a_horse_with_no_name

Jawaban:

21

Saya berasumsi bahwa database selalu memeriksa nilai default, bahkan jika saya memberikan nilai yang benar, maka saya melakukan pekerjaan yang sama dua kali.

Um, mengapa Anda menganggap itu? ;-). Mengingat bahwa Default ada untuk memberikan nilai ketika kolom yang dilampirkan tidak ada dalam INSERTpernyataan, saya akan mengasumsikan sebaliknya: bahwa mereka sepenuhnya diabaikan jika kolom terkait hadir diINSERT pernyataan.

Untungnya, tidak satu pun dari kita perlu berasumsi apa pun karena pernyataan ini dalam pertanyaan:

Saya sebagian besar tertarik pada kinerja.

Pertanyaan tentang kinerja hampir selalu dapat diuji. Jadi kita hanya perlu membuat tes untuk mengizinkan SQL Server (otoritas sebenarnya di sini) untuk menjawab pertanyaan ini.

MEMPERSIAPKAN

Jalankan yang berikut ini sekali:

SET NOCOUNT ON;

-- DROP TABLE #HasDefault;
CREATE TABLE #HasDefault
(
  [HasDefaultID] INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
  [SomeInt] INT NULL,
  [SomeDate] DATETIME NOT NULL DEFAULT (GETDATE())
);

-- DROP TABLE #NoDefault;
CREATE TABLE #NoDefault
(
  [NoDefaultID] INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
  [SomeInt] INT NULL,
  [SomeDate] DATETIME NOT NULL
);

-- make sure that data file and Tran Log file are grown, if need be, ahead of time:
INSERT INTO #HasDefault ([SomeInt])
  SELECT TOP (2000000) NULL
  FROM   [master].sys.[all_columns] ac1
  CROSS JOIN [master].sys.[all_columns] ac2;

Jalankan tes 1A dan 1B secara individual, tidak bersamaan karena itu mengacaukan waktunya. Jalankan masing-masing beberapa kali untuk mendapatkan rasa waktu rata-rata untuk masing-masing.

Tes 1A

TRUNCATE TABLE #HasDefault;
GO

PRINT '#HasDefault:';
SET STATISTICS TIME ON;
INSERT INTO #HasDefault ([SomeDate])
  SELECT TOP (1000000) '2017-05-15 10:11:12.000'
  FROM   [master].sys.[all_columns] ac1
  CROSS JOIN [master].sys.[all_columns] ac2;
SET STATISTICS TIME OFF;
GO

Tes 1B

TRUNCATE TABLE #NoDefault;
GO

PRINT '#NoDefault:';
SET STATISTICS TIME ON;
INSERT INTO #NoDefault ([SomeDate])
  SELECT TOP (1000000) '2017-05-15 10:11:12.000'
  FROM   [master].sys.[all_columns] ac1
  CROSS JOIN [master].sys.[all_columns] ac2;
SET STATISTICS TIME OFF;
GO

Jalankan tes 2A dan 2B secara individual, tidak bersamaan karena itu mengacaukan waktunya. Jalankan masing-masing beberapa kali untuk mendapatkan rasa waktu rata-rata untuk masing-masing.

Tes 2A

TRUNCATE TABLE #HasDefault;
GO

DECLARE @Counter INT = 0,
        @StartTime DATETIME,
        @EndTime DATETIME;

BEGIN TRAN;
--SET STATISTICS TIME ON;
SET @StartTime = GETDATE();
WHILE (@Counter < 100000)
BEGIN
  INSERT INTO #HasDefault ([SomeDate]) VALUES ('2017-05-15 10:11:12.000');
  SET @Counter = @Counter + 1;
END;
SET @EndTime = GETDATE();
--SET STATISTICS TIME OFF;
COMMIT TRAN;
PRINT DATEDIFF(MILLISECOND, @StartTime, @EndTime);

Tes 2B

TRUNCATE TABLE #NoDefault;
GO

DECLARE @Counter INT = 0,
        @StartTime DATETIME,
        @EndTime DATETIME;

BEGIN TRAN;
--SET STATISTICS TIME ON;
SET @StartTime = GETDATE();
WHILE (@Counter < 100000)
BEGIN
  INSERT INTO #NoDefault ([SomeDate]) VALUES ('2017-05-15 10:11:12.000');
  SET @Counter = @Counter + 1;
END;
SET @EndTime = GETDATE();
--SET STATISTICS TIME OFF;
COMMIT TRAN;
PRINT DATEDIFF(MILLISECOND, @StartTime, @EndTime);

Anda harus melihat bahwa tidak ada perbedaan nyata dalam pengaturan waktu antara tes 1A dan 1B, atau antara tes 2A dan 2B. Jadi, tidak, tidak ada penalti kinerja yang telah DEFAULTditentukan tetapi tidak digunakan.

Selain itu, selain hanya mendokumentasikan perilaku yang dituju, Anda perlu diingat bahwa sebagian besar Anda yang peduli tentang pernyataan DML yang sepenuhnya terkandung dalam prosedur tersimpan Anda. Orang-orang pendukung tidak peduli. Pengembang masa depan mungkin tidak menyadari keinginan Anda untuk memiliki semua DML yang dienkapsulasi dalam prosedur yang tersimpan, atau peduli bahkan jika mereka tahu. Dan siapa pun yang mempertahankan DB ini setelah Anda pergi (baik proyek atau pekerjaan lain) mungkin tidak peduli, atau mungkin tidak dapat mencegah penggunaan ORM tidak peduli berapa banyak mereka memprotes. Jadi, Defaults, dapat membantu karena mereka memberi orang "keluar" saat melakukan INSERT, terutama ad-hocINSERT dilakukan oleh perwakilan dukungan, karena itu adalah satu kolom yang tidak perlu mereka sertakan (itulah sebabnya saya selalu menggunakan default pada audit kolom tanggal).


DAN, hanya terpikir oleh saya bahwa itu dapat ditampilkan agak objektif apakah DEFAULTdiperiksa atau tidak ketika kolom terkait hadir dalam INSERTpernyataan: cukup berikan nilai yang tidak valid. Tes berikut tidak hanya itu:

-- DROP TABLE #BadDefault;
CREATE TABLE #BadDefault
(
  [BadDefaultID] INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
  [SomeInt] INT NOT NULL DEFAULT (1 / 0)
);


INSERT INTO #BadDefault ([SomeInt]) VALUES (1234); -- Success!!!
SELECT * FROM #BadDefault; -- just to be sure ;-)



INSERT INTO #BadDefault ([SomeInt]) VALUES (DEFAULT); -- Error:
/*
Msg 8134, Level 16, State 1, Line xxxxx
Divide by zero error encountered.
The statement has been terminated.
*/
SELECT * FROM #BadDefault; -- just to be sure ;-)
GO

Seperti yang Anda lihat, ketika kolom (dan nilai, bukan kata kunci DEFAULT) diberikan, Defaultnya adalah 100% diabaikan. Kami tahu ini karena INSERTberhasil. Tetapi jika Default digunakan, ada kesalahan karena akhirnya dieksekusi.


Apakah ada cara untuk menghindari batasan DEFAULT dalam eksekusi pemicu?

Meskipun perlu menghindari Batasan Default (setidaknya dalam konteks ini) sama sekali tidak perlu, demi kelengkapan, dapat dicatat bahwa hanya mungkin untuk "menghindari" Kendala Default dalam INSTEAD OFPemicu, tetapi tidak dalam AFTERPemicu. Menurut dokumentasi untuk CREATE TRIGGER :

Jika ada kendala pada tabel pemicu, mereka diperiksa setelah BUKAN eksekusi pemicu dan sebelum eksekusi pemicu SETELAH. Jika kendala dilanggar, BUKAN tindakan pemicu dibatalkan dan pemicu SETELAH tidak dipecat.

Tentu saja, menggunakan INSTEAD OFPemicu akan membutuhkan:

  1. Menonaktifkan Batasan Default
  2. Membuat AFTERTrigger yang memungkinkan Constraint

Namun, saya tidak akan merekomendasikan hal ini.

Solomon Rutzky
sumber
1
@McNets Tidak masalah :-). Pertanyaan ini sebenarnya memberikan peluang besar untuk meneliti sesuatu. Dan dengan melakukan itu, saya juga mencoba untuk menguji pada MySQL (karena Anda awalnya bertanya tentang RDBMS secara umum) dan menemukan bahwa Defaults di MySQL harus konstan, dengan hanya pengecualian untuk CURRENT_TIMESTAMPkolom datetime. Jadi, tes "mudah" menggunakan ekspresi yang tidak valid tidak akan berfungsi di sana (dan nilai yang tidak valid tidak dapat digunakan CREATE TABLEkarena itu divalidasi), dan saya tidak punya waktu untuk membuat tes kinerja. Tetapi saya menduga bahwa sebagian besar RDBMS akan memperlakukan mereka dengan cara yang sama.
Solomon Rutzky
9

Saya melihat tidak ada kerugian yang signifikan dalam memiliki kendala standar. Bahkan, saya melihat satu keuntungan khusus - Anda telah mendefinisikan default pada level logika yang sama dengan definisi tabel itu sendiri. Jika Anda memiliki nilai default yang Anda berikan dalam prosedur tersimpan Anda, seseorang harus pergi ke sana untuk mencari tahu apa nilai defaultnya; dan, itu bukan sesuatu yang akan jelas bagi seseorang yang baru dalam sistem, tentu saja (jika, misalnya, Anda mewarisi satu miliar dolar besok, membeli pulau tropis Anda sendiri, dan berhenti dan pindah ke sana, meninggalkan beberapa getah miskin lainnya untuk memikirkan hal-hal keluar sendiri).

RDFozz
sumber