web-dev-qa-db-fra.com

Comment libérer l'espace inutilisé pour une table

Cette question est posée comme des dizaines de fois, et à ma grande surprise, une exigence aussi simple devient si difficile. Pourtant, je ne peux pas résoudre ce problème.

J'utilise l'édition SQL Server 2014 Express avec une limite de 10 Go de taille de base de données (pas une taille de groupe de fichiers, une taille de base de données).

J'ai exploré les actualités et inséré du HTML dans un tableau. Le schéma de la table est:

Id bigint identity(1, 1) primary key,
Url varchar(250) not null,
OriginalHtml nvarchar(max),
...

La base de données est épuisée et j'ai reçu insufficient disk space

Bien sûr, la réduction de la base de données et du groupe de fichiers n'a pas aidé. DBCC SHRINKDATABASE n'a pas aidé. J'ai donc écrit une application simple pour lire chaque enregistrement, éliminer certaines parties indésirables de la section de tête OriginalHtml comme côté et pied de page pour garder le corps principal uniquement et je vois maintenant cette image lors de l'obtention du rapport d'utilisation du disque par tables supérieures:

enter image description here

Si je comprends bien cette image, l'espace inutilisé représente maintenant 50% de la taille totale. Autrement dit, j'ai maintenant 5 Go d'espace inutilisé. Mais je ne peux pas le récupérer. La reconstruction des index n'a pas aidé. L'option truncateonly n'aidera pas car, si j'ai bien compris, aucun enregistrement n'est supprimé, seule la taille de chaque enregistrement est réduite.

Je suis coincé à ce stade. Aidez-moi, que dois-je faire?

L'index cluster est sur la colonne Id.

Ceci est le résultat de EXECUTE sys.sp_spaceused @objname = N'dbo.Articles', @updateusage = 'true';

name        rows     reserved     data        index_size   unused
----------- -------- ------------ ----------- ------------ -----------
Articles    112258   8079784 KB   5199840 KB  13360 KB     2866584 KB 
8
Saeed Neamati

Toutes choses égales par ailleurs, il devrait suffire de compacter la colonne du grand objet (LOB) OriginalHTML. Vous ne spécifiez pas le nom d'index cluster dans la question, donc:

ALTER INDEX ALL
ON dbo.Articles
REORGANIZE 
WITH (LOB_COMPACTION = ON);

Voir ALTER INDEX (Transact-SQL)

Si vous avez le nom d'index cluster (et pas seulement les colonnes cluster), remplacez le ALL ci-dessus par ce nom.

L'option LOB_COMPACTION Est par défaut ON, mais il n'y a aucun mal à être explicite. Vous devrez peut-être exécuter le REORGANIZE à plusieurs reprises pour terminer la récupération de tout l'espace inutilisé.

Malheureusement, la façon dont les données LOB sont organisées et la façon dont le compactage LOB est implémenté signifie que cette méthode peut ne pas toujours être en mesure de récupérer tout l'espace inutilisé, quel que soit le nombre de fois que vous l'exécutez. Cela peut aussi être très lent.

Vous pouvez également essayer la méthode dans le Q & A associé Libération de l'espace SQL Server Table inutilisée

Si, pour une raison quelconque, ce qui précède ne fonctionne pas pour vous, exportez les données dans un fichier, tronquez la table, puis la recharger . Il existe plusieurs méthodes pour y parvenir, par exemple tilitaire bcp .

Exemple

Ce qui suit crée un tableau avec 10 000 lignes larges:

CREATE TABLE dbo.Test 
(
    c1 bigint IDENTITY NOT NULL, 
    c2 nvarchar(max) NOT NULL,

    CONSTRAINT PK_dbo_Test
        PRIMARY KEY CLUSTERED (c1)
);

-- Load 10,000 wide rows
INSERT dbo.Test WITH (TABLOCKX)
    (c2)
SELECT TOP (10000)
    REPLICATE(CONVERT(nvarchar(max), 'X'), 50000)
FROM master.sys.columns AS C1
CROSS JOIN master.sys.columns AS C2;

Nous pouvons voir l'utilisation de l'espace en utilisant le sys.dm_db_index_physical_stats DMV:

SELECT
    DDIPS.index_id,
    DDIPS.partition_number,
    DDIPS.index_type_desc,
    DDIPS.index_depth,
    DDIPS.index_level,
    DDIPS.page_count,
    DDIPS.avg_page_space_used_in_percent
FROM sys.dm_db_index_physical_stats
(
    DB_ID(),
    OBJECT_ID(N'dbo.Test', N'U'),
    1,
    NULL,
    'DETAILED'
) AS DDIPS
WHERE 
    DDIPS.alloc_unit_type_desc = N'LOB_DATA';

DMV output

Nous mettons maintenant à jour le contenu LOB vers une taille plus petite (mais qui nécessite toujours un stockage hors ligne):

-- Change LOB data to a smaller value (that will not move in-row)
UPDATE dbo.Test WITH (TABLOCKX)
SET c2 = REPLICATE(CONVERT(nvarchar(max), 'Y'), 5000);

DMV output

Notez qu'un certain espace a été récupéré, mais les pages restantes sont beaucoup moins pleines qu'elles ne l'étaient.

Nous pouvons compacter l'espace LOB en utilisant:

ALTER INDEX PK_dbo_Test ON dbo.Test 
REORGANIZE 
WITH (LOB_COMPACTION = ON);

DMV output

Il en résulte un compactage et des économies d'espace, mais ce n'est pas parfait. Une nouvelle exécution du compactage peut ou non améliorer la situation. Dans mon test, il ne l'a pas fait, peu importe combien de fois je l'ai relancé.

