DEFAULT CONSTRAINT, có đáng không?


18

Tôi thường thiết kế cơ sở dữ liệu của mình theo các quy tắc tiếp theo:

  • Không ai khác ngoài db_owner và sysadmin có quyền truy cập vào các bảng cơ sở dữ liệu.
  • Vai trò người dùng được kiểm soát ở lớp ứng dụng. Tôi thường sử dụng một vai trò db để cấp quyền truy cập vào các khung nhìn, các thủ tục và hàm được lưu trữ, nhưng trong một số trường hợp, tôi thêm một quy tắc thứ hai để bảo vệ một số thủ tục được lưu trữ.
  • Tôi sử dụng TRIGGERS để xác nhận thông tin quan trọng ban đầu.

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 được thực thi bằng các thủ tục được lưu trữ:

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

Bạn có nghĩ rằng, trong kịch bản này, đáng để sử dụng DEFAULT CONSTRAINTs hay tôi đang thêm một công việc phụ và không cần thiết vào máy chủ DB?

Cập nhật

Tôi hiểu rằng bằng cách sử dụng ràng buộc DEFAULT, tôi sẽ cung cấp thêm thông tin cho người khác phải quản trị cơ sở dữ liệu. Nhưng tôi chủ yếu quan tâm đến hiệu suất.

Tôi giả sử rằng cơ sở dữ liệu luôn kiểm tra các giá trị mặc định, ngay cả khi tôi cung cấp giá trị chính xác, do đó tôi đang thực hiện cùng một công việc hai lần.

Ví dụ: Có cách nào để tránh ràng buộc DEFAULT trong khi thực thi kích hoạt không?


1
Nếu bạn đang sử dụng các quy trình cho tất cả DML của mình, thì tôi sẽ thực hiện logic xác thực của bạn ở đó. Sử dụng các ràng buộc để ngăn bất kỳ ai có quyền truy cập vào các bảng cơ sở không mắc lỗi, nhưng các trình kích hoạt sẽ làm chậm đáng kể việc chèn.
Jonathan Fite

Xác nhận bạn đang làm gì trong kích hoạt? Nó có thể được thực hiện với các ràng buộc kiểm tra thay thế?
Martin Smith

@MartinSmith nó phụ thuộc, nhưng ràng buộc kiểm tra luôn áp dụng, tôi cần phải đảm bảo giá trị ban đầu, giống như người sử dụng, thời gian hiện tại, vv Có một cái nhìn tại khác của câu hỏi của tôi: dba.stackexchange.com/questions/164678/...
McNets

1
Nếu bạn muốn có câu trả lời "chung", bạn nên loại bỏ phần "ràng buộc". Trong SQL, các ràng buộc xác nhận các giá trị đầu vào. Họ không xác định một giá trị mặc định . Không có thứ gọi là "ràng buộc mặc định" trong SQL. Tôi đã thêm các thẻ sql-servertsqlvì mã trong câu hỏi của bạn rất đặc biệt cho DBMS đó
a_horse_with_no_name

Câu trả lời:


20

Tôi giả sử rằng cơ sở dữ liệu luôn kiểm tra các giá trị mặc định, ngay cả khi tôi cung cấp giá trị chính xác, do đó tôi đang thực hiện cùng một công việc hai lần.

Ừm, tại sao bạn lại cho rằng? ;-). Cho rằng Mặc định tồn tại để cung cấp một giá trị khi cột mà chúng được gắn vào không có trong INSERTcâu lệnh, tôi sẽ giả sử ngược lại: rằng chúng bị bỏ qua hoàn toàn nếu cột liên quan có mặt trong INSERTcâu lệnh.

May mắn thay, không ai trong chúng ta cần phải thừa nhận bất cứ điều gì do câu nói này trong câu hỏi:

Tôi chủ yếu quan tâm đến hiệu suất.

Các câu hỏi về hiệu suất gần như luôn luôn có thể kiểm tra. Vì vậy, chúng tôi chỉ cần đưa ra một thử nghiệm để cho phép SQL Server (cơ quan thực sự ở đây) trả lời câu hỏi này.

THIẾT LẬP

Chạy như sau một lần:

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;

Thực hiện các thử nghiệm riêng lẻ 1A và 1B, không cùng nhau vì điều đó làm lệch thời gian. Chạy từng cái một vài lần để có cảm giác về thời gian trung bình cho mỗi cái.

Kiểm tra 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

Kiểm tra 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

Thực hiện các thử nghiệm 2A và 2B riêng lẻ, không cùng nhau vì điều đó làm lệch thời gian. Chạy từng cái một vài lần để có cảm giác về thời gian trung bình cho mỗi cái.

Kiểm tra 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);

Kiểm tra 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);

Bạn sẽ thấy rằng không có sự khác biệt thực sự về thời gian giữa các thử nghiệm 1A và 1B, hoặc giữa các thử nghiệm 2A và 2B. Vì vậy, không, không có hình phạt hiệu suất để có một DEFAULTxác định nhưng không được sử dụng.

