默认约束,值得吗?


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 CONSTRAINTs,还是在DB服务器上添加了多余的工作?

更新资料

我知道通过使用DEFAULT约束,我可以向必须管理数据库的其他人提供更多信息。但是我对性能最感兴趣。

我假设即使我提供了正确的值,数据库也总是检查默认值,因此我将两次执行相同的工作。

例如,是否有一种方法可以避免触发器执行中的DEFAULT约束?


1
如果您对所有DML使用过程,那么我将在那里进行验证逻辑。使用约束来防止任何有权访问基表的人犯错,但是触发器将极大地降低插入速度。
乔纳森·菲特

您在触发器中执行什么验证?可以用检查约束代替吗?
马丁·史密斯

@MartinSmith这取决于,但检查约束始终应用,我需要确保初始值,如用户,当前时间等有一个在其他的我的问题:dba.stackexchange.com/questions/164678/...
McNets

1
如果您想要一个“一般”答案,则应删除“约束”部分。在SQL中,约束验证输入值。他们没有定义默认。SQL中没有“默认约束”之类的东西。我添加了标签,sql-server并且tsql由于您的问题中的代码非常特定于该DBMS
a_horse_with_no_name

Answers:


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,而不是一起执行,因为这会扭曲时间。每次运行几次,以了解每个定时的平均时间。

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

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

分别执行测试2A和2B,而不是一起执行,因为这会导致计时不正确。每次运行几次,以了解每个定时的平均时间。

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

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

您应该看到测试1A和1B之间或测试2A和2B之间的时序没有真正的区别。因此,没有,没有DEFAULT定义但不使用会降低性能。

此外,除了仅记录预期的行为外,您还需要记住,多数情况下您是在乎DML语句完全包含在存储过程中。支持者不在乎。未来的开发人员可能不会意识到您希望将所有DML封装在这些存储过程中的愿望,或者即使他们知道也不会在意。在您离开后维护该数据库的人(另一个项目或工作)可能不在乎,或者不管他们提出多少抗议,都可能无法阻止使用ORM。因此,默认值可以帮助他们在进行INSERT,特别是临时操作时给人们一个“机会”。INSERT是由支持代表,因为这是他们无需包括的一列(这就是为什么我总是在审计中使用Defaults日期列)。


而且,我刚想到,DEFAULTINSERT语句中存在关联的列时,可以相当客观地显示是否检查a :只需提供一个无效值即可。以下测试就是这样做的:

-- 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触发器需要:

  1. 禁用默认约束
  2. 创建一个AFTER启用约束的触发器

但是,我不建议您这样做。


1
@McNets没问题:-)。这个问题实际上提供了一个很好的机会来研究某些东西。这样做时,我还尝试在MySQL上进行测试(因为您最初通常询问RDBMS),发现MySQL中的Defaults必须是常量,只有CURRENT_TIMESTAMPdatetime列例外。因此,使用无效表达式的“简单”测试将无法在此处进行工作(并且CREATE TABLE由于已验证无效值,因此无法在其中使用无效值),而且我没有时间来构建性能测试。但是我怀疑大多数RDBMS都会以相同的方式对待它们。
所罗门·鲁兹基

9

我认为使用默认约束不会有太大危害。实际上,我看到了一个特别的优点-您已经在与表定义本身相同的逻辑级别上定义了默认值。如果您在存储过程中提供了默认值,则必须有人去那里查找默认值。而且,这对于系统的新手来说并不是显而易见的(例如,如果您明天继承十亿美元,购买自己的热带岛屿,然后退出并搬到那里,剩下一些可怜的树汁来解决问题)自行退出)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.