DEFAULT CONSTRAINT, lohnt es sich?

18

Normalerweise entwerfe ich meine Datenbanken nach den folgenden Regeln:

  • Niemand anderes als db_owner und sysadmin hat Zugriff auf die Datenbanktabellen.
  • Benutzerrollen werden auf Anwendungsebene gesteuert. Normalerweise verwende ich eine Datenbankrolle, um Zugriff auf die Ansichten, gespeicherten Prozeduren und Funktionen zu gewähren, aber in einigen Fällen füge ich eine zweite Regel hinzu, um einige gespeicherte Prozeduren zu schützen.
  • Ich benutze TRIGGERS, um wichtige Informationen zunächst zu validieren.

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 wird mit gespeicherten Prozeduren ausgeführt:

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

Denken Sie, dass es sich in diesem Szenario lohnt, DEFAULT CONSTRAINTs zu verwenden, oder füge ich dem DB-Server einen zusätzlichen und unnötigen Job hinzu?

Aktualisieren

Ich verstehe, dass ich durch die Verwendung der DEFAULT-Einschränkung einer anderen Person, die die Datenbank verwalten muss, mehr Informationen übergebe. Aber ich interessiere mich hauptsächlich für Leistung.

Ich gehe davon aus, dass die Datenbank immer Standardwerte überprüft, auch wenn ich den korrekten Wert eingebe. Daher mache ich den gleichen Job zweimal.

Gibt es beispielsweise eine Möglichkeit, die Einschränkung DEFAULT innerhalb einer Triggerausführung zu vermeiden?

McNets
quelle
1
Wenn Sie Prozeduren für Ihre gesamte DML verwenden, würde ich dort Ihre Validierungslogik ausführen. Verwenden Sie Einschränkungen, um zu verhindern, dass Benutzer, die Zugriff auf die Basistabellen haben, Fehler machen. Trigger verlangsamen jedoch die Einfügung erheblich.
Jonathan Fite
Welche Validierung machen Sie bei Triggern? Kann man das stattdessen mit Check-Constraints machen?
Martin Smith
@MartinSmith Das hängt davon ab, aber die Check-Bedingungen werden immer angewendet. Ich muss die Anfangswerte wie Benutzer, aktuelle Uhrzeit usw. überprüfen.
Weitere
1
Wenn Sie eine "allgemeine" Antwort wünschen, sollten Sie den Teil "Einschränkung" entfernen. In SQL validieren Einschränkungen Eingabewerte. Sie definieren nicht einen Standardwert . In SQL gibt es keine "Standardeinschränkung". Ich habe die Tags hinzugefügt sql-serverund tsqlda der Code in Ihrer Frage sehr spezifisch für dieses DBMS ist
a_horse_with_no_name

Antworten:

20

Ich gehe davon aus, dass die Datenbank immer Standardwerte überprüft, auch wenn ich den korrekten Wert eingebe. Daher mache ich den gleichen Job zweimal.

Warum würden Sie das annehmen? ;-). Da Standardwerte vorhanden sind, um einen Wert bereitzustellen, wenn die Spalte, an die sie angehängt sind, nicht in der INSERTAnweisung vorhanden ist, würde ich das genaue Gegenteil annehmen: Sie werden vollständig ignoriert, wenn die zugehörige Spalte in der INSERTAnweisung vorhanden ist.

Glücklicherweise muss keiner von uns aufgrund dieser Aussage in der Frage etwas annehmen:

Ich interessiere mich hauptsächlich für Leistung.

Fragen zur Leistung sind fast immer überprüfbar. Wir müssen also nur einen Test ausarbeiten, damit SQL Server (die wahre Autorität hier) diese Frage beantworten kann.

INSTALLIEREN

Führen Sie einmal Folgendes aus:

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;

Führen Sie die Tests 1A und 1B einzeln aus, nicht zusammen, da dies das Timing verzerrt. Führen Sie die einzelnen Schritte mehrmals aus, um einen Überblick über das durchschnittliche Timing der einzelnen Schritte zu erhalten.

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

Führen Sie die Tests 2A und 2B einzeln aus, nicht zusammen, da dies das Timing verzerrt. Führen Sie die einzelnen Schritte mehrmals aus, um einen Überblick über das durchschnittliche Timing der einzelnen Schritte zu erhalten.

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

Sie sollten feststellen, dass zwischen den Tests 1A und 1B oder zwischen den Tests 2A und 2B kein wirklicher Zeitunterschied besteht. Also, nein, es gibt keine Leistungseinbußen, die DEFAULTdefiniert, aber nicht verwendet werden.

