Попытки вернуть неиспользуемое пространство приводят к значительному увеличению используемого пространства в SQL Server.

15

У меня есть таблица в производственной базе данных, которая имеет размер 525 ГБ, из которых 383 ГБ не используется:

Неиспользуемое пространство

Я хотел бы освободить часть этого пространства, но, прежде чем связываться с производственной БД, я тестирую некоторые стратегии на идентичной таблице в тестовой БД с меньшим количеством данных. Эта таблица имеет похожую проблему:

Неиспользуемое пространство

Некоторая информация о столе:

  • Коэффициент заполнения установлен на 0
  • Есть около 30 столбцов
  • Один из столбцов - это большой объект типа изображения, в котором хранятся файлы размером от нескольких КБ до нескольких сотен МБ.
  • Таблица не имеет каких-либо гипотетических индексов, связанных с ней

Сервер работает под управлением SQL Server 2017 (RTM-GDR) (KB4505224) - 14.0.2027.2 (X64). База данных использует SIMPLEмодель восстановления.

Некоторые вещи, которые я пробовал:

  • Перестройка индексов: ALTER INDEX ALL ON dbo.MyTable REBUILD. Это оказало незначительное влияние.
  • Реорганизация индексов: ALTER INDEX ALL ON dbo.MyTable REORGANIZE WITH(LOB_COMPACTION = ON). Это оказало незначительное влияние.
  • Скопировал столбец большого объекта в другую таблицу, удалил столбец, заново создал столбец и скопировал данные обратно (как описано в этом сообщении: освобождение таблицы SQL Server неиспользуемого пространства ). Это уменьшило неиспользуемое пространство, но, казалось, просто преобразовало его в используемое пространство:

    Неиспользуемое пространство

  • Использовал утилиту bcp для экспорта таблицы, ее обрезания и перезагрузки (как описано в этом посте: Как освободить неиспользуемое пространство для таблицы ). Это также уменьшило неиспользуемое пространство и увеличило используемое пространство в той же степени, что и изображение выше.

  • Хотя это и не рекомендуется, я пробовал команды DBCC SHRINKFILE и DBCC SHRINKDATABASE, но они не оказали никакого влияния на неиспользуемое пространство.
  • Бег DBCC CLEANTABLE('myDB', 'dbo.myTable')не имел значения
  • Я пробовал все вышеперечисленное как при сохранении типов данных изображения и текста, так и после изменения типов данных на varbinary (max) и varchar (max).
  • Я попытался импортировать данные в новую таблицу в новой базе данных, и это также только преобразовало неиспользуемое пространство в используемое пространство. Я изложил подробности этой попытки в этом посте .

Я не хочу делать эти попытки на производственной базе данных, если это ожидаемые результаты, поэтому:

  1. Почему неиспользованное пространство просто превращается в использованное пространство после некоторых из этих попыток? Я чувствую, что не понимаю, что происходит под капотом.
  2. Что еще я могу сделать, чтобы уменьшить неиспользуемое пространство, не увеличивая его?

РЕДАКТИРОВАТЬ: Вот отчет использования диска и сценарий для таблицы:

Использование диска

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

Вот результаты выполнения команд в ответе Макса Вернона:

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

ОБНОВИТЬ:

Я запустил следующее по предложению Макса Вернона:

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

И вот был выход:

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.

Это обновило использование диска для таблицы:

введите описание изображения здесь

И общее использование диска:

введите описание изображения здесь

Таким образом, похоже, что проблема заключалась в том, что использование диска, отслеживаемое SQL Server, стало совершенно несоответствующим фактическому использованию диска. Я считаю, что эта проблема решена, но мне было бы интересно узнать, почему это произошло бы в первую очередь!

кругозор
источник

Ответы:

10

Я бы запустил DBCC UPDATEUSAGE для таблицы в качестве первого шага, поскольку симптомы указывают на непоследовательное использование пространства.

DBCC UPDATEUSAGE исправляет строки, используемые страницы, зарезервированные страницы, конечные страницы и количество страниц данных для каждого раздела в таблице или индексе. Если в системных таблицах нет неточностей, DBCC UPDATEUSAGE не возвращает данных. Если неточности найдены и исправлены, а WITH NO_INFOMSGS не используется, DBCC UPDATEUSAGE возвращает строки и столбцы, обновляемые в системных таблицах.

Синтаксис:

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

После того, как вы запустите это, я бегу EXEC sys.sp_spaceusedпо столу:

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

Приведенная выше команда имеет возможность обновить использование, но так как вы DBCC UPDATEUSAGEсначала запустили вручную, просто оставьте для этого значение false. Запуск DBCC UPDATEUSAGEвручную позволяет увидеть, было ли что-то исправлено.

Следующий запрос должен показать процент свободных байтов в таблице и процент свободных страниц в таблице. Поскольку в запросе используется недокументированная функция, неразумно рассчитывать на результаты, но он кажется точным по сравнению с выходными sys.sp_spaceusedданными высокого уровня.

Если процент свободных байтов значительно выше, чем процент свободных страниц, то у вас много частично пустых страниц.

Частично пустые страницы могут быть вызваны рядом причин, в том числе:

  1. Разделение страницы, где страница должна быть разделена для размещения новых вставок в кластеризованном индексе

  2. Невозможность заполнить страницу столбцами из-за размера столбца.

В запросе используется недокументированная sys.dm_db_database_page_allocationsфункция динамического управления:

;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

Вывод выглядит так:

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

Я написал сообщение в блоге, описывающее функцию здесь .

