Lưu trữ NULL so với lưu trữ '' trong một cột varchar


7

Tôi nhận ra điều này có thể được đánh dấu là trùng lặp, nhưng tôi đặc biệt hỏi về SQL Server 2005

Tôi đã đọc lời khuyên mâu thuẫn trên internet vì vậy tôi đang hỏi ở đây. Cụ thể trong SQL Server 2005, một NULL trong cột varchar có chiếm cùng một không gian như một chuỗi rỗng không?

Tôi đã tạo một bảng 'giữ' trên một ổ đĩa khác và điền dữ liệu từ bảng nguồn và bất cứ nơi nào các trường trống tôi sử dụng nullif([field],'')để chèn null vào vị trí trống.

Sau đó, tôi đã xây dựng một bảng mới với cấu trúc chính xác giống như bảng giữ, nhưng thay vì thay thế các khoảng trống bằng null, tôi chỉ chèn các khoảng trống và cho đến nay nó dường như chiếm nhiều không gian hơn (tôi vẫn chưa hoàn thành việc điền vào nó và Tôi không thể chắc chắn rằng nó đang chiếm nhiều dữ liệu hơn)

Vì vậy, trước khi tôi điền nó xa hơn và kết thúc với một bảng lớn hơn tôi nghĩ, tôi nên bỏ các khoảng trống hoặc khoảng trống?

Biên tập:

Sau khi di chuyển dữ liệu từ bảng giữ sang bảng mới, bảng mới lớn hơn khoảng 4gb.

Bảng kích thước khác nhau

Chỉ có hai sự khác biệt nhỏ trong thiết kế bảng - Trường 'serial_number' là char (15) trong bảng giữ nhưng varchar (15) trong bảng đích. (Độ dài tối đa của một số sê-ri là 14 và có nhiều giá trị trống - tôi nghĩ khoảng 30 triệu nếu tôi nhớ lại) và chỉ mục được nhóm cho bảng giữ có thêm một cột - program_name ..

Giữ bàn

USE [Temp_holding_EWS]
GO
/****** Object:  Table [dbo].[AmtoteAccountActivity_holding]    
 Script Date: 02/17/2017 20:41:32 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[AmtoteAccountActivity_holding](
    [_Date] [char](8) NULL,[Community] [varchar](10) NULL,
    [AccountNumber] [varchar](50) NULL,
    [Branch] [varchar](10) NULL,
    [Window] [varchar](3) NULL,
    [Time] [char](8) NULL,[Balance_Forward] [varchar](10) NULL,
    [Transaction_Type] [varchar](10) NULL,
    [Program_Name] [varchar](10) NULL,
    [Race] [varchar](10) NULL,[Pool_Type] [varchar](10) NULL,
    [Amount] [money] NULL,[Runners] [varchar](60) NULL,
    [Total_Bet_Amount] [varchar](10) NULL,
    [Debit_Amount] [varchar](10) NULL,
    [Credit_Amount] [varchar](10) NULL,
    [Tx_Date] [char](8) NULL,
    [Check_Clear_Date] [varchar](10) NULL,
    [Refund_Amt] [varchar](10) NULL,
    [Bet_Pool_Modifier] [varchar](5) NULL,
    [RecordID] [int] IDENTITY(1,1) NOT NULL,
    [serial_number] [char](15) NULL,
    [handle]  AS 
       (CONVERT([money],[total_bet_amount],(0))-CONVERT([money],[refund_amt],(0))),
    [txdatetime]  AS (CONVERT([datetime],([tx_date]+' ')+[time],(11))),
    [dbdate]  AS (CONVERT([datetime],[_date],(11))),
    [Audit_Trail] [varchar](20) NULL,
 CONSTRAINT [PK_AmtoteAccountActivity_holding] PRIMARY KEY NONCLUSTERED 
(
    [RecordID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, 
ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO
SET ANSI_PADDING OFF

(Chỉ mục cụm)

USE [Temp_holding_EWS]
GO
/****** Object:  Index [IX_AmtoteAccountActivity_holding] 
    Script Date: 02/17/2017 21:08:44 ******/
