Nén SQL Server 2014 và kích thước hàng tối đa


8

Tôi cần tạo bảng không chuẩn hóa rộng với nhiều cột thập phân (26,8) (giới hạn dưới 1024 cột, hầu hết các cột sẽ là null hoặc 0). Tôi biết khoảng 8060 byte cho mỗi hàng giới hạn, vì vậy tôi đã cố gắng tạo bảng với tính năng nén trang. Mã dưới đây tạo bảng, chèn một hàng và kích thước hàng truy vấn. Kích thước hàng thấp hơn giới hạn, nhưng nếu tôi cố thêm một cột thập phân (26,8) vào bảng, thao tác không thành công với lỗi "Tạo hoặc thay đổi bảng 't1' không thành công vì kích thước hàng tối thiểu sẽ là 8074, bao gồm 1256 byte chi phí nội bộ. ". Có cách nào để tạo một bảng với nhiều cột không?

drop table t1
GO
create table t1(c1 decimal(26, 8) null)
with (data_compression = page)
GO

declare @i int = 2;
declare @sql varchar(100);
while @i <= 486
begin
    set @sql = 'alter table t1 add c' + convert(varchar, @i) + ' decimal(26, 8) null';
    execute (@sql);
    set @i += 1;
end;
GO


insert into t1(c1) select 0
GO
declare @i int = 2;
declare @sql varchar(100);
while @i <= 486
begin
    set @sql = 'update t1 set c' + convert(varchar, @i) + ' = 0';
    execute (@sql);
    set @i += 1;
end;
GO

select max_record_size_in_bytes from sys.dm_db_index_physical_stats (db_id(), object_id('t1'), NULL, NULL, 'DETAILED')
GO

1
FWIW, tôi có thể nhận 613 DECIMAL(26, 8) NULLtrường vào một bảng mà không cần nén trang hoặc nén thập phân. Kích hoạt tính năng vardecimal nhưng không nén trang, chi phí nhảy vọt lên trên 1 K. Có khả năng bên ngoài là bạn sẽ có thể lưu trữ nhiều trường hơn trên mỗi trang mà không cần vardecimal, tùy thuộc vào giá trị của bạn.
Jon của tất cả các giao dịch

Câu trả lời:


4

Giới hạn mà bạn đang chạy không liên quan gì đến dữ liệu được lưu trữ trên trang. Việc tính toán được thực hiện dựa trên các kiểu dữ liệu của các cột. Đó là lý do tại sao bạn gặp phải lỗi mà không có dữ liệu nào trong bảng. Nén làm cho giới hạn này tồi tệ hơn. Bạn có thể đọc về các chi tiết kỹ thuật đằng sau chi phí ở đây .

Bạn có thể giải quyết vấn đề này bằng cách sử dụng các cột SPARSE . Điều đó có nghĩa là việc chèn có thể thất bại tùy thuộc vào nội dung bạn chèn, nhưng bạn có thể vượt qua giới hạn 8060 byte. Đoạn mã sau cho thấy bạn có thể tạo 1023 cột tốt:

drop table t1
GO
create table t1(c1 decimal(26, 8) null)
GO

declare @i int = 2;
declare @sql varchar(100);
while @i <= 1023
begin
    set @sql = 'alter table t1 add c' + convert(varchar, @i) + ' decimal(26, 8) SPARSE null';
    execute (@sql);
    set @i += 1;
end;
GO

Tuy nhiên, tất cả các hạn chế xung quanh nó (đọc bài viết được liên kết) có thể làm cho điều này không phù hợp với trường hợp sử dụng của bạn. Cụ thể, chỉ NULLcác giá trị (không 0) được tối ưu hóa để chiếm rất ít không gian. Nếu bạn cố gắng chèn quá nhiều 0s vào một hàng, bạn sẽ gặp lỗi. Đây là những gì tôi thấy khi tôi cố gắng chèn 1023 0giá trị:

Msg 511, Cấp 16, Trạng thái 1, Dòng 1 Không thể tạo một hàng có kích thước 17402 lớn hơn kích thước hàng tối đa cho phép là 8060.

Tôi cho rằng nếu bạn thực sự tuyệt vọng, bạn có thể tạo các cột VARCHAR(27)thay thế. Các cột có chiều dài thay đổi có thể được di chuyển khỏi trang để bạn có thể vượt quá giới hạn 8060 byte trong định nghĩa bảng nhưng việc chèn các kết hợp giá trị nhất định sẽ không thành công. SQL Server cảnh báo bạn về điều này khi tạo bảng:

Cảnh báo: Bảng "t1" đã được tạo, nhưng kích thước hàng tối đa của nó vượt quá mức tối đa cho phép là 8060 byte. XÁC NHẬN hoặc CẬP NHẬT vào bảng này sẽ thất bại nếu hàng kết quả vượt quá giới hạn kích thước.

