web-dev-qa-db-fra.com

SQL Server 2014 Compression et taille maximale de la ligne

J'ai besoin de créer une large table dénormalisée avec beaucoup de colonnes décimales (26,8) (moins de 1024 limites de colonne, la plupart des colonnes seraient nulles ou zéro). Je connais environ 8060 octets par limitation de ligne, j'ai donc essayé de créer une table avec compression de page. Le code ci-dessous crée une table, insère une rangée de ligne et des requêtes. La taille de la ligne est bien inférieure à la limite, mais si j'essaie d'ajouter une autre colonnes décimales (26,8) à la table, l'opération échoue avec une erreur "Création ou modification de la table" T1 '"Échec de la taille minimale de la ligne 8074, dont 1256 octets de surcharge interne. " Y a-t-il un moyen de créer une table unique avec de nombreuses 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
8
Alex

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 la table. La compression rend cette limite pire. Vous pouvez lire sur les détails techniques des frais généraux ici .

Vous pouvez contourner ce problème à l'aide [~ # ~] rares [~ # ~ ~] colonnes. Cela signifie que cela sera possible pour les insertions d'échec en fonction de ce que vous insérez, mais vous pouvez contourner la limite d'octets 8060. Le code suivant montre que vous pouvez 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 rendre cela ne convient pas à votre cas d'utilisation. Spécifiquement, seules les valeurs NULL (pas 0) Sont optimisées pour prendre très peu d'espace. Si vous essayez d'insérer trop de 0 S en une seule ligne, vous obtiendrez une erreur. Voici ce que je vois quand j'essaie d'insérer 1023 0 Valeurs:

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

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

AVERTISSEMENT: la table "T1" a été créée, mais sa taille maximale de la ligne dépasse le maximum maximum autorisé de 8060 octets. L'insertion ou la mise à jour de ce tableau échouera si la ligne résultante dépasse la limite de taille.

La compression de page ou de ligne peut être utile si vous allez avec l'approche VARCHAR(27). Cela minimisera l'espace utilisé par 0 Et NULL. Avec VARCHAR(27) Je suis capable d'insérer 1023 0 Les valeurs vont bien.

4
Joe Obbish

En dehors des aspects techniques et une œuvre proposée (utilisation VARCHAR(27) colonnes) discutées dans @ Joe's Reshant , je questionnerai le " besoin Pour créer [a] une large table dénormalisée "telle que exprimée par l'OP, sauf indication contraire de certaines colonnes DOIT Soyez dans une seule table, je vous suggérerais/recommanderais de les diffuser en plus de Tables "Sibling" si nécessaire. Tables de frère de soeur étant des tables qui:

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

Ici, vous fractionnez la rangée logique sur deux tables physiques ou plus. Mais c'est essentiellement quelle normalisation est de toute façon et quelles sont les bases de données relationnelles conçues pour gérer.

Dans ce scénario, vous engagez un espace supplémentaire utilisé par la duplication de la PK et une complexité de requête supplémentaire en raison de la nécessité de INNER JOIN Les tables ensemble (fréquemment mais pas toujours, à moins que tous les SELECT requêtes Utilisez toutes les colonnes, mais cela ne se produit généralement pas) ou crée une transaction explicite sur INSERT ou UPDATE-les ensemble (DELETE peut être géré via ON DELETE CASCADE mis sur le FK).

Cependant, vous obtenez les avantages d'avoir un modèle de données approprié avec des types de données autochtones appropriés et pas de tromperie qui pourrait avoir des conséquences imprévues plus tard. Même si vous utilisez VARCHAR(27) permet de travailler sur un niveau technique, je ne pense pas de manière pragmatique, je ne pense pas à stocker des décimales en tant que chaînes dans votre/le meilleur intérêt.

Donc, si vous n'êtes que "nécessitant" une table unique en raison de la ne pas réaliser qu'une seule entité logique n'a pas besoin d'être représentée physiquement dans un seul conteneur, alors n'essayez pas de forcer tout cela dans une seule table quand il fonctionnera gracieusement sur plusieurs tables.

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

[~ # ~] Configuration [~ # ~ ~]

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];

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