CREATE CLUSTERED INDEX [IX_AmtoteAccountActivity_holding] ON 
    [dbo].[AmtoteAccountActivity_holding] 
(
    [AccountNumber] ASC,
    [_Date] ASC,
    [Program_Name] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, 
    SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF,
ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

Bảng đích

USE [EWS]
GO
/****** Object:  Table [dbo].[AmtoteAccountActivity]    
Script Date: 02/17/2017 20:48:16 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[AmtoteAccountActivity](
    [_Date] [char](8) NULL,     [Community] [varchar](10) NULL,
    [AccountNumber] [varchar](50) NULL,
    [Branch] [varchar](10) NULL,[Window] [varchar](3) NULL,
    [Time] [char](8) NULL,  [Balance_Forward] [varchar](10) NULL,
    [Transaction_Type] [varchar](10) NULL,
    [Program_Name] [varchar](10) NULL,
    [Race] [varchar](10) NULL,
    [Pool_Type] [varchar](10) NULL,
    [Amount] [money] NULL,[Runners] [varchar](60) NULL,
    [Total_Bet_Amount] [varchar](10) NULL,
    [Debit_Amount] [varchar](10) NULL,
    [Credit_Amount] [varchar](10) NULL,
    [Tx_Date] [char](8) NULL,
    [Check_Clear_Date] [varchar](10) NULL,
    [Refund_Amt] [varchar](10) NULL,
    [Bet_Pool_Modifier] [varchar](5) NULL,
    [RecordID] [int] IDENTITY(1,1) NOT NULL,
    [serial_number] [varchar](15) NULL,
    [handle]  AS 
       (CONVERT([money],[total_bet_amount],(0))-CONVERT([money],[refund_amt],(0))),
    [txdatetime]  AS (CONVERT([datetime],([tx_date]+' ')+[time],(11))),
    [dbdate]  AS (CONVERT([datetime],[_date],(11))),
    [Audit_Trail] [varchar](20) NULL,
 CONSTRAINT [PK_AmtoteAccountActivity2] PRIMARY KEY NONCLUSTERED 
(
    [RecordID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, 
ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO
SET ANSI_PADDING OFF

(Chỉ mục cụm)

USE [EWS]
GO
/****** Object:  Index [IX_AmtoteAccountActivity2]  Script Date: 02/17/2017 21:06:29 ******/
CREATE CLUSTERED INDEX [IX_AmtoteAccountActivity2] ON [dbo].[AmtoteAccountActivity] 
(
    [AccountNumber] ASC,
    [_Date] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, 
SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, 
ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

( Lưu ý: Đối với bất kỳ ai thắc mắc tại sao nó rõ ràng có giá trị tài chính và số được lưu trữ trong các trường ký tự: Đó là thiết kế bảng gốc 17 năm trước (không phải bởi tôi) và hiện có hàng trăm truy vấn sql chạy trên cơ sở dữ liệu này, nó ít hơn làm việc để giữ chúng dưới dạng varchar và các truy vấn tiếp tục truyền, hơn là thay đổi chúng thành tiền, int hoặc thập phân và thay đổi hàng trăm truy vấn)


3
Sự khác biệt về không gian ở đây sẽ không đáng kể nếu có (chia sẻ DDL). Yếu tố quan trọng hơn là ngữ nghĩa. "Không gian trống" có nghĩa là không có giá trị? Điều gì xảy ra nếu một chuỗi trống sau này trở thành có nghĩa gì đó khác? NULL là một điều hữu ích; không sợ nó .
Aaron Bertrand

Vấn đề của tôi là nó không đáng kể trên một bảng có 120 triệu hàng trên một máy chủ cũ với không gian đĩa hạn chế. Không đáng kể cho thấy có một sự khác biệt. Một số bài viết nói null chiếm 0 byte + 2 byte trên đầu, số khác nói giá trị null không chiếm dung lượng.
MrVimes

1
@MrVimes - khi bạn nói "Tôi đã xây dựng một bảng mới có cùng cấu trúc" là cột trong câu hỏi được định nghĩa là NULLhay NOT NULL?
Max Vernon

1
để có câu trả lời hay, bạn cần thêm DDL cho cả hai bảng.
Max Vernon

Có vẻ như bạn hoàn toàn có khả năng chạy thử nghiệm để xác định bài nào trong số những bài báo đó. Vì vậy, nếu mối quan tâm của bạn chỉ là về không gian, không chắc tại sao bạn lại hỏi.
Aaron Bertrand

Câu trả lời:


9

Chúng ta hãy tạo ba bảng với một cột varchar, hai trong số chúng cho phép NULL, một bảng thì không.

CREATE TABLE dbo.x1(id int IDENTITY(1,1) PRIMARY KEY, field varchar(5) null);
CREATE TABLE dbo.x2(id int IDENTITY(1,1) PRIMARY KEY, field varchar(5) null);
CREATE TABLE dbo.x3(id int IDENTITY(1,1) PRIMARY KEY, field varchar(5) not null);

Dân số chúng với 1.000.000 hàng:

;WITH x(x) AS (SELECT 0 UNION ALL SELECT x+1 FROM x WHERE x < 1000000)
INSERT dbo.x1(field) SELECT NULL FROM x OPTION (MAXRECURSION 0);
INSERT dbo.x2(field) SELECT '' FROM dbo.x1;
INSERT dbo.x3(field) SELECT '' FROM dbo.x1;

Hãy kiểm tra kích thước:

SELECT COUNT(*)*8192/1024. FROM sys.dm_db_database_page_allocations(DB_ID(), 
  OBJECT_ID(N'dbo.x1'), 1, NULL, 'DETAILED');
SELECT COUNT(*)*8192/1024. FROM sys.dm_db_database_page_allocations(DB_ID(), 
  OBJECT_ID(N'dbo.x2'), 1, NULL, 'DETAILED');
SELECT COUNT(*)*8192/1024. FROM sys.dm_db_database_page_allocations(DB_ID(), 
  OBJECT_ID(N'dbo.x3'), 1, NULL, 'DETAILED');

Các kết quả:

12,928 KB
12,936 KB
12,936 KB

Vì vậy, có vẻ như cho 1.000.000 hàng, chọn NULLqua sẽ ''tiết kiệm được 8 KB (và điều này thậm chí không được phản ánh sp_spaceused, bởi vì một trang bạn đã lưu vẫn được bảo lưu, chỉ không được phân bổ).

Lặp đi lặp lại cho một đống (một lần nữa, phải thực hiện nhiều thử nghiệm vì chúng tôi đoán về cấu trúc bảng thực tế của bạn):

12,872 KB
12,872 KB
12,928 KB

Vì vậy, không đáng kể, như tôi đã đề xuất, thậm chí ngoại suy hơn 120.000.000 hàng, sự khác biệt lớn nhất có thể (một lần nữa, tùy thuộc vào lược đồ của bạn) sẽ là 960KB trên một bảng thích hợp và 6,7 MB trên một đống. Nếu máy chủ của bạn quá chật về không gian đĩa mà 6,7 MB sẽ đưa ra quyết định, bạn có thể xem xét một đĩa bổ sung sẽ có giá bao nhiêu khi so sánh với thời gian bạn dành để điều tra việc này.

IMHO, có nhiều lý do quan trọng hơn nhiều giữa việc quyết định sử dụng NULL hay không đại diện cho "không có dữ liệu". Một câu hỏi hay với nhiều ý kiến ​​và bình luận ở đây:


Có thể đáng để đề cập đến cách nén hoặc không tạo ra sự khác biệt, đặc biệt là nếu anh ta hết dung lượng trên máy chủ của mình.
Joe Obbish

1
@Joe Vâng, hiện tại họ đang ở trên 2005, không nén. Nhưng chắc chắn, rất ít tác động ở đây. Dữ liệu trong các bảng thử nghiệm của tôi ở đây không nhận được nhiều từ việc nén (khoảng 14-16%). Nếu chúng ta có thể nhận được toàn bộ lược đồ và phân phối dữ liệu, thì nó có thể thực tế hơn. Tất nhiên, OP có thể tự kiểm tra điều đó và nên, một khi chúng ở phiên bản không có thời tiền sử, vì nó sẽ phụ thuộc nhiều vào lược đồ + dữ liệu và chi phí / giá trị nén sẽ phụ thuộc nhiều vào khối lượng công việc (chúng tôi biết không có nhiều khoảng trống trên đĩa, nhưng cũng có thể không có nhiều khoảng trống CPU).
Aaron Bertrand

Hoạt động di chuyển của tôi đã kết thúc và có vẻ như bảng hiện chiếm một khoảng trống cao hơn đáng kể với các chuỗi trống thay vì null.
MrVimes

@MrVimas Điều này có thể là do cách dữ liệu được đặt trên các trang trong quá trình di chuyển, điều này có thể khác hoàn toàn với cách dữ liệu được đưa vào nguồn một cách tự nhiên, đặc biệt là nếu song song. Bạn có thể kiểm tra nó sau khi xây dựng lại?
Aaron Bertrand

Tôi sợ phải làm lại. Có 30gb dung lượng trên ổ dữ liệu và trong quá khứ tôi đã xem không gian có sẵn dần dần biến mất khi xây dựng lại chỉ mục được nhóm trên một bảng lớn. Tôi rất vui khi bàn nhỏ hơn bàn, ngay cả khi nó không nhỏ như bàn 'giữ'.
MrVimes

3

Xem bài viết này, giải thích cách SQL lưu trữ NULL .

Về cơ bản, một cột có chiều rộng thay đổi (varchar) lưu trữ một bitmap biểu thị null hoặc không null. Nếu nó là null, thì byte không được phân bổ cho trường varchar và bit được lật.

Đối với các cột có chiều rộng cố định (char), toàn bộ trường vẫn được phân bổ, không có dữ liệu được lưu trữ trong đó. Vì vậy, một trường char 10 yte sẽ phân bổ 10 byte, NULL hay không.

Bài viết đó thực hiện chèn với dữ liệu, với NULL và với chuỗi rỗng. Sau đó, nó thăm dò kích thước trang để xem những gì đang diễn ra trong nội bộ.

Đối với cả chuỗi Null và chuỗi rỗng, 0 byte được phân bổ cho các trường varchar.


2

Đối với ít nhất định dạng bản ghi tiêu chuẩn ( FixedVar ), nó không tạo ra bất kỳ sự khác biệt nào đối với lượng không gian mà bảng tiêu thụ (nó có thể tạo ra sự khác biệt biên cho các chỉ mục như được thảo luận sau).

Cả một chuỗi rỗng varcharvà một chuỗi rỗng đều được lưu trữ theo cùng một cách chính xác. Cách duy nhất để chúng được phân biệt là liệu có một 1hoặc 0trong bitmap null. Cả hai đều có độ dài bằng 0 trong phần dữ liệu cột có độ dài thay đổi và cả hai cũng có thể tránh chiếm hai byte trong mảng bù cột biến nếu chúng không được theo sau bởi bất kỳ cột nào có chứa dữ liệu.

Một trong những ý kiến ​​nói

Đối với bảng không cho phép NULL, bạn muốn xác định cột là KHÔNG NULL, vì một số lý do, không ít trong số đó là loại bỏ yêu cầu đối với bitmap null cho cột đó

Điều này không đúng với dữ liệu. Xem Huyền thoại # 6b: Bitmap null chỉ chứa các bit cho các cột không thể .

Có một sự khác biệt nhỏ đối với các chỉ mục ở chỗ nếu tất cả các cột tham gia vào một chỉ mục không thể rỗng thì bitmap null bị bỏ qua.

Tuy nhiên, sự khác biệt là không đáng kể và bạn nên chọn tùy chọn cung cấp cho bạn ngữ nghĩa mong muốn.

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.