Compression SQL Server 2014 et taille de ligne maximale

8

J'ai besoin de créer une large table dénormalisée avec beaucoup de colonnes décimales (26,8) (moins de 1024 colonnes, la plupart des colonnes seraient nulles ou nulles). Je connais environ 8060 octets par limite de ligne, j'ai donc essayé de créer un tableau avec une compression de page. Le code ci-dessous crée un tableau, insère une ligne et interroge la taille de la ligne. La taille des lignes est bien en dessous de la limite, mais si j'essaie d'ajouter une colonne décimale (26,8) de plus à la table, l'opération échoue avec l'erreur "La création ou la modification de la table 't1' a échoué car la taille de ligne minimale serait 8074, y compris 1256 octets de surcharge interne. ". Existe-t-il un moyen de créer une table unique avec autant de colonnes?

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
la source
1
FWIW, je peux obtenir 613 DECIMAL(26, 8) NULLchamps dans une table, sans compression de page ou compression décimale. En activant la compression vardécimale mais pas la page, la surcharge passe à plus de 1 K. Il y a une chance extérieure que vous puissiez stocker plus de champs par page sans vardécimal, selon vos valeurs.
Jon of All Trades

Réponses:

4

La limite que vous rencontrez n'a rien à voir avec les données stockées sur la page. Le calcul est effectué en fonction des types de données des colonnes. C'est pourquoi vous rencontrez l'erreur sans aucune donnée dans le tableau. La compression aggrave cette limite. Vous pouvez lire les détails techniques derrière les frais généraux ici .

Vous pouvez contourner ce problème en utilisant des colonnes SPARSE . Cela signifie qu'il sera possible que les insertions échouent en fonction de ce que vous insérez, mais vous pouvez contourner la limite de 8060 octets. Le code suivant montre que vous pouvez très bien créer 1023 colonnes:

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

Cependant, toutes les restrictions qui l'entourent (lire l'article lié) peuvent le rendre inapproprié pour votre cas d'utilisation. Plus précisément, seules les NULLvaleurs (non 0) sont optimisées pour occuper très peu d'espace. Si vous essayez d'insérer trop de 0s dans une seule ligne, vous obtiendrez une erreur. Voici ce que je vois lorsque j'essaie d'insérer des 0valeurs 1023 :

Msg 511, niveau 16, état 1, ligne 1 Impossible de créer une ligne de taille 17402 qui est supérieure à la taille de ligne maximale autorisée de 8060.

Je suppose que si vous deveniez vraiment désespéré, vous pourriez créer les colonnes à la VARCHAR(27)place. Les colonnes de longueur variable peuvent être déplacées hors de la page afin que vous puissiez dépasser la limite de 8060 octets dans la définition de la table, mais l'insertion de certaines combinaisons de valeurs échouera. SQL Server vous en avertit lors de la création de la table:

Avertissement: La table "t1" a été créée, mais sa taille de ligne maximale dépasse le maximum autorisé de 8060 octets. INSERT ou UPDATE dans cette table échouera si la ligne résultante dépasse la limite de taille.

La compression de page ou de ligne peut être utile si vous optez pour l' VARCHAR(27)approche. Cela minimisera l'espace utilisé par les deux 0et NULL. Avec VARCHAR(27)je suis capable d'insérer très bien 1023 0valeurs.

Joe Obbish
la source
2

En dehors des aspects techniques et des solutions de rechange proposées (en utilisant des VARCHAR(27)colonnes) discutés dans la réponse de @ Joe , je remets en question la " nécessité de créer [un] tableau dénormalisé large" tel qu'exprimé par le PO Sauf s'il existe une exigence technique étrange que toutes ces colonnes doit être dans une seule table, je suggère / recommande de les répartir sur autant de tables "frères" que nécessaire. Les tables fratries étant des tables qui:

  • avoir une relation 1 à 1 les uns avec les autres,
  • tous ont exactement la même clé primaire,
  • un seul a la IDENTITYcolonne (et pas de FK pour les autres)
  • les autres ont une clé étrangère (sur la colonne PK) pointant vers le PK de la table qui a le IDENTITY

Ici, vous divisez la ligne logique sur deux ou plusieurs tables physiques. Mais c'est essentiellement ce qu'est la normalisation de toute façon, et ce que les bases de données relationnelles sont conçues pour gérer.

Dans ce scénario, vous encourez un espace supplémentaire utilisé en dupliquant le PK, et une complexité de requête supplémentaire en raison de la nécessité soit INNER JOINdes tables ensemble (fréquemment mais pas toujours, sauf si toutes les SELECTrequêtes utilisent toutes les colonnes, mais cela ne se produit généralement pas) ou créez une transaction explicite vers INSERTou UPDATEentre eux ( DELETEpeut être gérée via ON DELETE CASCADEset sur le FK).

CEPENDANT, vous bénéficiez des avantages d'un modèle de données approprié avec des types de données natifs appropriés, et pas de supercherie qui pourrait avoir des conséquences imprévues plus tard. Même si l'utilisation VARCHAR(27)permet à cela de fonctionner au niveau technique, je ne pense pas que le stockage de décimales sous forme de chaînes soit dans votre / le meilleur intérêt du projet.

Donc, si vous "n'avez besoin" que d'une seule table car vous ne réalisez pas qu'une seule entité logique n'a pas besoin d'être représentée physiquement dans un seul conteneur, n'essayez pas de forcer tout cela dans une seule table quand cela fonctionnera. gracieusement sur plusieurs tables.

L'exemple ci-dessous illustre le concept de base:

INSTALLER

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

TESTER

EXEC #TestInsert 'aa', 454567678989;

EXEC #TestInsert 'bb', 12312312312234;

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

Retour:

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
la source