Я предполагаю, что база данных всегда проверяет значения по умолчанию, даже если я предоставляю правильное значение, поэтому я делаю одну и ту же работу дважды.
Хм, с чего ты это взял? ;-). Учитывая, что существуют значения по умолчанию для предоставления значения, когда столбец, к которому они присоединены, отсутствует в INSERT
выражении, я бы предположил прямо противоположное: они полностью игнорируются, если в INSERT
выражении присутствует соответствующий столбец .
К счастью, никому из нас не нужно ничего предполагать из-за этого утверждения в вопросе:
Я в основном заинтересован в производительности.
Вопросы о производительности почти всегда можно проверить. Поэтому нам просто нужно подготовить тест, чтобы SQL Server (настоящий авторитет здесь) мог ответить на этот вопрос.
НАСТРОИТЬ
Запустите следующее один раз:
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;
Выполните тесты 1A и 1B индивидуально, а не вместе, так как это искажает время. Запустите каждый из них несколько раз, чтобы получить представление о среднем времени для каждого из них.
Тест 1А
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
Тест 1Б
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
Выполните тесты 2A и 2B по отдельности, а не вместе, так как это искажает время. Запустите каждый из них несколько раз, чтобы получить представление о среднем времени для каждого из них.
Тест 2А
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);
Тест 2В
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 и 1B или между тестами 2A и 2B. Так что нет, нет снижения производительности, чтобы иметь DEFAULT
определенный, но не использованный.
Кроме того, помимо простого документирования предполагаемого поведения, вам необходимо помнить, что в основном вы заботитесь о том, чтобы DML-выражения полностью содержались в ваших хранимых процедурах. Поддержка людей не волнует. Будущие разработчики могут не знать о вашем желании инкапсулировать все DML в эти хранимые процедуры или не беспокоиться, даже если они это знают. А тому, кто поддерживает эту БД после вашего ухода (другой проект или задание), может быть наплевать, или он не сможет предотвратить использование ORM, независимо от того, сколько он протестует. Таким образом, значения по умолчанию могут помочь в том, что они дают людям «аут» при выполнении INSERT
, особенно ad-hocINSERT
сделанном представителем службы поддержки, поскольку это один столбец, который им не нужно включать (именно поэтому я всегда использую значения по умолчанию при аудите столбцы даты).
И мне просто пришло в голову, что довольно объективно можно показать, проверяется или нет DEFAULT
флажок, когда в INSERT
выражении присутствует связанный столбец : просто укажите недопустимое значение. Следующий тест делает именно это:
-- 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
Как видите, если указан столбец (и значение, а не ключевое слово DEFAULT
), значение по умолчанию игнорируется на 100%. Мы знаем это, потому что это INSERT
удается. Но если используется значение по умолчанию, возникает ошибка, поскольку он в конечном итоге выполняется.
Есть ли способ избежать ограничения DEFAULT при выполнении триггера?
Хотя необходимость избегать ограничений по умолчанию (по крайней мере, в этом контексте) совершенно не нужна, для полноты можно отметить, что можно было бы «избежать» ограничений по умолчанию только внутри INSTEAD OF
триггера, но не внутри AFTER
триггера. Согласно документации для CREATE TRIGGER :
Если для таблицы триггеров существуют ограничения, они проверяются после выполнения триггера INSTEAD OF и до выполнения триггера AFTER. Если ограничения нарушаются, действия триггера INSTEAD OF откатываются, а триггер AFTER не запускается.
Конечно, используя INSTEAD OF
триггера потребует:
- Отключение ограничения по умолчанию
- Создание
AFTER
триггера, который включает ограничение
Однако я бы точно не рекомендовал это делать.