VARSAYILAN KORUMA, buna değer mi?


19

Veritabanlarımı genellikle sonraki kurallara göre tasarlıyorum:

  • Db_owner ve sysadmin dışında hiç kimse veritabanı tablolarına erişemez.
  • Kullanıcı rolleri uygulama katmanında kontrol edilir. Genellikle görünümlere, saklı yordamlara ve işlevlere erişim vermek için bir db rolü kullanıyorum, ancak bazı durumlarda saklı yordamları korumak için ikinci bir kural ekliyorum.
  • Başlangıçta kritik bilgileri doğrulamak için TRIGGERS kullanıyorum.

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, saklı yordamlar kullanılarak yürütülür:

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

Bu senaryoda, VARSAYILAN KISITLAMALAR kullanmak için buna değer olduğunu düşünüyor musunuz, yoksa DB sunucusuna ekstra ve gereksiz bir iş ekliyorum?

Güncelleme

DEFAULT kısıtlaması kullanarak veritabanını yönetmek için gereken bir başkasına daha fazla bilgi verdiğimi anlıyorum. Ama çoğunlukla performansla ilgileniyorum.

Ben doğru değeri sağlasam bile veritabanı her zaman varsayılan değerleri kontrol varsayalım, bu nedenle iki kez aynı işi yapıyorum.

Örneğin, bir tetikleyici yürütmesinde DEFAULT kısıtlamasından kaçınmanın bir yolu var mı?


1
Tüm DML'niz için prosedürler kullanıyorsanız, doğrulama mantığınızı orada yapacağım. Temel tablolara erişimi olan herkesin hata yapmasını önlemek için kısıtlamalar kullanın, ancak tetikleyiciler ekleri önemli ölçüde yavaşlatır.
Jonathan Fite

Tetikleyicilerde ne tür doğrulama yapıyorsunuz? Bunun yerine kontrol kısıtlamaları ile yapılabilir mi?
Martin Smith

O bağlıdır, ancak onay kısıtlaması her zaman uygulanır @MartinSmith, benim soruların diğer göz at vb kullanıcı, şimdiki zaman gibi başlangıç değerlerini sağlamak gerekir: dba.stackexchange.com/questions/164678/...
McNets

1
"Genel" bir yanıt istiyorsanız, "kısıtlama" bölümünü kaldırmalısınız. SQL'de, kısıtlamalar giriş değerlerini doğrular . Varsayılan bir değer tanımlamazlar . SQL'de "varsayılan kısıtlama" diye bir şey yoktur. Etiketleri ekledim sql-servervetsql sorunuzdaki kod bu
DBMS'ye

Yanıtlar:


21

Ben doğru değeri sağlasam bile veritabanı her zaman varsayılan değerleri kontrol varsayalım, bu nedenle iki kez aynı işi yapıyorum.

Peki, neden bunu varsayıyorsun? ;-). Varsayılanların ekli oldukları sütun INSERTdeyimde mevcut olmadığında bir değer sağlamak için var olduğu göz önüne alındığında , tam tersini varsayarım: ilişkili sütun INSERTdeyimde mevcutsa tamamen yok sayılır .

Neyse ki, hiçbirimiz sorudaki bu ifade nedeniyle herhangi bir şey varsaymamız gerekmez:

Çoğunlukla performansla ilgileniyorum.

Performansla ilgili sorular neredeyse her zaman test edilebilir. Bu nedenle, SQL Server'ın (burada gerçek otorite) bu soruyu cevaplamasına izin vermek için bir test yapmamız gerekiyor.

KURMAK

Aşağıdakileri bir kez çalıştırın:

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;

Testleri 1A ve 1B ayrı ayrı yapın, zamanlamayı çarpıttığı için birlikte değil. Her birinin ortalama zamanlamasını anlamak için her birini birkaç kez çalıştırın.

Test 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

Test 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

Testleri 2A ve 2B ayrı ayrı yapın, zamanlamayı çarpıttığı için birlikte değil. Her birinin ortalama zamanlamasını anlamak için her birini birkaç kez çalıştırın.

Test 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);

Test 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);

1A ve 1B testleri arasında veya 2A ve 2B testleri arasında gerçek bir zamanlama farkı olmadığını görmelisiniz. Yani, hayır, bir performans cezası yokDEFAULT tanımlanmış ancak kullanılmamış .

