SQL Server 2014-Komprimierung und maximale Zeilengröße

8

Ich muss eine breite denormalisierte Tabelle mit vielen Dezimalspalten (26,8) erstellen (weniger als 1024 Spalten, die meisten Spalten wären null oder null). Ich kenne ungefähr 8060 Bytes pro Zeilenbeschränkung, also habe ich versucht, eine Tabelle mit Seitenkomprimierung zu erstellen. Der folgende Code erstellt eine Tabelle, fügt eine Zeile ein und fragt die Zeilengröße ab. Die Zeilengröße liegt weit unter dem Grenzwert, aber wenn ich versuche, der Tabelle eine weitere Dezimalspalte (26,8) hinzuzufügen, schlägt der Vorgang mit dem Fehler fehl. "Das Erstellen oder Ändern der Tabelle 't1' ist fehlgeschlagen, da die minimale Zeilengröße 8074 einschließlich 1256 betragen würde Bytes interner Overhead. ". Gibt es eine Möglichkeit, eine einzelne Tabelle mit so vielen Spalten zu erstellen?

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
quelle
1
FWIW, ich kann 613 DECIMAL(26, 8) NULLFelder in eine Tabelle bekommen, ohne Seitenkomprimierung oder Dezimalkomprimierung. Wenn Sie die vardezimale, aber nicht die Seitenkomprimierung aktivieren, springt der Overhead auf über 1 K. Es besteht die Möglichkeit, dass Sie abhängig von Ihren Werten mehr Felder pro Seite ohne vardezimal speichern können.
Jon of All Trades

Antworten:

4

Das Limit, auf das Sie stoßen, hat nichts mit den auf der Seite gespeicherten Daten zu tun. Die Berechnung erfolgt anhand der Datentypen der Spalten. Aus diesem Grund tritt der Fehler ohne Daten in der Tabelle auf. Die Komprimierung verschlimmert diese Grenze. Sie können hinter dem Overhead über die technischen Details lesen Sie hier .

Sie können dieses Problem mithilfe von SPARSE- Spalten umgehen . Das bedeutet, dass Einfügungen je nach dem, was Sie einfügen, möglicherweise fehlschlagen. Sie können jedoch das 8060-Byte-Limit umgehen. Der folgende Code zeigt, dass Sie problemlos 1023 Spalten erstellen können:

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

Aufgrund aller Einschränkungen (lesen Sie den verlinkten Artikel) ist dies möglicherweise nicht für Ihren Anwendungsfall geeignet. Insbesondere werden nur NULLWerte (nicht 0) so optimiert, dass sie sehr wenig Platz beanspruchen. Wenn Sie versuchen, zu viele 0s in eine einzelne Zeile einzufügen , wird eine Fehlermeldung angezeigt. Folgendes sehe ich, wenn ich versuche, 1023 0Werte einzufügen :

Meldung 511, Ebene 16, Status 1, Zeile 1 Es kann keine Zeile der Größe 17402 erstellt werden, die größer als die zulässige maximale Zeilengröße von 8060 ist.

Ich nehme an, wenn Sie wirklich verzweifelt sind, können Sie die Spalten VARCHAR(27)stattdessen so erstellen . Spalten mit variabler Länge können von der Seite verschoben werden, sodass Sie die 8060-Byte-Grenze in der Tabellendefinition überschreiten können. Das Einfügen bestimmter Wertekombinationen schlägt jedoch fehl. SQL Server warnt Sie beim Erstellen der Tabelle davor:

Warnung: Die Tabelle "t1" wurde erstellt, ihre maximale Zeilengröße überschreitet jedoch das zulässige Maximum von 8060 Byte. INSERT oder UPDATE für diese Tabelle schlagen fehl, wenn die resultierende Zeile die Größenbeschränkung überschreitet.

Die Seiten- oder Zeilenkomprimierung kann hilfreich sein, wenn Sie sich für den VARCHAR(27)Ansatz entscheiden. Dadurch wird der von 0und NULL. Mit kann VARCHAR(27)ich 1023 0Werte ganz gut einfügen .

Joe Obbish
quelle
2

Abgesehen von den technischen Aspekten und der vorgeschlagenen Problemumgehung (unter Verwendung von VARCHAR(27)Spalten), die in @ Joes Antwort erörtert wurden , stelle ich die " Notwendigkeit , eine [breite] denormalisierte Tabelle zu erstellen " in Frage, wie sie vom OP ausgedrückt wird, es sei denn, es gibt eine merkwürdige technische Anforderung, dass alle diese Spalten muss in einer einzigen Tabelle sein, ich würde vorschlagen / empfehlen, sie auf so viele "Geschwister" -Tische wie nötig zu verteilen. Geschwistertabellen sind Tabellen, die:

  • eine 1: 1-Beziehung zueinander haben,
  • alle haben genau den gleichen Primärschlüssel,
  • nur einer hat die IDENTITYSpalte (und keine FK zu den anderen)
  • Der Rest hat einen Fremdschlüssel (in der PK-Spalte), der auf die PK der Tabelle zeigt, in der sich die befindet IDENTITY

Hier teilen Sie die logische Zeile auf zwei oder mehr physische Tabellen auf. Aber das ist im Wesentlichen das, was Normalisierung sowieso ist und wofür relationale Datenbanken ausgelegt sind.

In diesem Szenario entsteht durch das Duplizieren der PK zusätzlicher Speicherplatz und zusätzliche Abfragekomplexität, da entweder INNER JOINdie Tabellen zusammen verwendet werden müssen (häufig, aber nicht immer, es sei denn, alle SELECTAbfragen verwenden alle Spalten, dies geschieht jedoch normalerweise nicht). oder erstellen Sie eine explizite Transaktion zu INSERToder UPDATEzusammen ( DELETEkann über ON DELETE CASCADEset auf dem FK abgewickelt werden ).

Sie erhalten jedoch die Vorteile eines ordnungsgemäßen Datenmodells mit geeigneten nativen Datentypen und ohne Tricks, die später unvorhergesehene Folgen haben könnten. Selbst wenn die Verwendung VARCHAR(27)dies auf technischer Ebene ermöglicht, denke ich pragmatisch nicht, dass das Speichern von Dezimalstellen als Zeichenfolgen im besten Interesse Ihres / des Projekts liegt.

Wenn Sie also nur eine einzelne Tabelle "benötigen", weil Sie nicht wissen, dass eine einzelne logische Entität nicht physisch in einem einzelnen Container dargestellt werden muss, versuchen Sie nicht, all dies in eine einzelne Tabelle zu zwingen, wenn dies funktioniert elegant über mehrere Tabellen.

Das folgende Beispiel veranschaulicht das Grundkonzept:

INSTALLIEREN

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

PRÜFUNG

EXEC #TestInsert 'aa', 454567678989;

EXEC #TestInsert 'bb', 12312312312234;

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

Kehrt zurück:

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
quelle