Neben der bloßen Dokumentation des beabsichtigten Verhaltens müssen Sie auch berücksichtigen, dass es hauptsächlich Sie selbst ist, der sich darum kümmert, dass die DML-Anweisungen vollständig in Ihren gespeicherten Prozeduren enthalten sind. Support-Leute interessieren sich nicht. Zukünftige Entwickler sind sich möglicherweise nicht bewusst, dass Sie den Wunsch haben, alle DML-Dateien in diese gespeicherten Prozeduren einzuschließen, oder kümmern sich selbst dann darum, wenn sie es wissen. Und wer diese Datenbank verwaltet, nachdem Sie gegangen sind (entweder ein anderes Projekt oder eine andere Stelle), ist möglicherweise egal oder kann die Verwendung eines ORM nicht verhindern, egal wie sehr sie protestieren. Defaults können also dabei helfen, dass die Leute ein "out" bekommen, wenn sie eine INSERT, insbesondere eine Ad-hoc- INSERTAktion eines Support-Mitarbeiters durchführen, da dies eine Spalte ist, die sie nicht einschließen müssen (weshalb ich beim Audit immer Defaults verwende Datumsspalten).


UND, mir ist gerade eingefallen, dass ziemlich objektiv gezeigt werden kann, ob ein DEFAULTHäkchen gesetzt ist oder nicht , wenn die zugehörige Spalte in der INSERTAnweisung vorhanden ist: Geben Sie einfach einen ungültigen Wert an. Der folgende Test macht genau das:

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

Wie Sie sehen, DEFAULTwird der Standardwert zu 100% ignoriert , wenn eine Spalte (und kein Schlüsselwort ) angegeben wird. Wir wissen das, weil das INSERTgelingt. Wenn jedoch die Standardeinstellung verwendet wird, liegt ein Fehler vor, während diese endgültig ausgeführt wird.


Gibt es eine Möglichkeit, die Einschränkung DEFAULT innerhalb einer Triggerausführung zu vermeiden?

Während das Vermeiden von Standardeinschränkungen (zumindest in diesem Zusammenhang) völlig unnötig ist, kann der Vollständigkeit halber angemerkt werden, dass es nur möglich wäre, eine Standardeinschränkung innerhalb eines INSTEAD OFAuslösers, jedoch nicht innerhalb eines AFTERAuslösers, zu "vermeiden" . In der Dokumentation zu CREATE TRIGGER heißt es :

Wenn Einschränkungen in der Triggertabelle vorhanden sind, werden diese nach der Ausführung des INSTEAD OF-Triggers und vor der Ausführung des AFTER-Triggers überprüft. Wenn die Einschränkungen verletzt werden, werden die INSTEAD OF-Triggeraktionen zurückgesetzt und der AFTER-Trigger wird nicht ausgelöst.

Natürlich INSTEAD OFwürde die Verwendung eines Triggers Folgendes erfordern:

  1. Deaktivieren der Standardeinschränkung
  2. Erstellen eines AFTERTriggers, der die Einschränkung aktiviert

Dies würde ich jedoch nicht unbedingt empfehlen.

Solomon Rutzky
quelle
1
@McNets Kein Problem :-). Diese Frage bot tatsächlich eine großartige Gelegenheit, etwas zu recherchieren. Dabei habe ich auch versucht, auf MySQL zu testen (da Sie ursprünglich allgemein nach RDBMS gefragt haben) und festgestellt, dass Standardeinstellungen in MySQL Konstanten sein müssen, mit Ausnahme CURRENT_TIMESTAMPvon datetime-Spalten. Daher funktioniert der "einfache" Test der Verwendung eines ungültigen Ausdrucks dort nicht (und ein ungültiger Wert kann nicht verwendet werden, CREATE TABLEda er validiert wurde), und ich habe keine Zeit, den Leistungstest zu erstellen. Ich vermute jedoch, dass die meisten RDBMS sie auf die gleiche Weise behandeln würden.
Solomon Rutzky
8

Ich sehe keinen nennenswerten Schaden, wenn ich Standardbedingungen habe. In der Tat sehe ich einen besonderen Vorteil: Sie haben den Standard auf derselben Logikebene wie die Tabellendefinition selbst definiert. Wenn Sie in Ihrer gespeicherten Prozedur einen Standardwert angeben, muss jemand dorthin gehen, um herauszufinden, wie hoch der Standardwert ist. und das ist nicht unbedingt etwas, das für jemanden, der neu im System ist, offensichtlich ist (wenn Sie zum Beispiel morgen eine Milliarde Dollar erben, Ihre eigene tropische Insel kaufen und dort aufhören und umziehen und einen anderen armen Saft zurücklassen, um die Dinge herauszufinden) auf eigene Faust).

RDFozz
quelle