Workaround pentru optimizarea spatiului pe servere SQL

Configurare noua (How To)

Situatie

In practica apar de multe ori alerte de spatiu care pot duce la efecte catastrofale pentru productie, de aceea trebuie ca prim pas sa intelegem cum se reduc acestea.

Solutie

Pasi de urmat

Se utilizeaza SSMS instrumetul de baza sau Azure Data Studio (cross-platform).

SSMS = SQL Server Management Studio

Prin SSMS poți să:

  • navighezi prin baze de date, tabele, coloane, indexuri ca într-un File Explorer
  • scrii și execuți interogări T-SQL într-un editor cu autocomplete și evidențiere sintaxă
  • gestionezi utilizatori, permisiuni și roluri
  • faci backup și restore cu câteva clicuri
  • monitorizezi activitatea serverului (Activity Monitor)
  • configurezi SQL Server Agent pentru joburi programate
  • faci exact operațiile din materialul de mai sus — shrink, compresie, autogrowth — fie grafic (clic dreapta → Tasks), fie prin scripturi.

Pasul 1 – SPATIUL PE FIECARE BAZA DE DATE

SELECT
  name AS [Baza de date],
  ROUND(SUM(size) * 8.0 / 1024, 1) AS [Dimensiune totala MB],
  ROUND(SUM(FILEPROPERTY(name, 'SpaceUsed')) * 8.0 / 1024, 1) AS [Spatiu utilizat MB],
  ROUND((SUM(size) - SUM(FILEPROPERTY(name, 'SpaceUsed'))) * 8.0 / 1024, 1) AS [Spatiu liber MB]
FROM sys.master_files
GROUP BY name
ORDER BY [Dimensiune totala MB] DESC;

Pasul 2 – Spatiu per fisier (MDF/LDF)

USE [NumeleBaseDeDate];
GO
SELECT
  name AS [Fisier],
  type_desc AS [Tip],
  ROUND(size * 8.0 / 1024, 1) AS [Dimensiune MB],
  ROUND(FILEPROPERTY(name, 'SpaceUsed') * 8.0 / 1024, 1) AS [Utilizat MB],
  ROUND((size - FILEPROPERTY(name, 'SpaceUsed')) * 8.0 / 1024, 1) AS [Liber MB]
FROM sys.database_files;

Pasul 3 – Cele mai mari tabele din baza de date

SELECT TOP 20
  t.name AS [Tabel],
  ROUND(SUM(a.total_pages) * 8.0 / 1024, 1) AS [Total MB],
  ROUND(SUM(a.used_pages) * 8.0 / 1024, 1) AS [Utilizat MB],
  ROUND(SUM(a.data_pages) * 8.0 / 1024, 1) AS [Date MB]
FROM sys.tables t
  INNER JOIN sys.indexes i ON t.object_id = i.object_id
  INNER JOIN sys.partitions p ON i.object_id = p.object_id AND i.index_id = p.index_id
  INNER JOIN sys.allocation_units a ON p.partition_id = a.container_id
WHERE t.is_ms_shipped = 0
GROUP BY t.name
ORDER BY [Total MB] DESC;

Dacă spațiul liber din MDF depășește 30%, ai candidat pentru shrink sau arhivare. Dacă LDF-ul crește continuu, verifică modelul de recuperare și planul de backup.

SHRINK este un workaround de urgență, nu o operație de mentenanță regulată. Cauzează fragmentare severă a index-urilor și degradare de performanță. Folosește-l doar când ai nevoie urgentă de spațiu pe disc.

Dezactivează AUTO_SHRINK (obligatoriu)

-- Verificare stare curenta
SELECT name, is_auto_shrink_on
FROM sys.databases
WHERE is_auto_shrink_on = 1;

-- Dezactivare (rulat per baza de date)
ALTER DATABASE [NumeleBaseDeDate]
SET AUTO_SHRINK OFF;
GO

Shrink fișier date (MDF) — modul recomandat

USE [NumeleBaseDeDate];
GO

-- Shrink cu target de 10% spatiu liber ramas
DBCC SHRINKFILE (N'NumeFisierDate', 10);

-- Varianta: shrink la dimensiune specifica (ex. 500 MB)
DBCC SHRINKFILE (N'NumeFisierDate', 500);
GO

Shrink baza de date completă (SSMS — clic dreapta)
 Echivalentul T-SQL al optiunii din SSMS
-- Tasks → Shrink → Database
DBCC SHRINKDATABASE (N'NumeleBaseDeDate', 10);
GO

Obligatoriu după shrink: reconstruiește index-urile pentru a elimina fragmentarea creată.

Rebuild indexuri după shrink

USE [NumeleBaseDeDate];
GO

-- Rebuild toate indexurile din toate tabelele
EXEC sp_MSforeachtable
  @command1 = 'ALTER INDEX ALL ON ? REBUILD WITH (ONLINE = ON)';
