SQL Server 2014 kompresi dan ukuran baris maksimum

8

Saya perlu membuat tabel denormalized luas dengan banyak desimal (26,8) kolom (kurang dari 1024 batas kolom, sebagian besar kolom akan menjadi nol atau nol). Saya tahu tentang 8060 byte per batasan baris, jadi saya mencoba membuat tabel dengan kompresi halaman. Kode di bawah ini membuat tabel, memasukkan satu baris dan menanyakan ukuran baris. Ukuran baris jauh di bawah batas, tetapi jika saya mencoba menambahkan satu lagi desimal (26,8) kolom ke tabel, operasi gagal dengan kesalahan "Membuat atau mengubah tabel 't1' gagal karena ukuran baris minimum adalah 8074, termasuk 1256 byte dari overhead internal. " Apakah ada cara untuk membuat tabel tunggal dengan banyak kolom?

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
Alex
sumber
1
FWIW, saya bisa mendapatkan 613 DECIMAL(26, 8) NULLbidang ke dalam tabel, tanpa kompresi halaman atau kompresi desimal. Mengaktifkan kompresi vardecimal tetapi tidak halaman, overhead melompat ke lebih dari 1 K. Ada kemungkinan di luar bahwa Anda akan dapat menyimpan lebih banyak bidang per halaman tanpa vardecimal, tergantung pada nilai Anda.
Jon of All Trades

Jawaban:

4

Batas yang Anda temui tidak ada hubungannya dengan data yang disimpan di halaman. Perhitungan dilakukan berdasarkan tipe data kolom. Itu sebabnya Anda mengalami kesalahan tanpa data di tabel. Kompresi membuat batas ini semakin buruk. Anda dapat membaca tentang detail teknis di balik overhead di sini .

Anda dapat mengatasi masalah ini dengan menggunakan kolom SPARSE . Itu berarti bahwa kemungkinan insert gagal tergantung pada apa yang Anda masukkan, tetapi Anda dapat melewati batas 8060 byte. Kode berikut menunjukkan bahwa Anda dapat membuat 1023 kolom dengan baik:

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

Namun, semua batasan di sekitarnya (baca artikel yang ditautkan) mungkin membuat ini tidak cocok untuk kasus penggunaan Anda. Secara khusus, hanya NULLnilai-nilai (tidak 0) yang dioptimalkan untuk mengambil ruang yang sangat sedikit. Jika Anda mencoba memasukkan terlalu banyak 0dalam satu baris, Anda akan mendapatkan kesalahan. Inilah yang saya lihat ketika saya mencoba memasukkan 0nilai 1023 :

Msg 511, Level 16, State 1, Line 1 Tidak dapat membuat baris ukuran 17402 yang lebih besar dari ukuran maksimum baris yang diijinkan 8060.

Saya kira jika Anda benar-benar putus asa, Anda dapat membuat kolom sebagai VARCHAR(27)gantinya. Kolom panjang variabel dapat dipindahkan dari halaman sehingga Anda dapat melebihi batas 8060 byte dalam definisi tabel tetapi memasukkan kombinasi nilai tertentu akan gagal. SQL Server memperingatkan Anda tentang hal ini ketika membuat tabel:

Peringatan: Tabel "t1" telah dibuat, tetapi ukuran baris maksimumnya melebihi maksimum yang diijinkan 8060 byte. MASUKKAN atau DIPERBARUI ke tabel ini akan gagal jika baris yang dihasilkan melebihi batas ukuran.

Kompresi halaman atau baris dapat membantu jika Anda menggunakan VARCHAR(27)pendekatan tersebut. Itu akan meminimalkan ruang yang digunakan oleh keduanya 0dan NULL. Dengan VARCHAR(27)saya dapat memasukkan 1023 0nilai dengan baik.

Joe Obbish
sumber
2

Di luar aspek teknis dan usulan penyelesaian (menggunakan VARCHAR(27)kolom) yang dibahas dalam jawaban @ Joe , saya mempertanyakan " kebutuhan untuk membuat tabel denormalized luas" sebagaimana diungkapkan oleh OP Kecuali jika ada beberapa persyaratan teknis aneh bahwa semua kolom ini harus dalam satu tabel, saya akan menyarankan / merekomendasikan menyebar mereka di banyak "saudara" tabel yang diperlukan. Tabel saudara adalah tabel yang:

  • memiliki hubungan 1-ke-1 satu sama lain,
  • semua memiliki Kunci Utama yang sama persis,
  • hanya satu yang memiliki IDENTITYkolom (dan tidak ada FK ke yang lain)
  • sisanya memiliki Kunci Asing (pada kolom PK) yang menunjuk ke PK tabel yang memiliki IDENTITY

Di sini Anda membelah baris logis di dua atau lebih tabel fisik. Tapi itu pada dasarnya apa itu normalisasi, dan database relasional apa yang dirancang untuk ditangani.

Dalam skenario ini Anda mengeluarkan beberapa ruang tambahan yang digunakan dengan menduplikasi PK, dan beberapa kompleksitas kueri tambahan karena kebutuhan untuk INNER JOINtabel bersama (sering tetapi tidak selalu, kecuali jika semua SELECTkueri menggunakan semua kolom, tapi itu biasanya tidak terjadi) atau membuat Transaksi eksplisit untuk INSERTatau UPDATEmereka bersama-sama ( DELETEdapat ditangani melalui ON DELETE CASCADEset di FK).

NAMUN, Anda mendapatkan manfaat dari memiliki model data yang tepat dengan tipe data asli yang tepat, dan tidak ada tipu daya yang dapat memiliki konsekuensi yang tidak terduga nantinya. Bahkan jika menggunakan VARCHAR(27)memungkinkan ini bekerja pada tingkat teknis, secara pragmatis saya tidak berpikir menyimpan desimal karena string adalah untuk kepentingan terbaik Anda / proyek.

Jadi, jika Anda hanya "membutuhkan" satu tabel karena tidak menyadari bahwa satu entitas logis tidak perlu diwakili secara fisik dalam satu wadah, maka jangan mencoba untuk memaksa semua ini menjadi satu tabel ketika itu akan bekerja anggun di beberapa tabel.

Contoh di bawah menggambarkan konsep dasar:

MENDIRIKAN

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

UJI

EXEC #TestInsert 'aa', 454567678989;

EXEC #TestInsert 'bb', 12312312312234;

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

Pengembalian:

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
Solomon Rutzky
sumber