SQL Server utilise-t-il des pointeurs au lieu de stocker des lignes en double?

8

J'utilise la sp_spaceusedprocédure stockée intégrée avant et après avoir effectué une opération dans notre logiciel pour voir quelles tables ont des insertions de lignes et comment la taille de chaque table change.

Ce que je vois, c'est que de toutes les tables sur lesquelles des lignes sont écrites, seule une poignée montre que la table a augmenté de côté. Les autres qui affichent des lignes ont été ajoutées ne montrent aucun changement de taille de cette procédure stockée.

Le seul cas où cela n'est pas vrai concerne la première transaction après avoir effectué un tronçon sur toutes les tables. Donc, pour moi, il semble qu'au lieu de stocker des données en double, SQL Server montre que les lignes sont insérées mais doivent simplement stocker des pointeurs vers les lignes identiques précédentes.

Quelqu'un peut-il confirmer cela s'il vous plaît?

Dan Revell
la source
Signalé pour dba.se
gbn

Réponses:

13

Non, SQL Server ne détecte pas les lignes en double

SQL Server remplit des pages vides ou partiellement vides dans les pages allouées.

Donc, si j'ai une ligne très étroite (disons 2 colonnes), je peux ajouter quelques centaines de lignes supplémentaires sur la même page sans augmenter l'espace utilisé.

Démo rapide et sale (sans lignes en double, mais vous pouvez jouer avec cela si vous le souhaitez)

IF OBJECT_ID('dbo.Demo') IS NOT NULL
    DROP TABLE dbo.Demo;
GO
CREATE TABLE dbo.Demo (DemoID int NOT NULL IDENTITY(1,1), Demo char(1) NOT NULL)
GO
SELECT 'zero rows, zero space', SUM(ps.reserved_page_count)/128.0 AS ReservedMB, SUM(ps.used_page_count)/128.0 AS UsedMB
FROM sys.dm_db_partition_stats ps
WHERE ps.object_id = OBJECT_ID('dbo.Demo')
GO

INSERT dbo.Demo VALUES ('a');
GO
SELECT 'one row. Peanuts', SUM(ps.reserved_page_count)/128.0 AS ReservedMB, SUM(ps.used_page_count)/128.0 AS UsedMB
FROM sys.dm_db_partition_stats ps
WHERE ps.object_id = OBJECT_ID('dbo.Demo')
GO

INSERT dbo.Demo VALUES ('b');
GO 100
SELECT '101 rows. All on one page', SUM(ps.reserved_page_count)/128.0 AS ReservedMB, SUM(ps.used_page_count)/128.0 AS UsedMB
FROM sys.dm_db_partition_stats ps
WHERE ps.object_id = OBJECT_ID('dbo.Demo')
GO

INSERT dbo.Demo VALUES ('b');
GO 1899
SELECT '2000 rows. More than one page', SUM(ps.reserved_page_count)/128.0 AS ReservedMB, SUM(ps.used_page_count)/128.0 AS UsedMB
FROM sys.dm_db_partition_stats ps
WHERE ps.object_id = OBJECT_ID('dbo.Demo')
GO

TRUNCATE TABLE dbo.Demo
GO
SELECT 'zero rows, zero space. TRUNCATE deallocates pages', SUM(ps.reserved_page_count)/128.0 AS ReservedMB, SUM(ps.used_page_count)/128.0 AS UsedMB
FROM sys.dm_db_partition_stats ps
WHERE ps.object_id = OBJECT_ID('dbo.Demo')
GO

INSERT dbo.Demo VALUES ('c');
GO 500
SELECT '500 rows. Some space used', SUM(ps.reserved_page_count)/128.0 AS ReservedMB, SUM(ps.used_page_count)/128.0 AS UsedMB
FROM sys.dm_db_partition_stats ps
WHERE ps.object_id = OBJECT_ID('dbo.Demo')
GO

DELETE dbo.Demo
GO
SELECT 'zero rows after delete. Space still allocated', SUM(ps.reserved_page_count)/128.0 AS ReservedMB, SUM(ps.used_page_count)/128.0 AS UsedMB
FROM sys.dm_db_partition_stats ps
WHERE ps.object_id = OBJECT_ID('dbo.Demo')
GO

IF OBJECT_ID('dbo.Demo') IS NOT NULL
    DROP TABLE dbo.Demo;
