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


19

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 valeur correcte, donc je fais le même travail deux fois.

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


1
Si vous utilisez des procédures pour tout votre DML, je ferais votre logique de validation là-bas. Utilisez des contraintes pour empêcher quiconque ayant accès aux tables de base de faire des erreurs, mais les déclencheurs ralentiront considérablement les insertions.
Jonathan Fite

Quelle validation faites-vous dans les déclencheurs? Peut-il être fait avec des contraintes de vérification à la place?
Martin Smith

@MartinSmith cela dépend, mais les contraintes de vérification sont toujours appliquées, je dois m'assurer des valeurs initiales, comme l'utilisateur, l'heure actuelle, etc. Jetez un œil à d'autres de mes questions: dba.stackexchange.com/questions/164678/…
McNets

1
Si vous voulez une réponse "générale", vous devez supprimer la partie "contrainte". En SQL, les contraintes valident les valeurs d'entrée. Ils ne définissent pas de valeur par défaut . Il n'existe pas de "contrainte par défaut" en SQL. J'ai ajouté les balises sql-serveret tsqlcomme le code dans votre question est très spécifique à ce SGBD
a_horse_with_no_name

Réponses:


21

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

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 la INSERTdéclaration, je suppose que c'est exactement le contraire: qu'elles sont complètement ignorées si la colonne associée est présente dans la INSERTdéclaration.

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.

INSTALLER

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 devriez 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 DEFAULTdéfini mais pas 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. Par conséquent, les valeurs par défaut peuvent aider en ce sens qu'elles donnent aux gens un "out" lors de la réalisation d'un INSERT, en particulier un ad-hoc INSERTeffectué 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 lors de l'audit). colonnes de date).


ET, il m'est venu à l'esprit qu'il peut être montré de manière assez objective si oui ou non DEFAULTest vérifiée lorsque la colonne associée est présente dans l' INSERTinstruction: 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, pas le mot-clé DEFAULT) est fournie, la valeur par défaut est 100% ignorée. Nous le savons parce que cela INSERTré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 des raisons d'exhaustivité, on peut noter qu'il ne serait possible "d'éviter" une contrainte par défaut dans un INSTEAD OFdéclencheur, mais pas dans un AFTERdéclencheur. 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 sont violées, les actions de déclenchement INSTEAD OF sont annulées et le déclencheur AFTER n'est pas déclenché.

Bien sûr, l'utilisation d'un INSTEAD OFdéclencheur nécessiterait:

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

Cependant, je ne recommanderais pas exactement cela.


1
@McNets Pas de problème :-). Cette question a en fait fourni une excellente occasion de rechercher quelque chose. Et ce faisant, j'ai également essayé de tester sur MySQL (puisque vous avez initialement posé des questions sur les SGBDR en général) et j'ai constaté que les valeurs par défaut dans MySQL doivent être des constantes, à l'exception seulement CURRENT_TIMESTAMPdes colonnes datetime. Ainsi, le test «facile» d'utilisation d'une expression invalide ne fonctionnera pas là (et une valeur invalide ne peut pas être utilisée dans le CREATE TABLEcar elle est validée), et je n'ai pas le temps de construire le test de performance. Mais je soupçonne que la plupart des SGBDR les traiteraient de la même manière.
Solomon Rutzky

9

Je ne vois aucun inconvénient majeur à 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).

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.