Exporter, tronquer, recharger

Une façon de le faire entièrement à partir de Management Studio consiste à utiliser xp_cmdshell Pour exporter les données de la table vers un fichier. Si xp_cmdshell N'est pas actuellement activé, les opérations suivantes le feront:

-- Enable xp_cmdshell if necessary
EXECUTE sys.sp_configure
    @configname = 'show advanced options',
    @configvalue = 1;

RECONFIGURE;

EXECUTE sys.sp_configure
    @configname = 'xp_cmdshell',
    @configvalue = 1;

RECONFIGURE;

Maintenant, nous pouvons effectuer l'exportation:

-- Export table
EXECUTE sys.xp_cmdshell
    'bcp Sandpit.dbo.Test out c:\temp\Test.bcp -n -S .\SQL2017 -T';

Notez que vous devrez modifier le chemin d'accès et le nom du serveur -S, Et éventuellement fournir des informations de connexion.

Comment tronquer la table et la recharger en utilisant BULK INSERT:

-- Truncate
TRUNCATE TABLE dbo.Test;

-- Switch to BULK_LOGGED recovery model if currently set to FULL
-- Bulk load
BULK INSERT dbo.Test
FROM 'c:\temp\Test.bcp' 
WITH 
(
    DATAFILETYPE = 'widenative', 
    ORDER (c1), 
    TABLOCK,
    KEEPIDENTITY
);

La dernière étape consiste à réinitialiser la graine d'identité:

-- Check and reseed identity
DBCC CHECKIDENT('dbo.Test', RESEED);

Cette séquence d'opérations est généralement plus rapide que le compactage LOB et devrait toujours produire des résultats optimaux:

DMV output

Ce qui précède n'est pas aussi efficace qu'il pourrait l'être en raison d'un bogue de longue date: BULK INSERT avec la colonne IDENTITY crée un plan de requête avec SORT . La solution de contournement indiquée ici est efficace, mais je ne m'embêterais avec elle que si la table est très grande.

N'oubliez pas de supprimer le fichier temporaire utilisé pour contenir les données exportées.

Vous êtes bien sûr libre d'utiliser la méthode d'exportation/importation en masse qui vous convient le mieux. Il n'est pas nécessaire d'utiliser xp_cmdshell Ou bcp.

Notes complémentaires:

  • FILLFACTOR ne s'applique qu'aux pages d'index. Il n'affecte pas le stockage LOB hors ligne (qui n'est pas stocké sur les pages d'index).
  • La ligne et la page compression ne sont pas disponibles pour le stockage hors ligne.
  • Vous pouvez également compresser et décompresser les données de manière explicite à l'aide des fonctions COMPRESS et DECOMPRESS disponibles à partir de SQL Server 2016.

    Une option pour ceux qui utilisent SQL Server 2014 (ce qui est le cas ici) ou plus ancien (jusqu'à SQL Server 2005) pour obtenir la même fonctionnalité de compression fournie par les fonctions intégrées COMPRESS et DECOMPRESS est d'utiliser SQLCLR. Les fonctions pré-construites qui font exactement cela sont disponibles dans la version gratuite de SQL # écrite par Solomon Rutzky . Les fonctions Util_GZip et Util_GUnzip doivent être équivalentes à COMPRESS et DECOMPRESS, respectivement. Et, toute personne utilisant SQL Server 2012 ou une version plus récente doit s'assurer que le serveur exécutant SQL Server est mis à jour avec .NET Framework version 4.5 ou plus récente afin que l'algorithme de compression bien amélioré soit utilisé.

10
Paul White 9

Si vous pouvez effectuer une mise à niveau vers SQL Server Express 2016 SP1 ou version ultérieure, vous pouvez économiser énormément d'espace en utilisant COMPRESSION DE DONNÉES .

Vous pouvez avoir d'autres choses en jeu qui gonflent votre base de données.Cependant, comme le suggère le commentaire de Dan Guzman, vous devez vérifier le facteur de remplissage sur tous vos index.

Tout autre chose que 0 (zéro) ou 100 signifie que, lorsque l'index a été créé (ou reconstruit), SQL Server n'a rempli chaque page que jusqu'au pourcentage du facteur de remplissage. Ainsi, par exemple, si vous aviez un facteur de remplissage de 50, seulement 50% de la page serait remplie lors de la création/reconstruction de l'index, ce qui doublerait essentiellement la quantité d'espace nécessaire pour contenir réellement les données.

Extraire une requête de la publication Rechercher les facteurs de remplissage pour les index dans une base de données SQL Server

Si vous souhaitez rechercher tous les index de toutes les tables utilisateur dans une base de données SQL Server qui ont un facteur de remplissage différent de 0 ou 100:

SELECT DB_NAME() AS Database_Name
, sc.name AS Schema_Name
, o.name AS Table_Name
, o.type_desc
, i.name AS Index_Name
, i.type_desc AS Index_Type
, i.fill_factor
FROM sys.indexes i
INNER JOIN sys.objects o ON i.object_id = o.object_id
INNER JOIN sys.schemas sc ON o.schema_id = sc.schema_id
WHERE i.name IS NOT NULL
AND o.type = 'U'
AND i.fill_factor not in (0, 100)
ORDER BY i.fill_factor DESC, o.name

Des informations précieuses supplémentaires concernant le facteur de remplissage peuvent être trouvées à l'adresse

5 choses sur Fillfactor

Résultat Blitz: Facteur de remplissage (%)

1
Scott Hodgin