GO
gbn
la source
Merci c'est une merveilleuse réponse. J'aimerais pouvoir voter plus. Peut-être pourriez-vous également suggérer, si vous êtes si gentil, comment je pourrais déterminer les données réelles utilisées dans la page. Tout ce que je peux voir, c'est: nom lignes réservées données_index_taille inutilisées Charges 6 16 Ko 8 Ko 8 Ko 0 Ko De cela, je ne peux pas voir quelle partie de la page j'utilise pour mes 6 lignes. Cela me dit que j'ai 0 Ko inutilisé dans cette page, même si je sais que ce n'est pas le cas.
J'ai essayé DBCC SHOWCONTIG mais cela ne montre pas les grandes colonnes qui sont stockées dans le LOB si je comprends bien.
J'ai pensé commenter plutôt que de créer une nouvelle question ... Comment fonctionne le stockage de tables plus larges? Que se passe-t-il si, par exemple, j'ai une table qui est vraiment large mais la plupart du temps environ 60% des colonnes sont nulles? Je suppose que cette ligne prendra la même quantité d'espace à stocker dans la page car ces colonnes POURRAIENT avoir des données? En termes de stockage uniquement (tout peut être pris trop littéral bien sûr) il vaut mieux avoir plus de tables étroites? Si vous finissez par avoir à insérer fréquemment les colonnes "vides" de toute façon, il serait probablement judicieux de conserver cela avec la table principale?
bdwakefield
7

SQL Server utilise-t-il des pointeurs au lieu de stocker des lignes en double?

Cela dépend de la version de SQL Server et des options de compression des données:

  • À partir de SQL Server 2008, il existe une option de compression au niveau de la ligne ou de la page.
  • La compression au niveau de la page utilise de nombreux algorithmes / techniques de compression. Concernant votre question (pointeurs pour les données dupliquées), la compression de page utilise (également) la compression de préfixe et la compression de dictionnaire :

Compression de préfixe [...] Les valeurs de préfixe répétées dans la colonne sont remplacées par une référence au préfixe [...] correspondant

Compression du dictionnaire Une fois la compression du préfixe terminée, la compression du dictionnaire est appliquée. La compression du dictionnaire recherche les valeurs répétées n'importe où sur la page et les stocke dans la zone CI. Contrairement à la compression de préfixe, la compression de dictionnaire n'est pas limitée à une colonne. La compression du dictionnaire peut remplacer les valeurs répétées qui se produisent n'importe où sur une page. L'illustration suivante montre la même page après la compression du dictionnaire.

Ainsi, pour la compression de préfixe et de dictionnaire (compression de page), SQL Server utilise des pointeurs pour stocker (partiellement ou entièrement) les valeurs dupliquées (pas les lignes dupliquées) dans la même colonne ou dans diff. Colonnes.

CREATE DATABASE TestComp;
GO

USE TestComp;
GO

CREATE TABLE Person1 (
    PersonID INT IDENTITY PRIMARY KEY,
    FirstName NVARCHAR(100) NOT NULL,
    LastName NVARCHAR(100) NOT NULL
);
GO

DECLARE 
    @f NVARCHAR(100) = REPLICATE('A',100), 
    @l NVARCHAR(100) = REPLICATE('B',100);

INSERT Person1 (FirstName, LastName)
VALUES (@f, @l);
GO 1000

CREATE TABLE Person2 (
    PersonID INT IDENTITY PRIMARY KEY,
    FirstName NVARCHAR(100) NOT NULL,
    LastName NVARCHAR(100) NOT NULL
);
GO

ALTER TABLE Person2
REBUILD
WITH (DATA_COMPRESSION=PAGE);
GO

DECLARE 
    @f NVARCHAR(100) = REPLICATE('A',100), 
    @l NVARCHAR(100) = REPLICATE('B',100);

INSERT Person2 (FirstName, LastName)
VALUES (@f, @l);
GO 1000

SELECT  f.page_count AS PageCount_Person1_Uncompressed
FROM    sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID('Person1'), 1, DEFAULT, DEFAULT) f
SELECT  f.page_count AS PageCount_Person2_Compressed
FROM    sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID('Person2'), 1, DEFAULT, DEFAULT) f
GO

Résultats:

PageCount_Person1_Uncompressed
------------------------------
53

PageCount_Person2_Compressed
----------------------------
2
Bogdan Sahlean
la source