Ngoài ra, bên cạnh việc chỉ ghi lại hành vi dự định, bạn cần lưu ý rằng chủ yếu là bạn quan tâm đến các câu lệnh DML được chứa hoàn toàn trong các thủ tục được lưu trữ của bạn. Hỗ trợ mọi người không quan tâm. Các nhà phát triển trong tương lai có thể không nhận thức được mong muốn của bạn để có tất cả DML được gói gọn trong các quy trình được lưu trữ đó hoặc quan tâm ngay cả khi họ biết. Và bất cứ ai duy trì DB này sau khi bạn đi (dự án hoặc công việc khác) có thể không quan tâm, hoặc có thể không thể ngăn chặn việc sử dụng ORM cho dù họ có phản đối bao nhiêu. Vì vậy, Mặc định, có thể giúp họ cung cấp cho mọi người "hết" khi thực hiện INSERT, đặc biệt là quảng cáo INSERTđược thực hiện bởi một đại diện hỗ trợ, vì đó là một cột họ không cần đưa vào (đó là lý do tại sao tôi luôn sử dụng mặc định cho kiểm toán cột ngày).


VÀ, điều đó xảy ra với tôi rằng nó có thể được hiển thị khá khách quan cho dù a DEFAULTcó được kiểm tra hay không khi cột liên quan có mặt trong INSERTcâu lệnh: chỉ đơn giản cung cấp một giá trị không hợp lệ. Bài kiểm tra sau đây thực hiện điều đó:

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

Như bạn có thể thấy, khi một cột (và một giá trị, không phải từ khóa DEFAULT) được cung cấp, Mặc định sẽ bị bỏ qua 100%. Chúng tôi biết điều này bởi vì những người INSERTthành công. Nhưng nếu Mặc định được sử dụng, sẽ xảy ra lỗi vì cuối cùng nó cũng được thực thi.


Có cách nào để tránh ràng buộc DEFAULT trong khi thực thi kích hoạt không?

Mặc dù cần phải tránh các ràng buộc mặc định (ít nhất là trong bối cảnh này) là hoàn toàn không cần thiết, vì để hoàn thiện, có thể lưu ý rằng chỉ có thể "tránh" một ràng buộc mặc định trong một INSTEAD OFTrình kích hoạt, nhưng không phải trong AFTERTrình kích hoạt. Theo tài liệu cho CREATE TRIGGER :

Nếu các ràng buộc tồn tại trên bảng kích hoạt, chúng sẽ được kiểm tra sau khi thực thi kích hoạt INSTEAD OF và trước khi thực thi kích hoạt SAU. Nếu các ràng buộc bị vi phạm, các hành động kích hoạt INSTEAD OF sẽ được khôi phục và kích hoạt SAU không được kích hoạt.

Tất nhiên, sử dụng INSTEAD OFTrình kích hoạt sẽ yêu cầu:

  1. Vô hiệu hóa ràng buộc mặc định
  2. Tạo AFTERTrình kích hoạt cho phép ràng buộc

Tuy nhiên, tôi không chính xác khuyên bạn nên làm điều này.


1
@McNets Không có vấn đề :-). Câu hỏi này thực sự cung cấp một cơ hội tuyệt vời để nghiên cứu một cái gì đó. Và khi làm như vậy, tôi cũng đã thử kiểm tra trên MySQL (vì ban đầu bạn đã hỏi về RDBMS nói chung) và thấy rằng Mặc định trong MySQL cần phải là hằng số, chỉ có một ngoại lệ cho CURRENT_TIMESTAMPcác cột thời gian. Vì vậy, thử nghiệm "dễ dàng" sử dụng biểu thức không hợp lệ sẽ không hoạt động ở đó (và không thể sử dụng giá trị không hợp lệ trong CREATE TABLEkhi nó được xác thực) và tôi không có thời gian để xây dựng thử nghiệm hiệu suất. Nhưng tôi nghi ngờ rằng hầu hết các RDBMS sẽ đối xử với họ theo cách tương tự.
Solomon Rutzky

8

Tôi thấy không có tác hại đáng kể trong việc có các ràng buộc mặc định. Trong thực tế, tôi thấy một lợi thế cụ thể - bạn đã xác định mặc định ở cùng mức logic như chính định nghĩa bảng. Nếu bạn có một giá trị mặc định mà bạn cung cấp trong quy trình được lưu trữ của mình, ai đó phải đến đó để tìm hiểu giá trị mặc định là gì; và, đó không phải là điều hiển nhiên đối với người mới sử dụng hệ thống, nhất thiết (ví dụ, nếu bạn thừa kế một tỷ đô la vào ngày mai, mua hòn đảo nhiệt đới của riêng bạn, và bỏ đi và chuyển đến đó, để lại một số nhựa nghèo khác để tìm ra mọi thứ tự mình ra).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.