web-dev-qa-db-fra.com

CONTRAINTE PAR DÉFAUT, ça vaut le coup?

Je conçois généralement mes bases de données en suivant les règles suivantes:

  • Personne d'autre que db_owner et sysadmin n'a accès aux tables de la base de données.
  • Les rôles utilisateur sont contrôlés au niveau de la couche application. J'utilise généralement un rôle db pour accorder l'accès aux vues, aux procédures stockées et aux fonctions, mais dans certains cas, j'ajoute une deuxième règle pour protéger certaines procédures stockées.
  • J'utilise TRIGGERS pour valider initialement les informations critiques.
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 est exécuté à l'aide de procédures stockées:
sp_MyTable_Insert(@Field1, @Field2, @Field3, ...);
sp_MyTable_Delete(@Key1, @Key2, ...);
sp_MyTable_Update(@Key1, @Key2, @Field3, ...);

Pensez-vous que, dans ce scénario, cela vaut la peine d'utiliser les CONTRAINTES PAR DÉFAUT, ou j'ajoute un travail supplémentaire et inutile au serveur de base de données?

Mise à jour

Je comprends qu'en utilisant la contrainte DEFAULT, je donne plus d'informations à quelqu'un d'autre qui doit administrer la base de données. Mais je m'intéresse surtout à la performance.

Je suppose que la base de données vérifie toujours les valeurs par défaut, même si je fournis la bonne valeur, donc je fais deux fois le même travail.

Par exemple, existe-t-il un moyen d'éviter la contrainte DEFAULT dans une exécution de déclencheur?

20
McNets

Je suppose que la base de données vérifie toujours les valeurs par défaut, même si je fournis la bonne valeur, donc je fais deux fois le même travail.

Pourquoi supposeriez-vous cela? ;-). Étant donné que les valeurs par défaut existent pour fournir une valeur lorsque la colonne à laquelle elles sont attachées n'est pas présente dans l'instruction INSERT, je suppose que c'est exactement le contraire: qu'elles sont complètement ignorées si la colonne associée est présente dans l'instruction INSERT.

Heureusement, aucun de nous n'a à supposer quoi que ce soit en raison de cette déclaration dans la question:

Je m'intéresse principalement à la performance.

Les questions sur les performances sont presque toujours testables. Il nous suffit donc de proposer un test pour permettre à SQL Server (la véritable autorité ici) de répondre à cette question.

[~ # ~] configuration [~ # ~]

Exécutez la commande suivante une fois:

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;

Exécutez les tests 1A et 1B individuellement, pas ensemble car cela biaise le timing. Exécutez chacun plusieurs fois pour avoir une idée de la durée moyenne de chacun.

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

Exécutez les tests 2A et 2B individuellement, pas ensemble car cela biaise le timing. Exécutez chacun plusieurs fois pour avoir une idée de la durée moyenne de chacun.

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

Vous devez voir qu'il n'y a pas de réelle différence de timing entre les tests 1A et 1B, ou entre les tests 2A et 2B. Donc, non, il n'y a pas de pénalité de performance pour avoir un DEFAULT défini mais non utilisé.

De plus, en plus de simplement documenter le comportement prévu, vous devez garder à l'esprit que c'est surtout vous qui vous souciez que les instructions DML soient complètement contenues dans vos procédures stockées. Les gens de soutien s'en moquent. Les futurs développeurs pourraient ne pas être conscients de votre désir d'avoir tous les DML encapsulés dans ces procédures stockées, ou s'en soucier même s'ils le savent. Et quiconque maintient cette base de données après votre départ (soit un autre projet ou un autre travail) peut ne pas s'en soucier, ou ne pas être en mesure d'empêcher l'utilisation d'un ORM, peu importe combien ils protestent. Ainsi, les valeurs par défaut peuvent aider en ce sens qu'elles donnent aux gens un "out" lors de la création d'un INSERT, en particulier une ad-hoc INSERT effectuée par un représentant du support, car il s'agit d'une colonne qu'ils n'ont pas besoin d'inclure (c'est pourquoi j'utilise toujours les valeurs par défaut sur les colonnes de date d'audit).


ET, je viens de penser qu'il peut être montré de manière plutôt objective si oui ou non un DEFAULT est vérifié lorsque la colonne associée est présente dans l'instruction INSERT: fournissez simplement une valeur non valide. Le test suivant fait exactement cela:

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

Comme vous pouvez le voir, lorsqu'une colonne (et une valeur, et non le mot clé DEFAULT) est fournie, la valeur par défaut est 100% ignorée. Nous le savons parce que le INSERT réussit. Mais si la valeur par défaut est utilisée, il y a une erreur car elle est finalement en cours d'exécution.


Existe-t-il un moyen d'éviter la contrainte DEFAULT dans une exécution de déclencheur?

Bien qu'il soit nécessaire d'éviter les contraintes par défaut (au moins dans ce contexte), pour être complet, on peut noter qu'il ne serait possible "d'éviter" une contrainte par défaut dans un INSTEAD OF Déclenchement, mais pas dans un déclencheur AFTER. Selon la documentation de CREATE TRIGGER :

S'il existe des contraintes sur la table de déclenchement, elles sont vérifiées après l'exécution du déclencheur INSTEAD OF et avant l'exécution du déclencheur AFTER. Si les contraintes ne sont pas respectées, les actions de déclenchement INSTEAD OF sont annulées et le déclencheur AFTER n'est pas déclenché.

Bien sûr, en utilisant un INSTEAD OF Le déclencheur nécessiterait:

  1. Désactivation de la contrainte par défaut
  2. Création d'un déclencheur AFTER qui active la contrainte

Cependant, je ne recommanderais pas exactement cela.

22
Solomon Rutzky

Je ne vois aucun mal significatif à avoir des contraintes par défaut. En fait, je vois un avantage particulier - vous avez défini la valeur par défaut au même niveau de logique que la définition de table elle-même. Si vous avez une valeur par défaut que vous fournissez dans votre procédure stockée, quelqu'un doit y aller pour savoir quelle est la valeur par défaut; et, ce n'est pas quelque chose qui serait évident pour quelqu'un de nouveau dans le système, nécessairement (si, par exemple, vous héritez d'un milliard de dollars demain, achetez votre propre île tropicale, quittez et déménagez là-bas, laissant une autre mauvaise sève pour comprendre les choses) par eux-mêmes).

9
RDFozz