Ayrıca, yalnızca amaçlanan davranışı belgelemenin yanı sıra, DML ifadelerinin saklı yordamlarınızda tamamen yer almasını önemseyenlerin siz olduğunu unutmayın. Destek millet umurumda değil. Gelecekteki geliştiriciler, tüm DML'leri bu saklı yordamlar içinde kapsülleme isteğinizin farkında olmayabilir veya bilmelerine rağmen dikkatli olabilirler. Ve gittikten sonra bu DB'yi kim tutarsa ​​(başka bir proje ya da iş) umursamayabilir ya da ne kadar protesto etseler de bir ORM kullanımını engelleyemeyebilir. Öyleyse, Defaults, INSERTözellikle ad-hoc yaparken insanlara bir "çıkış" vermelerine yardımcı olabilirINSERT bir destek destek temsilcisi tarafından yapılan , çünkü eklemeye gerek duymadıkları bir sütun (bu yüzden denetimde her zaman varsayılanları kullanıyorum tarih sütunları).


VE bana DEFAULT, INSERTdeyimde ilişkili sütun bulunduğunda a'nın işaretlenip denetlenmediğinin oldukça nesnel olarak gösterilebileceği bana geldi : sadece geçersiz bir değer sağlayın. Aşağıdaki test sadece bunu yapar:

-- 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

Gördüğünüz gibi, bir sütun (ve anahtar kelime değil, bir değer DEFAULT) sağlandığında, Varsayılan% 100 yoksayılır. Bunu biliyoruz çünkü INSERTbaşarılı. Ancak Varsayılan kullanılırsa, sonunda yürütülmekte olan bir hata vardır.


Bir tetikleyici yürütmesinde DEFAULT kısıtlamasından kaçınmanın bir yolu var mı?

(Bu bağlamda en azından) Varsayılan Kısıtlamaları önlemek gerek ederken bütünlük uğruna buna sadece "kaçınmak" bir dahilinde Bir Hata Kısıtlama mümkün olacağını kaydetti edilebilir tamamen gereksizdir INSTEAD OFama, Tetikleyici değil bir mesafede AFTERTetikleyici. CREATE TRIGGER dokümanlarına göre :

Tetikleyici tabloda kısıtlamalar varsa, bunlar INSTEAD OF tetikleyici yürütme işleminden sonra ve AFTER tetikleyici yürütme işleminden önce kontrol edilir. Kısıtlamalar ihlal edilirse, INSTEAD OF tetikleyici eylemleri geri alınır ve AFTER tetikleyici tetiklenmez.

Tabii ki, bir INSTEAD OF Tetik şunları gerektirir:

  1. Varsayılan Kısıtlamayı Devre Dışı Bırakma
  2. AFTERKısıtlamayı etkinleştiren bir Tetikleyici oluşturma

Ancak, bunu tam olarak tavsiye etmem.


1
@McNets Sorun değil :-). Bu soru aslında bir şeyi araştırmak için harika bir fırsat sağladı. Bunu yaparken de, MySQL üzerinde test yapmaya çalıştım (başlangıçta genel olarak RDBMS'leri sorduğunuzdan beri) ve MySQL'deki Varsayılanların sabit olması gerektiğini, yalnızca CURRENT_TIMESTAMPdatetime sütunları için bir istisna ile buldum . Bu nedenle, geçersiz bir ifade kullanmanın "kolay" testi burada işe yaramaz (ve CREATE TABLEdoğrulandığı için geçersiz bir değer kullanılamaz ) ve performans testini oluşturmak için zamanım yok. Ancak çoğu RDBMS'nin onlara aynı şekilde davranacağından şüpheleniyorum.
Solomon Rutzky

9

Varsayılan kısıtlamalarda önemli bir zarar görmüyorum. Aslında, belirli bir avantaj görüyorum - varsayılanı tablo tanımının kendisiyle aynı mantık düzeyinde tanımladınız. Saklı yordamda sağladığınız varsayılan bir değeriniz varsa, birinin varsayılan değerin ne olduğunu bulmak için oraya gitmesi gerekir; ve bu, sistemde yeni olan biri için açık olan bir şey değildir, mutlaka (örneğin, yarın bir milyar doları miras alırsanız, kendi tropik adayı satın alır ve oradan ayrılıp hareket eder, şeyleri anlamaya başka zayıf bir sap bırakır. kendi başlarına).

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.