GO

-- Alternativa fara sp_MSforeachtable
DECLARE @sql NVARCHAR(MAX) = N'';
SELECT @sql += 'ALTER INDEX ALL ON [' + s.name + '].[' + t.name + '] REBUILD;' + CHAR(13)
FROM sys.tables t JOIN sys.schemas s ON t.schema_id = s.schema_id;
EXEC sp_executesql @sql;
GO

Workflow complet shrink în SSMS (interfață grafică)

1.Click dreapta pe baza de date → Tasks → Shrink → Files

2.Selectează File type: Data (sau Log pentru LDF)

3.Alege “Release unused space” sau setează dimensiunea dorită

4.Click OK și monitorizează progresul în Activity Monitor

5.După finalizare, rulează rebuild indexuri (scriptul de mai sus).

Fișierul LDF (transaction log) crește adesea necontrolat și este una din principalele cauze de epuizare a spațiului pe serverele cu model de recuperare FULL.

Diagnosticare LDF — verifică starea curentă

– Vizualizare utilizare log per baza de date

DBCC SQLPERF(LOGSPACE);

-- Detalii VLF si motiv blocare truncare
SELECT
  log_reuse_wait_desc AS [Motiv blocare],
  name AS [Baza de date]
FROM sys.databases
WHERE log_reuse_wait_desc <> 'NOTHING';
GO

Motivele comune pentru care LDF-ul nu se poate micsora:

  • LOG_BACKUP Nu s-a efectuat niciun backup de log. Soluție: fă un backup log, apoi shrink.
  • ACTIVE_TRANSACTION Există tranzacții deschise / rulând de mult timp. Soluție: identifică și termină tranzacția.
  • REPLICATION Replicare sau CDC activă blochează truncarea. Verifică că agenții funcționează.
  • DATABASE_MIRRORING Mirroring activ — partenerul trebuie să fie sincronizat.

Shrink LDF — model SIMPLE

-- In modelul SIMPLE, truncarea e automata dupa checkpoint
-- Shrink direct:
USE [NumeleBaseDeDate];
GO
DBCC SHRINKFILE (N'NumeFisierLog', 1);
GO

Shrink LDF — model FULL (pași obligatorii)
-- Pas 1: Backup log pentru a marca VLF-urile ca inactive
BACKUP LOG [NumeleBaseDeDate]
TO DISK = 'D:\Backup\NumeleBaseDeDate_log.bak'
WITH NOFORMAT, NOINIT, STATS = 10;
GO

-- Pas 2: Shrink fisierul log
USE [NumeleBaseDeDate];
GO
DBCC SHRINKFILE (N'NumeFisierLog', 1);
GO

-- Verifica rezultatul
SELECT name, ROUND(size * 8.0 / 1024, 1) AS [MB dupa shrink]
FROM sys.database_files
WHERE type = 1;
Best practice: în model FULL, programează backup-uri de log la fiecare 15–30 minute cu SQL Agent.
 Fără backup regulat de log, LDF-ul crește la infinit și nu poate fi recuperat fără backup.

Soluțiile pe termen lung elimină nevoia de shrink repetat. Abordează cauza reală a creșterii spațiului.

1 — Compresie date (row / page)

-- Estimeaza economiile INAINTE de aplicare
EXEC sys.sp_estimate_data_compression_savings
  @schema_name = 'dbo',
  @object_name = 'NumeleTabelului',
  @index_id = NULL,
  @partition_number = NULL,
  @data_compression = 'PAGE';

-- Aplica compresie PAGE pe un tabel
ALTER TABLE [dbo].[NumeleTabelului]
REBUILD WITH (DATA_COMPRESSION = PAGE);

-- Aplica compresie pe un index specific
ALTER INDEX [IX_NumeIndex] ON [dbo].[NumeleTabelului]
REBUILD WITH (DATA_COMPRESSION = PAGE);
GO

 Compresia PAGE poate reduce spațiul cu 30–50% pe tabele cu date repetitive.
 Necesită CPU suplimentar la citire/scriere, ideal pentru date istorice sau de arhivă.

2 — Arhivare date vechi

-- Exemplu: muta date mai vechi de 2 ani intr-un tabel de arhiva
BEGIN TRANSACTION;

INSERT INTO [dbo].[TabelArhiva] (col1, col2, col3, data_inregistrare)
SELECT col1, col2, col3, data_inregistrare
FROM [dbo].[TabelPrincipal]
WHERE data_inregistrare < DATEADD(YEAR, -2, GETDATE());

DELETE FROM [dbo].[TabelPrincipal]
WHERE data_inregistrare < DATEADD(YEAR, -2, GETDATE());

COMMIT TRANSACTION;
GO

Rulează DELETE în batches (ex. câte 10.000 rânduri) pentru a evita creșterea explozivă a LDF-ului și blocarea tabelelor.

