web-dev-qa-db-ja.com

デフォルトの制約、それだけの価値がありますか?

私は通常、次のルールに従ってデータベースを設計します。

  • Db_ownerとsysadmin以外の誰もデータベーステーブルにアクセスできません。
  • ユーザーの役割はアプリケーション層で制御されます。私は通常、1つのdbロールを使用してビュー、ストアドプロシージャ、関数へのアクセスを許可しますが、場合によっては、いくつかのストアドプロシージャを保護するために2番目のルールを追加します。
  • トリガーを使用して、最初に重要な情報を検証します。
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制約を使用することで、データベースを管理する必要がある他のユーザーに詳細情報を提供していることを理解しています。しかし、私は主にパフォーマンスに興味があります。

正しい値を指定した場合でも、データベースは常にデフォルト値をチェックしていると想定しているため、同じジョブを2回実行しています。

たとえば、トリガーの実行内でDEFAULT制約を回避する方法はありますか?

20
McNets

正しい値を指定した場合でも、データベースは常にデフォルト値をチェックしていると想定しているため、同じ作業を2回実行しています。

ええと、なぜあなたはそれを仮定しますか? ;-)。デフォルトが存在し、それらが接続されている列がINSERTステートメントに存在しない場合に値を提供することを考えると、正反対のことを想定します:関連する列がINSERTステートメントに存在する場合、それらは完全に無視されます。

幸いなことに、私たちのどちらも問題のこの声明のために何も仮定する必要はありません:

私は主にパフォーマンスに興味があります。

パフォーマンスに関する質問は、ほとんどの場合テスト可能です。したがって、SQL Server(ここでは真の権限)がこの質問に回答できるようにするためのテストを考案する必要があります。

[〜#〜]セットアップ[〜#〜]

以下を1回実行します。

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をそれらのストアドプロシージャ内にカプセル化したいというあなたの望みを知らないかもしれませんし、知っていても気にしないかもしれません。そして、あなたが去った後(別のプロジェクトまたは仕事)にこのDBを保守している人は誰でも気にしないかもしれませんし、どんなに抗議してもORMの使用を妨げられないかもしれません。したがって、デフォルトは、INSERT、特にサポート担当者が行う特別なINSERTを実行するときに人々に「アウト」を与えるのに役立ちます。これは、含める必要のない1つの列であるためです(これは、常にデフォルトを使用する理由です)監査日付列)。


さらに、関連付けられた列が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トリガー内のデフォルトの制約のみを「回避」することが可能であることに注意してください。 notAFTERトリガー内。 CREATE TRIGGER のドキュメントによると:

トリガーテーブルに制約が存在する場合、それらはINSTEAD OFトリガーの実行後、AFTERトリガーの実行前にチェックされます。制約に違反すると、INSTEAD OFトリガーアクションがロールバックされ、AFTERトリガーは起動されません。

もちろん、INSTEAD OFトリガーを使用するには、次のものが必要です。

  1. デフォルトの制約を無効にする
  2. 制約を有効にするAFTERトリガーの作成

ただし、これを行うことをお勧めしません。

22
Solomon Rutzky

デフォルトの制約があることに大きな害はないようです。実際、特定の利点が1つあります。テーブル定義自体と同じレベルのロジックでデフォルトを定義しました。ストアドプロシージャで提供するデフォルト値がある場合、誰かがそこに行ってデフォルト値を確認する必要があります。そして、それはシステムに新しい人にとって明白なことではありません(たとえば、明日10億ドルを相続し、自分の熱帯の島を購入してそこに移動し、他の貧しい樹液を残して物事を把握する場合)自分で出します)。

9
RDFozz