Compressione di SQL Server 2014 e dimensione massima delle righe


8

Devo creare un'ampia tabella denormalizzata con molte colonne decimali (26,8) (meno del limite di 1024 colonne, la maggior parte delle colonne sarebbe nulla o zero). Conosco circa 8060 byte per limitazione di riga, quindi ho provato a creare una tabella con la compressione della pagina. Il codice seguente crea una tabella, inserisce una riga e interroga la dimensione della riga. La dimensione della riga è di gran lunga inferiore al limite, ma se provo ad aggiungere un'altra colonna decimale (26,8) alla tabella, l'operazione non riesce con errore "Creazione o modifica della tabella 't1' non riuscita perché la dimensione minima della riga sarebbe 8074, incluso 1256 byte di sovraccarico interno ". C'è un modo per creare una singola tabella con così tante colonne?

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, posso ottenere 613 DECIMAL(26, 8) NULLcampi in una tabella, senza compressione di pagina o compressione decimale. Abilitando la compressione vardecimal ma non la pagina, l'overhead passa a oltre 1 K. Esiste una possibilità esterna che sarai in grado di memorizzare più campi per pagina senza vardecimal, a seconda dei tuoi valori.
Jon of All Trades,

Risposte:


4

Il limite che stai incontrando non ha nulla a che fare con i dati memorizzati nella pagina. Il calcolo viene eseguito in base ai tipi di dati delle colonne. Ecco perché ti imbatti nell'errore senza dati nella tabella. La compressione peggiora questo limite. Puoi leggere i dettagli tecnici dietro l'overhead qui .

È possibile aggirare il problema utilizzando le colonne SPARSE . Ciò significa che gli inserimenti potrebbero non riuscire a seconda di ciò che si inserisce, ma è possibile bypassare il limite di 8060 byte. Il codice seguente mostra che puoi creare ben 1023 colonne:

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

Tuttavia, tutte le restrizioni che lo riguardano (leggi l'articolo collegato) potrebbero non essere adatte al tuo caso d'uso. In particolare, solo i NULLvalori (non 0) sono ottimizzati per occupare pochissimo spazio. Se provi a inserire troppi messaggi 0in una sola riga, otterrai un errore. Ecco cosa vedo quando provo a inserire 1023 0valori:

Messaggio 511, livello 16, stato 1, riga 1 Impossibile creare una riga della dimensione 17402 che è maggiore della dimensione massima consentita della riga di 8060.

Suppongo che se diventassi davvero disperato potresti creare le colonne come VARCHAR(27)invece. Le colonne a lunghezza variabile possono essere spostate dalla pagina in modo da poter superare il limite di 8060 byte nella definizione della tabella ma l'inserimento di determinate combinazioni di valori non riuscirà. SQL Server ti avvisa di ciò durante la creazione della tabella:

Avviso: la tabella "t1" è stata creata, ma la sua dimensione massima della riga supera il massimo consentito di 8060 byte. INSERT o UPDATE in questa tabella falliranno se la riga risultante supera il limite di dimensioni.

La compressione di pagine o righe può essere utile se segui l' VARCHAR(27)approccio. Ciò minimizzerà lo spazio utilizzato da entrambi 0e NULL. Con VARCHAR(27)sono in grado di inserire 1023 0valori bene.


2

Al di fuori degli aspetti tecnici e della soluzione proposta (usando le VARCHAR(27)colonne) discussi nella risposta di @ Joe , metto in dubbio la " necessità di creare [una] ampia tabella denormalizzata" come espressa dall'OP A meno che non ci siano dei requisiti tecnici dispari che tutte queste colonne deve essere in una singola tabella, suggerirei / raccomanderei di distribuirli su tutte le tabelle "fratelli" necessarie. Le tabelle di pari livello sono tabelle che:

  • avere una relazione 1 a 1 tra loro,
  • tutti hanno la stessa chiave primaria esatta,
  • solo uno ha la IDENTITYcolonna (e nessun FK per gli altri)
  • il resto ha una chiave esterna (sulla colonna PK) che punta al PK della tabella che ha il IDENTITY

Qui stai dividendo la sua riga logica su due o più tabelle fisiche. Ma questo è essenzialmente ciò che è la normalizzazione e quali database relazionali sono progettati per gestire.

In questo scenario si incorre in uno spazio aggiuntivo utilizzato duplicando il PK e una maggiore complessità delle query a causa della necessità di entrambe INNER JOINle tabelle insieme (spesso ma non sempre, a meno che tutte le SELECTquery non utilizzino tutte le colonne, ma ciò non accade di solito) o creare una Transazione esplicita a loro INSERTo UPDATEinsieme ( DELETEpuò essere gestita tramite ON DELETE CASCADEset sull'FK).

TUTTAVIA, si ottengono i vantaggi di disporre di un modello di dati adeguato con tipi di dati nativi adeguati e senza inganni che potrebbero avere conseguenze impreviste in seguito. Anche se l'utilizzo VARCHAR(27)consente di farlo funzionare a livello tecnico, pragmaticamente non penso che archiviare i decimali poiché le stringhe siano nel miglior interesse del progetto.

Quindi, se stai solo "necessitando" di una singola tabella perché non ti rendi conto che una singola entità logica non ha bisogno di essere rappresentata fisicamente in un singolo contenitore, allora non tentare di forzare tutto questo in una singola tabella quando funzionerà con grazia su più tavoli.

L'esempio seguente illustra il concetto di base:

IMPOSTARE

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

TEST

EXEC #TestInsert 'aa', 454567678989;

EXEC #TestInsert 'bb', 12312312312234;

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

Ritorna:

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.