Nén trang hoặc hàng có thể hữu ích nếu bạn thực hiện theo VARCHAR(27)cách tiếp cận. Điều đó sẽ giảm thiểu không gian được sử dụng bởi cả hai 0NULL. Với VARCHAR(27)tôi có thể chèn 1023 0giá trị tốt.


2

Ngoài các khía cạnh kỹ thuật và đề xuất xung quanh (sử dụng VARCHAR(27)các cột) được thảo luận trong câu trả lời của @ Joe , tôi đặt câu hỏi về " cần tạo [một] bảng không chuẩn hóa rộng" như được thể hiện bởi OP Trừ khi có một số yêu cầu kỹ thuật kỳ lạ rằng tất cả các cột này phải ở trong một bảng duy nhất, tôi muốn đề xuất / khuyên bạn nên trải rộng chúng ra càng nhiều bảng "anh chị em" nếu cần. Các bảng anh chị em là các bảng:

  • có mối quan hệ 1-1 với nhau,
  • tất cả đều có cùng khóa chính,
  • chỉ một người có IDENTITYcột (và không có FK cho người khác)
  • phần còn lại có Khóa ngoài (trên cột PK) chỉ vào PK của bảng có IDENTITY

Ở đây bạn đang chia anh ta hàng logic trên hai hoặc nhiều bảng vật lý. Nhưng đó thực chất là những gì bình thường hóa và dù sao thì cơ sở dữ liệu quan hệ được thiết kế để xử lý.

Trong kịch bản này, bạn phải chịu một số không gian bổ sung được sử dụng bằng cách sao chép PK và một số phức tạp truy vấn bổ sung do cần phải kết hợp INNER JOINcác bảng với nhau (thường xuyên nhưng không phải luôn luôn, trừ khi tất cả các SELECTtruy vấn sử dụng tất cả các cột, nhưng điều đó thường không xảy ra) hoặc tạo một Giao dịch rõ ràng để INSERThoặc UPDATEchúng cùng nhau ( DELETEcó thể được xử lý thông qua ON DELETE CASCADEthiết lập trên FK).

TUY NHIÊN, bạn nhận được những lợi ích của việc có một mô hình dữ liệu phù hợp với các kiểu dữ liệu gốc, phù hợp và không có mánh khóe nào có thể gây ra hậu quả không lường trước sau này. Ngay cả khi việc sử dụng VARCHAR(27)cho phép điều này hoạt động ở cấp độ kỹ thuật, thực tế tôi không nghĩ việc lưu trữ số thập phân vì các chuỗi là lợi ích tốt nhất của bạn / dự án.

Vì vậy, nếu bạn chỉ "cần" một bảng duy nhất do không nhận ra rằng một thực thể logic đơn lẻ không cần phải được biểu diễn vật lý trong một thùng chứa duy nhất, thì đừng cố ép tất cả những thứ này vào một bảng khi nó sẽ hoạt động duyên dáng trên nhiều bảng.

Ví dụ dưới đây minh họa khái niệm cơ bản:

THIẾT LẬP

CREATE TABLE tempdb.dbo.T1
(
  [ID] INT NOT NULL IDENTITY(11, 2) PRIMARY KEY,
  [Col1] VARCHAR(25),
  [Col2] DATETIME NOT NULL DEFAULT (GETDATE())
);

CREATE TABLE tempdb.dbo.T2
(
  [ID] INT NOT NULL PRIMARY KEY
                    FOREIGN KEY REFERENCES tempdb.dbo.T1([ID]) ON DELETE CASCADE,
  [Col3] UNIQUEIDENTIFIER,
  [Col4] BIGINT
);

GO
CREATE PROCEDURE #TestInsert
(
  @Val1 VARCHAR(25),
  @Val4 BIGINT
)
AS
SET NOCOUNT ON;

BEGIN TRY
  BEGIN TRAN;

  DECLARE @InsertedID INT;

  INSERT INTO tempdb.dbo.T1 ([Col1])
  VALUES (@Val1);

  SET @InsertedID = SCOPE_IDENTITY();

  INSERT INTO tempdb.dbo.T2 ([ID], [Col3], [Col4])
  VALUES (@InsertedID, NEWID(), @Val4);

  COMMIT TRAN;
END TRY
BEGIN CATCH
  IF (@@TRANCOUNT > 0)
  BEGIN
    ROLLBACK TRAN;
  END;

  THROW;
END CATCH;

SELECT @InsertedID AS [ID];
GO

KIỂM TRA

EXEC #TestInsert 'aa', 454567678989;

EXEC #TestInsert 'bb', 12312312312234;

SELECT *
FROM   tempdb.dbo.T1
INNER JOIN tempdb.dbo.T2
        ON T2.[ID] = T1.[ID];

Trả về:

ID  Col1  Col2                     ID  Col3                                  Col4
11  aa    2017-07-04 10:39:32.660  11  44465676-E8A1-4F38-B5B8-F50C63A947A4  454567678989
13  bb    2017-07-04 10:41:38.180  13  BFE43379-559F-4DAD-880B-B09D7ECA4914  12312312312234
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.