VINCITORE DI DEFAULT, ne vale la pena?


19

Di solito progetto i miei database seguendo le seguenti regole:

  • Nessun altro che db_owner e sysadmin hanno accesso alle tabelle del database.
  • I ruoli utente sono controllati a livello di applicazione. Di solito utilizzo un ruolo db per concedere l'accesso a viste, procedure e funzioni memorizzate, ma in alcuni casi aggiungo una seconda regola per proteggere alcune procedure memorizzate.
  • Uso TRIGGERS per convalidare inizialmente le informazioni critiche.

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
  • Il DML viene eseguito utilizzando le stored procedure:

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

Ritieni che, in questo scenario, valga la pena utilizzare VINCOLI DEFAULT o sto aggiungendo un lavoro extra e non necessario al server DB?

Aggiornare

Capisco che usando il vincolo DEFAULT sto dando più informazioni a qualcun altro che deve amministrare il database. Ma sono principalmente interessato alle prestazioni.

Presumo che il database stia controllando sempre i valori predefiniti, anche se fornisco il valore corretto, quindi sto facendo lo stesso lavoro due volte.

Ad esempio, c'è un modo per evitare il vincolo DEFAULT all'interno dell'esecuzione di un trigger?


1
Se stai usando le procedure per tutto il tuo DML, allora farei la tua logica di validazione lì. Utilizzare i vincoli per impedire a chiunque abbia accesso alle tabelle di base di commettere errori, ma i trigger rallenteranno notevolmente gli inserti.
Jonathan Fite,

Quale convalida stai eseguendo nei trigger? Si può fare invece con vincoli di controllo?
Martin Smith,

@MartinSmith dipende, ma il vincolo del controllo viene sempre applicato, devo garantire i valori iniziali, come utente, ora corrente, ecc. Dai
McNets

1
Se si desidera una risposta "generale", è necessario rimuovere la parte "vincolo". In SQL, i vincoli convalidano i valori di input. Non definiscono un valore predefinito . Non esiste un "vincolo predefinito" in SQL. Ho aggiunto i tag sql-servere tsqlpoiché il codice nella tua domanda è altamente specifico per quel DBMS
a_horse_with_no_name

Risposte:


21

Presumo che il database stia controllando sempre i valori predefiniti, anche se fornisco il valore corretto, quindi sto facendo lo stesso lavoro due volte.

Ehm, perché dovresti assumerlo? ;-). Dato che i valori predefiniti esistono per fornire un valore quando la colonna a cui sono collegati non è presente INSERTnell'istruzione, assumerei l'esatto contrario: che sono completamente ignorati se la colonna associata è presente INSERTnell'istruzione.

Fortunatamente, nessuno di noi ha bisogno di assumere nulla a causa di questa affermazione nella domanda:

Sono principalmente interessato alle prestazioni.

Le domande sulle prestazioni sono quasi sempre verificabili. Quindi dobbiamo solo elaborare un test per consentire a SQL Server (la vera autorità qui) di rispondere a questa domanda.

IMPOSTARE

Eseguire quanto segue una volta:

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;

Esegui i test 1A e 1B singolarmente, non insieme poiché ciò distorce i tempi. Esegui ciascuno più volte per avere un'idea della tempistica media di ognuno.

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

Esegui i test 2A e 2B singolarmente, non insieme in quanto distorce i tempi. Esegui ciascuno più volte per avere un'idea della tempistica media di ognuno.

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

Dovresti vedere che non c'è una vera differenza nei tempi tra i test 1A e 1B o tra i test 2A e 2B. Quindi, no, non vi è alcuna penalità prestazionale per avere un DEFAULTdefinito ma non utilizzato.

Inoltre, oltre a documentare semplicemente il comportamento previsto, è necessario tenere presente che è in gran parte a te che importa che le istruzioni DML siano completamente contenute nelle procedure memorizzate. Alle persone di supporto non importa. I futuri sviluppatori potrebbero non essere consapevoli del tuo desiderio di avere tutto il DML incapsulato all'interno di quelle procedure memorizzate, o preoccuparti anche se lo sanno. E chiunque manterrà questo DB dopo che te ne sarai andato (o un altro progetto o lavoro) potrebbe non interessarti o potrebbe non essere in grado di impedire l'uso di un ORM, non importa quanto protestino. Quindi, Predefiniti, possono aiutare a dare alle persone un "out" quando fanno un INSERT, specialmente un fatto ad-hoc INSERTfatto da un rappresentante del supporto, in quanto quella è una colonna che non è necessario includere (motivo per cui uso sempre i valori predefiniti durante il controllo colonne della data).


E, mi è appena venuto in mente che può essere mostrato in modo piuttosto oggettivo se un DEFAULTè verificato o meno quando la colonna associata è presente INSERTnell'istruzione: fornire semplicemente un valore non valido. Il seguente test fa proprio questo:

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

Come puoi vedere, quando DEFAULTviene fornita una colonna (e un valore, non la parola chiave ), l'impostazione predefinita viene ignorata al 100%. Lo sappiamo perché ci INSERTriesce. Ma se viene utilizzato il valore predefinito, si verifica un errore in quanto viene infine eseguito.


C'è un modo per evitare il vincolo DEFAULT all'interno di un'esecuzione trigger?

Sebbene la necessità di evitare vincoli predefiniti (almeno in questo contesto) sia del tutto superflua, per completezza si può notare che sarebbe solo possibile "evitare" un vincolo predefinito all'interno di un INSTEAD OFtrigger, ma non all'interno di un AFTERtrigger. Secondo la documentazione per CREATE TRIGGER :

Se esistono vincoli sulla tabella dei trigger, questi vengono controllati dopo l'esecuzione del trigger INSTEAD OF e prima dell'esecuzione del trigger AFTER. Se i vincoli vengono violati, le azioni del trigger INSTEAD OF vengono annullate e il trigger AFTER non viene attivato.

Naturalmente, l'utilizzo di un INSTEAD OFtrigger richiederebbe:

  1. Disabilitazione del vincolo predefinito
  2. Creazione di un AFTERtrigger che abilita il vincolo

Tuttavia, non consiglierei esattamente di farlo.


1
@McNets Nessun problema :-). Questa domanda in realtà ha fornito una grande opportunità per ricercare qualcosa. E nel fare ciò, ho anche provato a testare su MySQL (dal momento che in origine avevi chiesto informazioni sui RDBMS in generale) e ho scoperto che i valori predefiniti in MySQL devono essere costanti, con un'eccezione solo CURRENT_TIMESTAMPper le colonne datetime. Quindi, il test "facile" di usare un'espressione non valida non funzionerà lì (e un valore non valido non può essere usato in CREATE TABLEquanto è validato), e non ho tempo per costruire il test delle prestazioni. Ma sospetto che la maggior parte dei RDBMS li tratterà allo stesso modo.
Solomon Rutzky,

9

Non vedo alcun danno significativo nell'avere vincoli predefiniti. In effetti, vedo un vantaggio particolare: hai definito il valore predefinito allo stesso livello di logica della definizione della tabella stessa. Se si dispone di un valore predefinito fornito nella procedura memorizzata, qualcuno deve andare lì per scoprire qual è il valore predefinito; e, non è qualcosa che sarebbe ovvio per qualcuno di nuovo nel sistema, necessariamente (se, per esempio, domani erediti un miliardo di dollari, compri la tua isola tropicale e esci e ti sposti, lasciando qualche altra povera linfa per capire le cose da soli).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.