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


18

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

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

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

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


1
すべてのDMLにプロシージャを使用している場合は、そこで検証ロジックを実行します。制約を使用して、ベーステーブルへのアクセス権を持っている人が間違いを犯さないようにしますが、トリガーによって挿入が大幅に遅くなります。
ジョナサンファイト

トリガーでどのような検証をしていますか?代わりにチェック制約を使用して実行できますか?
マーティンスミス

それは依存しますが、チェック制約は常に適用されている@MartinSmith、私はユーザ等、現在の時刻、などの初期値は、私の質問の他のを見ていることを確認する必要があります。dba.stackexchange.com/questions/164678/...
McNets

1
「一般的な」回答が必要な場合は、「制約」部分を削除する必要があります。SQLでは、制約入力値を検証します。彼らはデフォルト値を定義しません。SQLには「デフォルトの制約」というものはありません。私は、タグを追加sql-serverし、tsqlあなたの質問のコードとしてそのDBMSに非常に特異的である
a_horse_with_no_name

回答:


20

正しい値を指定しても、データベースは常にデフォルト値をチェックしているため、同じジョブを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サポート担当者が行う特別なことを行うときに、人々に「アウト」を与えるのに役立ちます。日付列)。


DEFAULTそして、関連する列がINSERTステートメントに存在するときに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トリガー内ではなく、トリガー内でのみデフォルト制約を「回避」できることに注意してくださいAFTERCREATE TRIGGERのドキュメントによると:

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

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

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

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


1
@McNets問題ありません:-)。この質問は、実際に何かを研究する絶好の機会を提供しました。また、そうすることで、MySQLでテストを試みて(元々RDBMSについて一般的に尋ねたため)、MySQLのデフォルトは定数である必要があることがわかりましたCURRENT_TIMESTAMP。ただし、datetimeカラムは例外です。したがって、無効な式を使用する「簡単な」テストはそこでは機能せず(CREATE TABLE検証されているため無効な値は使用できません)、パフォーマンステストを作成する時間がありません。しかし、ほとんどのRDBMSは同じ方法でそれらを扱うと思われます。
ソロモンラッツキー

8

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.