Próby odzyskania nieużywanego miejsca powodują znaczne zwiększenie używanego miejsca w programie SQL Server

15

Mam tabelę w produkcyjnej bazie danych o wielkości 525 GB, z czego 383 GB jest nieużywane:

Niewykorzystana przestrzeń

Chciałbym odzyskać trochę tego miejsca, ale zanim zacznę działać z produkcyjną bazą danych, testuję niektóre strategie na identycznej tabeli w testowej bazie danych z mniejszą ilością danych. W tej tabeli występuje podobny problem:

Niewykorzystana przestrzeń

Niektóre informacje o tabeli:

  • Współczynnik wypełnienia jest ustawiony na 0
  • Istnieje około 30 kolumn
  • Jedna z kolumn to LOB typu image i przechowuje pliki o rozmiarach od kilku KB do kilkuset MB
  • Tabela nie ma z nią żadnych hipotetycznych indeksów

Serwer działa SQL Server 2017 (RTM-GDR) (KB4505224) - 14.0.2027.2 (X64). Baza danych korzysta z SIMPLEmodelu odzyskiwania.

Niektóre rzeczy próbowałem:

  • Odbudowa indeksów: ALTER INDEX ALL ON dbo.MyTable REBUILD. Miało to znikomy wpływ.
  • Reorganizacja indeksów: ALTER INDEX ALL ON dbo.MyTable REORGANIZE WITH(LOB_COMPACTION = ON). Miało to znikomy wpływ.
  • Skopiowałem kolumnę LOB do innej tabeli, upuściłem kolumnę, ponownie utworzyłem kolumnę i skopiowałem dane z powrotem (jak opisano w tym poście: Uwalnianie nieużywanej tabeli SQL Server Space ). Zmniejszyło to nieużywane miejsce, ale wydawało się, że po prostu przekształciło je w używane miejsce:

    Niewykorzystana przestrzeń

  • Użyłem narzędzia bcp do wyeksportowania tabeli, obcięcia jej i ponownego załadowania (jak opisano w tym poście: Jak zwolnić nieużywane miejsce na tabelę ). Zmniejszyło to również niewykorzystane miejsce i zwiększyło używane miejsce w podobnym stopniu, jak na powyższym obrazie.

  • Chociaż nie jest to zalecane, wypróbowałem polecenia DBCC SHRINKFILE i DBCC SHRINKDATABASE, ale nie miały one wpływu na nieużywane miejsce.
  • Bieganie DBCC CLEANTABLE('myDB', 'dbo.myTable') nie miało znaczenia
  • Próbowałem wszystkich powyższych, zachowując typy danych obrazu i tekstu oraz po zmianie typów danych na varbinary (max) i varchar (max).
  • Próbowałem zaimportować dane do nowej tabeli w świeżej bazie danych, co również przekształciło tylko nieużywane miejsce w miejsce używane. Szczegóły tej próby opisałem w tym poście .

Nie chcę podejmować tych prób na produkcyjnej bazie danych, jeśli takich wyników mogę się spodziewać, więc:

  1. Dlaczego niewykorzystane miejsce jest po prostu konwertowane na używane miejsce po niektórych z tych prób? Czuję, że nie rozumiem dobrze, co dzieje się pod maską.
  2. Czy jest coś jeszcze, co mogę zrobić, aby zmniejszyć niewykorzystane miejsce bez zwiększania zajętego miejsca?

EDYCJA: Oto raport Wykorzystanie dysku i skrypt dla tabeli:

