Спроба повернути невикористаний простір призводить до значного збільшення використовуваного простору на SQL Server


15

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

Невикористаний простір

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

Невикористаний простір

Деякі відомості про таблицю:

  • Коефіцієнт заповнення встановлюється на 0
  • Є близько 30 стовпців
  • Один із стовпців - це зображення типу LOB, і він зберігає файли розміром від кількох КБ до декількох сотень МБ
  • У таблиці немає гіпотетичних індексів, пов'язаних з нею

Сервер працює під керуванням 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). Це мало незначний вплив.
  • Скопіював стовпець LOB в іншу таблицю, скинув стовпчик, знову створив стовпець і скопіював дані назад (як зазначено в цій публікації: Вивільнення невикористаної таблиці простору SQL Server ). Це зменшило невикористаний простір, але, здавалося, просто перетворило його у використаний простір:

    Невикористаний простір

  • Використовував утиліту bcp, щоб експортувати таблицю, усікати її та перезавантажувати її (як зазначено в цій публікації: Як звільнити невикористаний простір для таблиці ). Це також зменшило невикористаний простір і збільшило використаний простір приблизно в такому ж розмірі, як наведене вище зображення.

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

Я не хочу робити цих спроб у виробничій БД, якщо ці результати я можу очікувати, тому:

  1. Чому після деяких із цих спроб невикористаний простір просто перетворюється на використаний простір? Я відчуваю, що я не дуже добре розумію, що відбувається під капотом.
  2. Чи можу я щось зробити, щоб зменшити невикористаний простір, не збільшуючи використаний простір?

EDIT: Ось звіт про використання диска та сценарій для таблиці:

Використання диска

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 не повертає даних. Якщо неточності виявлені та виправлені, а З 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спочатку вручну, просто залиште цей набір помилковим. Запуск 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 ║ UnusedPagesPecent
╠═════════╬════════╬════════════╬═════════════════ ╬══════════════════╬════════════════════╣
║ 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

Один із стовпців - це зображення типу LOB, і він зберігає файли розміром від кількох КБ до декількох сотень МБ

Ви можете відчути внутрішню фрагментацію.
Яка фрагментація сторінки для цієї таблиці?
І чи відрізняється фрагментація для рядка в рядках поза рядками?

Ви кажете, що у вас є файли, які є кількома КБ.
SQL Server зберігає все на 8060 сторінках байт. Значить, якщо у вас є рядок (або дані про рядки), що має 4040 байт, а наступний схожий, він не може вміститися як на одній сторінці, так і ви витратите половину місця. Спробуйте змінити розмір рядка, зберігаючи стовпці змінної довжини (почніть із зображення, наприклад) в іншій таблиці.


Я не думаю, що це фрагментація. Після відновлення індексів фрагментація кластеризованого індексу становить 0,45%, а наповненість сторінки - 98,93%.
Кен

Перебудова таблиці або індексу не допоможе, якщо ви страждаєте від дуже великих рядків або даних LOB, які добре не вкладаються на сторінки в 8 КБ. Це Макс Вернон пояснив більш докладно: "у вас багато частково порожніх сторінок". також називається внутрішньою фрагментацією
DrTrunks Bell

-3

Чи база даних перебуває в повному режимі відновлення? Якщо це так, коли ви виконуєте скорочення, він реєструє всі зміни і не зменшує їх так, як ви очікуєте. Залежно від вашої години роботи, ви можете зробити резервну копію, перейти в режим відновлення масової доставки, а потім запустити скорочення у файлі даних. Після цього вам слід запустити індексні скрипти для відновлення / відновлення та повернення до повного відновлення. Це я б спробував у будь-якому випадку, але знову ж таки, це залежить від ваших годин роботи для всього цього.


4
Цікаво підвести модель відновлення. Я думаю, це було б більш застосовним, якби у ОП були проблеми з розміром файлу журналу. На сьогоднішній день у них виникають проблеми з розміром файлу даних, тому я буду здивований, якби модель відновлення викликала описану проблему.
Джош Дарнелл

Правда, але єдиний раз, коли я запускав скорочення, і це не дуже впливало на простір, було через модель відновлення, тому я подумав, що варто підняти його, якщо це буде неправильний діагноз.
Джон-Генрі Лохбаум

-3

Єдиний раз, коли я не зміг зменшити простір БД і повернути простір, це тому, що ви не можете зменшити БД, що перевищує початковий розмір БД, коли він був створений. Наприклад, якщо ваша БД є копією виробничого БД, і ви вперше створили БД на 525 ГБ, сервер sql не дозволить зменшити розмір нижче 525 ГБ незалежно від того, скільки даних ви видалите з БД. Але якщо БД була створена нижче 383 Гб, а потім зросла до 525 ГБ, у вас не повинно виникнути проблем із поверненням місця. Я давно вважаю, що це дурне і свавільне обмеження з боку Microsoft.

Скорочуйте базу даних лише до її початкового розміру, який встановлюється після створення бази даних


Питання не в тому, щоб зменшити базу даних (а якщо вона була, можливість зменшити її, залежить від використовуваного простору після області початкового розміру)
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

відновлення таблиці, як заявляє ОП, позбулося б більшості фрагментації. Сумніваюсь, що ще одна перебудова допоможе.
Макс Вернон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.