3 — Ștergere date în batches (fără explozie log)

-- DELETE in batches de 10.000 randuri
DECLARE @BatchSize INT = 10000;
DECLARE @RowsDeleted INT = 1;

WHILE @RowsDeleted > 0
BEGIN
  DELETE TOP (@BatchSize)
  FROM [dbo].[TabelPrincipal]
  WHERE data_inregistrare < DATEADD(YEAR, -2, GETDATE());

  SET @RowsDeleted = @@ROWCOUNT;
  WAITFOR DELAY '00:00:01'; -- pauza 1 secunda intre batches
END
GO

4 — Partiționare tabele mari pe date
-- Creeaza o functie de partitionare pe an
CREATE PARTITION FUNCTION PF_AnInregistrare (DATE)
AS RANGE RIGHT FOR VALUES
  ('2022-01-01', '2023-01-01', '2024-01-01', '2025-01-01');

-- Creeaza schema de partitionare
CREATE PARTITION SCHEME PS_AnInregistrare
AS PARTITION PF_AnInregistrare
ALL TO ([PRIMARY]);

-- Aplica pe tabelul nou (sau recreeaza tabelul)
CREATE TABLE [dbo].[TabelPartitionat] (
  id INT NOT NULL,
  data_inregistrare DATE NOT NULL,
  valoare NVARCHAR(200)
) ON PS_AnInregistrare(data_inregistrare);
GO

Partiționarea permite partition switching — arhivare instantanee a unui întreg segment fără DELETE, fără impact pe log.

Configurarea greșită a autogrowth este cauza numărul 1 a fișierelor log cu mii de VLF-uri și a creșterilor imprevizibile de spațiu.

Verifică configurarea autogrowth curentă

SELECT
  DB_NAME(database_id) AS [Baza de date],
  name AS [Fisier],
  type_desc AS [Tip],
  ROUND(size * 8.0 / 1024, 1) AS [Dimensiune MB],
  is_percent_growth AS [Crestere in %],
  CASE is_percent_growth
    WHEN 1 THEN CAST(growth AS VARCHAR) + '%'
    ELSE CAST(ROUND(growth * 8.0 / 1024, 0) AS VARCHAR) + ' MB'
  END AS [Increment crestere],
  ROUND(max_size * 8.0 / 1024, 0) AS [Dimensiune maxima MB]
FROM sys.master_files
ORDER BY DB_NAME(database_id), type;

Setează autogrowth corect (valori fixe, nu procente)

-- GREȘIT: autogrowth procentual (creste haotic pe fisiere mari)
-- ALTER DATABASE [NumeleBaseDeDate] MODIFY FILE (NAME='NumeFisier', FILEGROWTH=10%)

-- CORECT: autogrowth in MB, valori fixe
-- Pentru fisierul de date (MDF): increment 512 MB sau 1 GB
ALTER DATABASE [NumeleBaseDeDate]
MODIFY FILE (NAME = N'NumeFisierDate', FILEGROWTH = 512MB);

-- Pentru fisierul log (LDF): increment 256 MB (sub 1 GB)
ALTER DATABASE [NumeleBaseDeDate]
MODIFY FILE (NAME = N'NumeFisierLog', FILEGROWTH = 256MB);
GO

Verifică numărul de VLF-uri (SQL Server 2016+)

-- VLF-uri per baza de date (SQL Server 2016+)
SELECT
  [name] AS [Baza de date],
  COUNT(*) AS [Nr VLF-uri]
FROM sys.databases
CROSS APPLY sys.dm_db_log_info(database_id)
GROUP BY [name]
ORDER BY [Nr VLF-uri] DESC;

-- Sub 50 VLF = optim
-- Peste 500 VLF = problema de performanta la backup/recovery


Resetare VLF — reducere număr excesiv
-- ATENTIE: rulat DOAR in fereastra de mentenanta!
USE [NumeleBaseDeDate];
GO

-- Pas 1: backup log
BACKUP LOG [NumeleBaseDeDate] TO DISK = 'NUL';

-- Pas 2: shrink fisier log la minim
DBCC SHRINKFILE (N'NumeFisierLog', 1);

-- Pas 3: redimensioneaza la dimensiunea initiala dorita
ALTER DATABASE [NumeleBaseDeDate]
MODIFY FILE (NAME = N'NumeFisierLog', SIZE = 512MB, FILEGROWTH = 256MB);
GO

VLF optim
< 50
VLF atenție
50–200
VLF problemă
> 500
Increment log recomandat
256 MB
Instant File Initialization (IFI): activează IFI pentru contul de serviciu SQL Server (drept SE_MANAGE_VOLUME_NAME) — permite creșterea fișierelor MDF fără a umple cu zerouri. Nu funcționează pentru LDF.

Tip solutie

Permanent

Voteaza

(2 din 4 persoane apreciaza acest articol)

Despre Autor

Leave A Comment?