Użycie dysku

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[MyTable](
    [Column1]  [int] NOT NULL,
    [Column2]  [int] NOT NULL,
    [Column3]  [int] NOT NULL,
    [Column4]  [bit] NOT NULL,
    [Column5]  [tinyint] NOT NULL,
    [Column6]  [datetime] NULL,
    [Column7]  [int] NOT NULL,
    [Column8]  [varchar](100) NULL,
    [Column9]  [varchar](256) NULL,
    [Column10] [int] NULL,
    [Column11] [image] NULL,
    [Column12] [text] NULL,
    [Column13] [varchar](100) NULL,
    [Column14] [varchar](6) NULL,
    [Column15] [int] NOT NULL,
    [Column16] [bit] NOT NULL,
    [Column17] [datetime] NULL,
    [Column18] [varchar](50) NULL,
    [Column19] [varchar](50) NULL,
    [Column20] [varchar](60) NULL,
    [Column21] [varchar](20) NULL,
    [Column22] [varchar](120) NULL,
    [Column23] [varchar](4) NULL,
    [Column24] [varchar](75) NULL,
    [Column25] [char](1) NULL,
    [Column26] [varchar](50) NULL,
    [Column27] [varchar](128) NULL,
    [Column28] [varchar](50) NULL,
    [Column29] [int] NULL,
    [Column30] [text] NULL,
 CONSTRAINT [PK] PRIMARY KEY CLUSTERED 
(
    [Column1] ASC,
    [Column2] ASC,
    [Column3] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
ALTER TABLE [dbo].[MyTable] ADD  CONSTRAINT [DF_Column4]  DEFAULT (0) FOR [Column4]
GO
ALTER TABLE [dbo].[MyTable] ADD  CONSTRAINT [DF_Column5]  DEFAULT (0) FOR [Column5]
GO
ALTER TABLE [dbo].[MyTable] ADD  CONSTRAINT [DF_Column15]  DEFAULT (0) FOR [Column15]
GO
ALTER TABLE [dbo].[MyTable] ADD  CONSTRAINT [DF_Column16]  DEFAULT (0) FOR [Column16]
GO

Oto wyniki wykonania poleceń w odpowiedzi Maxa Vernona:

╔════════════╦═══════════╦════════════╦═════════════════╦══════════════════════╦════════════════════╗
 TotalBytes  FreeBytes  TotalPages  TotalEmptyPages  PageBytesFreePercent  UnusedPagesPercent 
╠════════════╬═══════════╬════════════╬═════════════════╬══════════════════════╬════════════════════╣
  9014280192 8653594624     1100376          997178             95.998700           90.621500 
╚════════════╩═══════════╩════════════╩═════════════════╩══════════════════════╩════════════════════╝
╔═════════════╦═══════════════════╦════════════════════╗
 ObjectName   ReservedPageCount       UsedPageCount 
╠═════════════╬═══════════════════╬════════════════════╣
 dbo.MyTable            5109090             2850245 
╚═════════════╩═══════════════════╩════════════════════╝

AKTUALIZACJA:

Uruchomiłem następujące, zgodnie z sugestią Maxa Vernona:

DBCC UPDATEUSAGE (N'<database_name>', N'<table_name>');

A oto wynik:

DBCC UPDATEUSAGE: Usage counts updated for table 'MyTable' (index 'PK_MyTable', partition 1):
        USED pages (LOB Data): changed from (568025) to (1019641) pages.
        RSVD pages (LOB Data): changed from (1019761) to (1019763) pages.

To zaktualizowało użycie dysku dla tabeli:

wprowadź opis zdjęcia tutaj

I ogólne użycie dysku:

wprowadź opis zdjęcia tutaj

Wygląda więc na to, że problem polegał na tym, że użycie dysku śledzone przez SQL Server stało się bardzo niezsynchronizowane z rzeczywistym użyciem dysku. Uważam ten problem za rozwiązany, ale chciałbym wiedzieć, dlaczego tak się stało!

Rozpoznać
źródło

Odpowiedzi:

10

Jako pierwszy krok uruchomiłbym DBCC UPDATEUSAGE dla tabeli, ponieważ objawy wskazują na niespójne wykorzystanie miejsca.

DBCC UPDATEUSAGE koryguje wiersze, używane strony, zarezerwowane strony, strony liści i liczbę stron danych dla każdej partycji w tabeli lub indeksie. Jeśli w tabelach systemowych nie ma niedokładności, DBCC UPDATEUSAGE nie zwraca danych. Jeśli zostaną znalezione i poprawione niedokładności i nie zostanie użyta opcja Z NO_INFOMSGS, DBCC UPDATEUSAGE zwraca wiersze i kolumny aktualizowane w tabelach systemowych.

Składnia to:

DBCC UPDATEUSAGE (N'<database_name>', N'<table_name>');

Gdy to uruchomisz, pobiegnę EXEC sys.sp_spaceusedna stół:

EXEC sys.sp_spaceused @objname = N'dbo.MyTable'
    , @updateusage = 'false' --true or false
    , @mode = 'ALL' --ALL, LOCAL_ONLY, REMOTE_ONLY
    , @oneresultset = 1;

Powyższe polecenie ma opcję aktualizacji użycia, ale ponieważ najpierw uruchomiłeś DBCC UPDATEUSAGEręcznie, po prostu ustaw wartość false. Uruchamianie DBCC UPDATEUSAGEręczne pozwala sprawdzić, czy coś zostało poprawione.

Poniższe zapytanie powinno pokazywać procent wolnych bajtów w tabeli i procent wolnych stron w tabeli. Ponieważ zapytanie wykorzystuje funkcję nieudokumentowaną, nierozsądnie jest liczyć na wyniki, ale wydaje się dokładne w porównaniu z danymi wyjściowymi z sys.sp_spaceusedwysokiego poziomu.

Jeśli procent wolnych bajtów jest znacznie wyższy niż procent wolnych stron, oznacza to, że masz dużo częściowo pustych stron.

Częściowo puste strony mogą wynikać z wielu przyczyn, w tym:

  1. Podziały strony, przy czym strona musi zostać podzielona, ​​aby uwzględnić nowe wstawki w indeksie klastrowym

  2. Niemożność wypełnienia strony kolumnami ze względu na rozmiar kolumny.

W zapytaniu użyto nieudokumentowanej sys.dm_db_database_page_allocationsfunkcji zarządzania dynamicznego:

;WITH dpa AS 
(
    SELECT dpa.*
        , page_free_space_percent_corrected = 
          CASE COALESCE(dpa.page_type_desc, N'')
            WHEN N'TEXT_MIX_PAGE' THEN 100 - COALESCE(dpa.page_free_space_percent, 100)
            WHEN N'TEXT_TREE_PAGE' THEN 100 - COALESCE(dpa.page_free_space_percent, 100)
            ELSE COALESCE(dpa.page_free_space_percent, 100)
          END
    FROM sys.dm_db_database_page_allocations(DB_ID(), OBJECT_ID('dbo.MyTable'), NULL, NULL, 'DETAILED') dpa
)
, src AS
(
SELECT TotalKB = COUNT_BIG(1) * 8192 / 1024
    , FreeKB = SUM((dpa.page_free_space_percent_corrected / 100) * CONVERT(bigint, 8192)) / 1024
    , TotalPages = COUNT_BIG(1)
    , TotalEmptyPages = SUM(CASE WHEN dpa.page_free_space_percent_corrected = 100 THEN 1 ELSE 0 END) --completely empty pages
FROM dpa
)
SELECT *
    , BytesFreePercent = (CONVERT(decimal(38,2), src.FreeKB) / src.TotalKB) * 100
    , UnusedPagesPercent = (CONVERT(decimal(38,2), src.TotalEmptyPages) / src.TotalPages) * 100
FROM src

Wyjście wygląda następująco:

╔═════════╦════════╦════════════╦═════════════════ ╦══════════════════╦════════════════════╗
║ TotalKB ║ FreeKB ║ TotalPages ║ TotalEmptyPages ║ BytesFreePercent ║ UnusedPagesPercent ║
╠═════════╬════════╬════════════╬═════════════════ ╬══════════════════╬════════════════════╣
║ 208 ║ 96 ║ 26 ║ 12 ║ 46,153800 ║ 46,153800 ║
╚═════════╩════════╩════════════╩═════════════════ ╩══════════════════╩════════════════════╝

Napisałem blogu opisujące funkcję tutaj .

W twoim scenariuszu, ponieważ wykonałeś ALTER TABLE ... REBUILD, powinieneś zobaczyć bardzo niską liczbę TotalEmptyPages, ale domyślam się, że nadal będziesz miał około 72% BytesFreePercent.

Użyłem twojego CREATE TABLEskryptu do odtworzenia twojego scenariusza.

Oto MCVE, którego używam:

DROP TABLE IF EXISTS dbo.MyTable;

CREATE TABLE [dbo].[MyTable](
    [Column1]  [int]            NOT NULL IDENTITY(1,1),
    [Column2]  [int]            NOT NULL,
    [Column3]  [int]            NOT NULL,
    [Column4]  [bit]            NOT NULL,
    [Column5]  [tinyint]        NOT NULL,
    [Column6]  [datetime]       NULL,
    [Column7]  [int]            NOT NULL,
    [Column8]  [varchar](100)   NULL,
    [Column9]  [varchar](256)   NULL,
    [Column10] [int]            NULL,
    [Column11] [image]          NULL,
    [Column12] [text]           NULL,
    [Column13] [varchar](100)   NULL,
    [Column14] [varchar](6)     NULL,
    [Column15] [int]            NOT NULL,
    [Column16] [bit]            NOT NULL,
    [Column17] [datetime]       NULL,
    [Column18] [varchar](50)    NULL,
    [Column19] [varchar](50)    NULL,
    [Column20] [varchar](60)    NULL,
    [Column21] [varchar](20)    NULL,
    [Column22] [varchar](120)   NULL,
    [Column23] [varchar](4)     NULL,
    [Column24] [varchar](75)    NULL,
    [Column25] [char](1)        NULL,
    [Column26] [varchar](50)    NULL,
    [Column27] [varchar](128)   NULL,
    [Column28] [varchar](50)    NULL,
    [Column29] [int]            NULL,
    [Column30] [text]           NULL,
 CONSTRAINT [PK] PRIMARY KEY CLUSTERED 
(
    [Column1] ASC,
    [Column2] ASC,
    [Column3] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

ALTER TABLE [dbo].[MyTable] ADD  CONSTRAINT [DF_Column4]  DEFAULT (0) FOR [Column4]

ALTER TABLE [dbo].[MyTable] ADD  CONSTRAINT [DF_Column5]  DEFAULT (0) FOR [Column5]

ALTER TABLE [dbo].[MyTable] ADD  CONSTRAINT [DF_Column15]  DEFAULT (0) FOR [Column15]

ALTER TABLE [dbo].[MyTable] ADD  CONSTRAINT [DF_Column16]  DEFAULT (0) FOR [Column16]
GO

INSERT INTO dbo.MyTable (
      Column2
    , Column3
    , Column4
    , Column5
    , Column6
    , Column7
    , Column8
    , Column9
    , Column10
    , Column11
    , Column12
    , Column13
    , Column14
    , Column15
    , Column16
    , Column17
    , Column18
    , Column19
    , Column20
    , Column21
    , Column22
    , Column23
    , Column24
    , Column25
    , Column26
    , Column27
    , Column28
    , Column29
    , Column30
)
VALUES (
          0
        , 0
        , 0
        , 0
        , '2019-07-09 00:00:00'
        , 1
        , REPLICATE('A', 50)    
        , REPLICATE('B', 128)   
        , 0
        , REPLICATE(CONVERT(varchar(max), 'a'), 1)
        , REPLICATE(CONVERT(varchar(max), 'b'), 9000)
        , REPLICATE('C', 50)    
        , REPLICATE('D', 3)     
        , 0
        , 0
        , '2019-07-10 00:00:00'
        , REPLICATE('E', 25)    
        , REPLICATE('F', 25)    
        , REPLICATE('G', 30)    
        , REPLICATE('H', 10)    
        , REPLICATE('I', 120)   
        , REPLICATE('J', 4)     
        , REPLICATE('K', 75)    
        , 'L'       
        , REPLICATE('M', 50)    
        , REPLICATE('N', 128)   
        , REPLICATE('O', 50)    
        , 0
        , REPLICATE(CONVERT(varchar(max), 'c'), 90000)
);
--GO 100

;WITH dpa AS 
(
    SELECT dpa.*
        , page_free_space_percent_corrected = 
          CASE COALESCE(dpa.page_type_desc, N'')
            WHEN N'TEXT_MIX_PAGE' THEN 100 - COALESCE(dpa.page_free_space_percent, 100)
            WHEN N'TEXT_TREE_PAGE' THEN 100 - COALESCE(dpa.page_free_space_percent, 100)
            ELSE COALESCE(dpa.page_free_space_percent, 100)
          END
    FROM sys.dm_db_database_page_allocations(DB_ID(), OBJECT_ID('dbo.MyTable'), NULL, NULL, 'DETAILED') dpa
)
, src AS
(
SELECT TotalKB = COUNT_BIG(1) * 8192 / 1024
    , FreeKB = SUM((dpa.page_free_space_percent_corrected / 100) * CONVERT(bigint, 8192)) / 1024
    , TotalPages = COUNT_BIG(1)
    , TotalEmptyPages = SUM(CASE WHEN dpa.page_free_space_percent_corrected = 100 THEN 1 ELSE 0 END) --completely empty pages
FROM dpa
)
SELECT *
    , BytesFreePercent = (CONVERT(decimal(38,2), src.FreeKB) / src.TotalKB) * 100
    , UnusedPagesPercent = (CONVERT(decimal(38,2), src.TotalEmptyPages) / src.TotalPages) * 100
FROM src

Poniższe zapytanie pokazuje jeden wiersz dla każdej strony przypisanej do tabeli i używa tego samego nieudokumentowanego DMV:

SELECT DatabaseName = d.name
    , ObjectName = o.name
    , IndexName = i.name
    , PartitionID = dpa.partition_id
    , dpa.allocation_unit_type_desc
    , dpa.allocated_page_file_id
    , dpa.allocated_page_page_id
    , dpa.is_allocated
    , dpa.page_free_space_percent --this seems unreliable
    , page_free_space_percent_corrected = 
        CASE COALESCE(dpa.page_type_desc, N'')
        WHEN N'TEXT_MIX_PAGE' THEN 100 - COALESCE(dpa.page_free_space_percent, 100)
        WHEN N'TEXT_TREE_PAGE' THEN 100 - COALESCE(dpa.page_free_space_percent, 100)
        ELSE COALESCE(dpa.page_free_space_percent, 100)
        END
    , dpa.page_type_desc
    , dpa.is_page_compressed
    , dpa.has_ghost_records
FROM sys.dm_db_database_page_allocations(DB_ID(), OBJECT_ID('dbo.MyTable'), NULL, NULL, 'DETAILED') dpa
    LEFT JOIN sys.databases d ON dpa.database_id = d.database_id
    LEFT JOIN sys.objects o ON dpa.object_id = o.object_id
    LEFT JOIN sys.indexes i ON dpa.object_id = i.object_id AND dpa.index_id = i.index_id
WHERE dpa.database_id = DB_ID() --sanity check for sys.objects and sys.indexes

Dane wyjściowe pokażą wiele wierszy, jeśli uruchomisz je z rzeczywistą tabelą w środowisku testowym, ale może pozwolić ci zobaczyć, gdzie jest problem.

Czy możesz uruchomić następujący skrypt i opublikować wyniki w swoim pytaniu? Próbuję tylko upewnić się, że jesteśmy na tej samej stronie.

SELECT ObjectName = s.name + N'.' + o.name
    , ReservedPageCount = SUM(dps.reserved_page_count)
    , UsePageCount = SUM(dps.used_page_count)
FROM sys.schemas s
    INNER JOIN sys.objects o ON s.schema_id = o.schema_id
    INNER JOIN sys.partitions p ON o.object_id = p.object_id
    INNER JOIN sys.dm_db_partition_stats dps ON p.object_id = dps.object_id
WHERE s.name = N'dbo'
    AND o.name = N'MyTable'
GROUP BY s.name + N'.' + o.name;
Max Vernon
źródło
2
Uruchamianie DBCC UPDATEUSAGEzaktualizowało nieużywane miejsce i liczbę nieużywanych stron. Wygląda na to, że użycie dysku i informacje o stronie zgłaszane przez SQL Server były bardzo niezsynchronizowane - zaktualizowałem swój post ze szczegółami. Jestem ciekawy, jak by to się stało, ale przynajmniej problem został znaleziony. Dziękuję za całą pomoc, naprawdę to doceniam!
Ken,
0

Jedna z kolumn to LOB typu image i przechowuje pliki o rozmiarach od kilku KB do kilkuset MB

Może występować fragmentacja wewnętrzna.
Jaka jest fragmentacja strony dla tej tabeli?
Czy fragmentacja dla wiersza w wierszu różni się od stron poza wierszem?

Mówisz, że masz pliki o rozmiarze kilku KB.
SQL Server przechowuje wszystko na stronach 8060 bajtów. Oznacza to, że jeśli masz wiersz (lub dane poza wierszem), który ma 4040 bajtów, a następny jest podobny, nie może zmieścić się na obu stronach i zmarnujesz połowę miejsca. Spróbuj zmienić rozmiar wiersza, przechowując kolumny o zmiennej długości (na przykład zacznij od obrazu) w innej tabeli.

DrTrunks Bell
źródło
Nie sądzę, że problemem jest fragmentacja. Po odbudowaniu indeksów fragmentacja indeksu klastrowanego wynosi 0,45%, a jego wypełnienie strony wynosi 98,93%.
Ken
Odbudowa tabeli lub indeksu nie pomoże, jeśli cierpisz na bardzo duże wiersze lub dane LOB, które nie mieszczą się dobrze na stronach o wielkości 8 KB. Dokładnie to wyjaśnił Max Vernon: „masz wiele częściowo pustych stron”. zwany także fragmentacją wewnętrzną
DrTrunks Bell
-3

Czy baza danych jest w trybie pełnego odzyskiwania? Jeśli tak, podczas zmniejszania rejestrowane są wszystkie zmiany i nie zmniejszają się w oczekiwany sposób. W zależności od godzin pracy możesz wykonać kopię zapasową, przejść do trybu odzyskiwania wysyłek masowych, a następnie uruchomić zmniejszanie pliku danych. Następnie należy uruchomić skrypty indeksowe w celu naprawy / przebudowy i powrotu do pełnego odzyskiwania. I tak bym spróbował, ale to wszystko zależy od twoich godzin pracy.

John-Henry Lochbaum
źródło
4
Przywołanie modelu odzyskiwania jest interesujące. Myślę, że byłoby bardziej odpowiednie, gdyby OP miał problemy z rozmiarem swojego pliku dziennika. W tej chwili mają problem z rozmiarem pliku danych, więc byłbym zaskoczony, gdyby model odzyskiwania spowodował opisany problem.
Josh Darnell
To prawda, ale jedyny raz, kiedy uruchomiłem skurcz i to tak naprawdę nie wpłynęło na przestrzeń, wynikało z modelu odzyskiwania, więc pomyślałem, że warto o tym wspomnieć w przypadku błędnej diagnozy.
John-Henry Lochbaum
-3

Jedyny raz, kiedy nie byłem w stanie zmniejszyć bazy danych i odzyskać miejsca, ponieważ nie można zmniejszyć bazy danych powyżej początkowej wielkości bazy danych, kiedy została utworzona. Na przykład, jeśli twoja baza danych jest kopią produkcyjnej bazy danych, a baza danych została utworzona po raz pierwszy przy wielkości 525 GB, serwer SQL nie pozwoli ci zmniejszyć rozmiaru poniżej 525 GB bez względu na to, ile danych usuniesz z bazy danych. Ale jeśli DB został utworzony poniżej 383 GB, a następnie wyrósł do 525 GB, nie powinieneś mieć problemu z odzyskaniem miejsca. Od dawna myślałem, że to głupie i arbitralne ograniczenie ze strony Microsoft.

Zmniejsz bazę danych tylko do jej początkowego rozmiaru, który jest ustawiony po utworzeniu bazy danych

ZyxwvuTJ
źródło
Pytanie nie dotyczy zmniejszania bazy danych (a jeśli tak, to możliwość jej zmniejszenia zależy od wykorzystanej przestrzeni po regionie początkowej wielkości)
eckes
Dopóki jest nieużywane miejsce, możliwe jest zmniejszenie bazy danych do kilku MB, niezależnie od pierwotnego rozmiaru. Niekoniecznie jest to dobry pomysł, ale miałem wiele okazji, aby zmniejszyć bazy danych i nigdy nie napotkać takiego limitu.
Ray
-3

Zetknąłem się wcześniej z tym problemem na polach produkcyjnych, co musisz zrobić, to odbudować tabele i indeksy dla każdej tabeli (w tej kolejności).

Oto zapytanie, którego używam do kontrolowania tabel. Pomoże Ci określić, które tabele należy odbudować, i utworzyć zapytania SQL, które należy uruchomić. To zapytanie jest ograniczone do tych, które mają więcej niż 1 MB nieużywanego miejsca i 5% nieużywanego współczynnika, dzięki czemu można odbudować tylko to, na czym naprawdę należy się skupić:

SELECT  'alter table [' + t.NAME + '] rebuild;' AS SQL1, 'alter index all on [' + t.NAME + '] rebuild;' as SQL2, t.NAME AS TableName, p.rows AS RowCounts, SUM(a.total_pages) * 8/1024 AS TotalSpaceMB,  SUM(a.used_pages) * 8/1024 AS UsedSpaceMB,  (SUM(a.total_pages) - SUM(a.used_pages)) * 8/1024 AS UnusedSpaceMB, case when SUM(a.total_pages)=0 then 0 else (SUM(a.total_pages) - SUM(a.used_pages))*100/SUM(a.total_pages) end as Ratio  FROM     sys.tables t (nolock) INNER JOIN       sys.indexes i (nolock)  ON t.OBJECT_ID = i.object_id INNER JOIN  sys.partitions p (nolock) ON i.object_id = p.OBJECT_ID AND i.index_id = p.index_id INNER JOIN  sys.allocation_units a (nolock) ON p.partition_id = a.container_id LEFT OUTER JOIN  sys.schemas s (nolock) ON t.schema_id = s.schema_id WHERE  t.is_ms_shipped = 0 AND i.OBJECT_ID > 255  GROUP BY  t.Name, s.Name, p.Rows  
having  (SUM(a.total_pages) - SUM(a.used_pages)) * 8/1024>1
and (SUM(a.total_pages) - SUM(a.used_pages))*100/SUM(a.total_pages)>5
ORDER BY    5 desc
Luis Alberto Barandiaran
źródło
przebudowa stołu, zgodnie z tym, co zrobili PO, wyeliminowałaby większość fragmentacji. Wątpię, aby kolejna przebudowa pomogła dalej.
Max Vernon