ВИМОГА КОНСТРЕЙН, варто?


19

Я зазвичай проектую свої бази даних, керуючись наступними правилами:

  • Ніхто інший, ніж db_owner та sysadmin, не мають доступу до таблиць баз даних.
  • Ролі користувачів керуються на рівні програми. Зазвичай я використовую одну db роль, щоб надати доступ до переглядів, збережених процедур та функцій, але в деяких випадках я додаю друге правило для захисту деяких збережених процедур.
  • Я використовую TRIGGERS для початкової перевірки критичної інформації.

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 виконується за допомогою збережених процедур:

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

Чи вважаєте ви, що для цього сценарію варто використовувати DEFAULT CONSTRAINT, або я додаю зайву та непотрібну роботу на сервер DB?

Оновлення

Я розумію, що використовуючи обмеження DEFAULT, я надаю більше інформації комусь іншому, який повинен адмініструвати базу даних. Але мене найбільше цікавлять виступи.

Я припускаю, що база даних перевіряє завжди значення за замовчуванням, навіть якщо я надаю правильне значення, отже, я виконую ту саму роботу двічі.

Наприклад, чи є спосіб уникнути обмеження DEFAULT в рамках виконання тригера?


1
Якщо ви використовуєте процедури для всіх своїх DML, я б там здійснив вашу логіку перевірки. Використовуйте обмеження, щоб не допустити помилок усіх, хто має доступ до базових таблиць, але тригери різко сповільнить вставки.
Джонатан Фійт

Яку перевірку ви робите в тригерах? Чи можна це зробити замість перевірки обмежень?
Мартін Сміт

@MartinSmith це залежить, але обмеження перевірки завжди застосовується, мені потрібно забезпечити початкові значення, такі як користувач, поточний час тощо. Перегляньте інші мої запитання: dba.stackexchange.com/questions/164678/…
McNets

1
Якщо ви хочете отримати "загальну" відповідь, слід видалити частину "обмеження". У SQL обмеженнями перевіряють вхідні значення. Вони не визначають значення за замовчуванням . У SQL немає такого поняття, як "обмеження за замовчуванням". Я додав теги, sql-serverі tsqlоскільки код у вашому запитанні дуже специфічний для цієї СУБД
a_horse_with_no_name

Відповіді:


21

Я припускаю, що база даних перевіряє завжди значення за замовчуванням, навіть якщо я надаю правильне значення, отже, я виконую ту саму роботу двічі.

Гм, навіщо ти це припускати? ;-). Зважаючи на те, що за замовчуванням існують значення, коли стовпець, до якого вони прикріплені, немає у 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);

Ви повинні побачити, що між тестами 1А та 1Б або між тестами 2А та 2Б немає реальної різниці в термінах. Отже, ні, немає пені за ефективність, щоб мати DEFAULTвизначений, але не використаний.

Крім того, окрім того, що просто документувати призначене поведінку, потрібно пам’ятати, що саме ви дбаєте про те, щоб висловлювання DML повністю містилися у ваших збережених процедурах. Підтримка людей не хвилює. Майбутні розробники, можливо, не знають про ваше бажання зробити весь DML інкапсульованим у межах цих збережених процедур, або небайдуже, навіть якщо вони це знають. І той, хто підтримує цю БД після того, як вас немає (будь-який інший проект чи робота), може не хвилюватись, або не зможе запобігти використанню ОРМ, незалежно від того, наскільки вони протестують. Таким чином, за замовчуванням вони можуть допомогти тим, що вони дають людям "вийти" під час виконання INSERT, особливо спеціального, INSERTзробленого службою підтримки, оскільки це один стовпець, який вони не потрібно включати (саме тому я завжди використовую за замовчуванням аудит стовпці дати).


І мені просто спало на думку, що можна досить об'єктивно показати, перевіряється чи ні 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 тригера відкочуються назад, і тригер ПІСЛЯ не спрацьовує.

Звичайно, використання INSTEAD OFтригера зажадає:

  1. Вимкнення обмеження за замовчуванням
  2. Створення AFTERтригера, який дозволяє обмежити

Однак я б точно не рекомендував це робити.


1
@McNets Немає проблем :-). Це питання фактично дало чудову можливість щось дослідити. Роблячи це, я також спробував протестувати на MySQL (оскільки ви спочатку запитували про RDBMS взагалі) і виявив, що за замовчуванням у MySQL повинні бути константи, за винятком виключення для CURRENT_TIMESTAMPстовпців datetime. Отже, "простий" тест використання недійсного виразу там не працюватиме (а недійсне значення не можна використовувати, CREATE TABLEоскільки воно перевірено), і я не встигаю побудувати тест на ефективність. Але я підозрюю, що більшість RDBMS поводиться з ними так само.
Соломон Руцький

9

Я не бачу суттєвої шкоди в обмеженні за замовчуванням. Насправді я бачу одну особливу перевагу - ви визначили за замовчуванням на тому ж рівні логіки, що і саме визначення таблиці. Якщо у вашій збереженій процедурі вказане значення за замовчуванням, хтось повинен зайти туди, щоб дізнатися, що таке значення за замовчуванням; і, це не те, що було б очевидним для когось нового в системі, обов'язково (якщо, наприклад, завтра ти успадковуєш мільярд доларів, купуєш свій власний тропічний острів, кидаєш і переїжджаєш туди, залишаючи якийсь інший бідний сік для роздумів про речі самостійно).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.