В вашем сценарии, поскольку вы выполнили ALTER TABLE ... REBUILD, вы должны увидеть очень низкое число для TotalEmptyPages, но я предполагаю, что у вас все еще будет около 72%BytesFreePercent .

Я использовал ваш CREATE TABLEсценарий, чтобы попытаться воссоздать ваш сценарий.

Это MCVE, который я использую:

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

Следующий запрос показывает одну строку для каждой страницы, выделенной для таблицы, и использует тот же недокументированный 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

Вывод покажет много строк, если вы запустите его в своей тестовой среде с реальной таблицей, но это может позволить вам увидеть, в чем проблема.

Можете ли вы запустить следующий скрипт и опубликовать результаты в вашем вопросе? Я просто пытаюсь убедиться, что мы на одной странице.

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;
Макс Вернон
источник
2
Выполняется DBCC UPDATEUSAGEобновление неиспользуемого пространства и количества неиспользуемых страниц. Похоже, что использование диска и информация о страницах, сообщаемая SQL Server, была чрезвычайно несинхронизирована - я обновил свой пост с подробностями. Мне интересно, как это могло бы произойти в первую очередь, но, по крайней мере, проблема была найдена. Спасибо за вашу помощь, я действительно ценю это!
Кен
0

Один из столбцов - это большой объект типа изображения, в котором хранятся файлы размером от нескольких КБ до нескольких сотен МБ.

Возможно, вы испытываете внутреннюю фрагментацию.
Какова фрагментация страницы для этой таблицы?
И отличается ли фрагментация для строки в ряду от страниц вне строки?

Вы говорите, что у вас есть файлы размером в несколько килобайт.
SQL Server хранит все в 8060 байтовых страницах. Это означает, что если у вас есть строка (или данные вне строки), которая составляет 4040 байтов, а следующая аналогична, она не может поместиться в одну и ту же страницу, и вы потеряете половину своего пространства. Попробуйте изменить размер строки, сохранив столбцы переменной длины (например, начните с изображения) в другой таблице.

DrTrunks Bell
источник
Я не думаю, что фрагментация является проблемой. После перестройки индексов фрагментация кластеризованного индекса составляет 0,45%, а заполненность его страниц - 98,93%.
Кен
Перестройка таблицы или индекса не поможет, если вы страдаете от очень больших строк или данных больших объектов, которые плохо помещаются на страницах размером 8 КБ. Это то, что Макс Вернон объяснил более подробно: «у вас много частично пустых страниц». также называется внутренней фрагментацией
DrTrunks Bell
-3

База данных находится в режиме полного восстановления? Если да, то когда вы выполняете сжатие, оно регистрирует все изменения и не будет сокращать его так, как вы ожидаете. В зависимости от количества часов работы вы можете сделать резервную копию, переключиться в режим массового восстановления и затем выполнить сжатие файла данных. После этого вы захотите запустить индексные сценарии для восстановления / восстановления и вернуться к полному восстановлению. Это то, что я бы попробовал в любом случае, но опять же, это зависит от ваших часов работы для всего этого.

Джон-Генри Лохбаум
источник
4
Воспитывать модель восстановления интересно. Я думаю, что было бы более применимым, если бы у ОП были проблемы с размером их файла журнала. В настоящее время у них возникают проблемы с размером файла данных, поэтому я бы удивился, если модель восстановления стала причиной описанной проблемы.
Джош Дарнелл
Это правда, но единственные случаи, когда я выполнял сжатие, и это не оказывало существенного влияния на пространство, было связано с моделью восстановления, поэтому я подумал, что стоит поднять вопрос на случай, если это будет неправильный диагноз.
Джон-Генри Лохбаум
-3

Единственный раз, когда мне не удалось сжать БД и освободить пространство, это потому, что вы не можете сжать БД сверх первоначального размера БД, когда она была создана. Например, если ваша БД является копией рабочей БД и вы впервые создали БД на 525 ГБ, сервер sql не позволит вам уменьшить размер ниже 525 ГБ независимо от того, сколько данных вы удаляете из БД. Но если база данных была создана ниже 383 ГБ, а затем выросла до 525 ГБ, у вас не должно возникнуть проблем с восстановлением свободного пространства. Я долго думал, что это глупое и произвольное ограничение со стороны Microsoft.

Сжатие базы данных только до ее первоначального размера, который устанавливается после создания базы данных

ZyxwvuTJ
источник
Вопрос не в том, чтобы сжать базу данных (и, если это так, возможность ее уменьшения зависит от используемого пространства после области начального размера)
eckes
Пока есть неиспользуемое пространство, можно сократить базу данных до пары МБ независимо от исходного размера. Это не обязательно хорошая идея, но у меня было много случаев сократить базы данных и никогда не сталкивался с таким ограничением.
Рэй
-3

Я сталкивался с этой проблемой ранее на производственных блоках, вам нужно перестроить таблицы и индексы для каждой таблицы (в таком порядке).

Вот запрос, который я использую, чтобы контролировать таблицы. Это поможет вам определить, какие таблицы необходимо перестроить, и создать запросы SQL, которые необходимо выполнить. Этот запрос ограничен запросами с неиспользуемым пространством более 1 МБ и соотношением неиспользованных 5%, так что вы перестраиваете только то, на чем действительно нужно сосредоточиться:

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
Луис Альберто Барандиаран
источник
восстановление таблицы, как утверждает OP, избавило бы от большей части фрагментации. Я сомневаюсь, что дальнейшая перестройка поможет в дальнейшем.
Макс Вернон