525 GBのサイズの本番データベースにテーブルがあり、そのうち383 GBは未使用です。
この領域の一部を再利用したいのですが、本番DBをいじる前に、データが少ないテストDBの同じテーブルでいくつかの戦略をテストしています。このテーブルにも同様の問題があります。
テーブルに関するいくつかの情報:
サーバーは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 CLEANTABLE('myDB', 'dbo.myTable')
を実行しても違いはありませんでしたこれらが期待できる結果である場合、本番DBでこれらの試みを行いたくないので、次のようにします。
編集:テーブルのディスク使用量レポートとスクリプトは次のとおりです:
_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
_
Max Vernonの回答のコマンドを実行した結果は次のとおりです。
_╔════════════╦═══════════╦════════════╦═════════════════╦══════════════════════╦════════════════════╗
║ TotalBytes ║ FreeBytes ║ TotalPages ║ TotalEmptyPages ║ PageBytesFreePercent ║ UnusedPagesPercent ║
╠════════════╬═══════════╬════════════╬═════════════════╬══════════════════════╬════════════════════╣
║ 9014280192║ 8653594624║ 1100376║ 997178 ║ 95.998700 ║ 90.621500 ║
╚════════════╩═══════════╩════════════╩═════════════════╩══════════════════════╩════════════════════╝
_
_╔═════════════╦═══════════════════╦════════════════════╗
║ ObjectName ║ ReservedPageCount ║ UsedPageCount ║
╠═════════════╬═══════════════════╬════════════════════╣
║ dbo.MyTable ║ 5109090 ║ 2850245 ║
╚═════════════╩═══════════════════╩════════════════════╝
_
UPDATE:
Max Vernonの提案に従って、以下を実行しました。
_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によって追跡されたディスク使用量が実際のディスク使用量と大幅に同期しなくなったことが問題であると思われます。この問題は解決したと考えますが、そもそもなぜこれが起こったのか知りたいです。
最初のステップとして、テーブルに対して 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
からの出力と比較すると正確なようです。
空きバイトの割合が空きページの割合よりも大幅に高い場合、部分的に空のページが多数あります。
部分的に空のページは、次のようないくつかの原因から発生する可能性があります。
ページ分割。クラスター化インデックスへの新しい挿入に対応するためにページを分割する必要があります。
列のサイズが原因で、ページを列で埋めることができない。
クエリは、ドキュメント化されていない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║ ╠═════════╬════════╬════════════╬═══ ══════════════╬══════════════════╬════════════════ ════╣ ║208║96║26║12║46.153800║46.153800║ ╚═════════╩════════╩══ ══════════╩═════════════════╩══════════════════╩ ═══════════════════╝
私は、関数 here について説明するブログ投稿を書きました。
あなたのシナリオでは、ALTER TABLE ... REBUILD
を実行したので、TotalEmptyPages
の数値は非常に低いはずですが、BytesFreePercent
には約72%が残っていると思います。 。
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
次のクエリは、テーブルに割り当てられたページごとに1行を表示し、同じドキュメント化されていない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
テスト環境で実際のテーブルに対して実行すると、出力には行のlotが表示されますが、問題の場所を確認できる場合があります。
次のスクリプトを実行して、結果を質問に投稿できますか?私たちは同じページにいることを確認しようとしています。
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;
列の1つはタイプimageのLOBで、数KBから数百MBのサイズのファイルを格納します
内部の断片化が発生している可能性があります。
このテーブルの ページの断片化 とは何ですか?
そして、行内の断片化は行外ページと異なりますか?
数KBのファイルがあるとします。
SQL Serverはすべてを8060バイトのページに格納します。つまり、4040バイトの行(または行外データ)があり、次の行が類似している場合、同じページに両方を収めることはできず、スペースの半分が無駄になります。可変長の列(たとえば、画像から開始)を別のテーブルに格納して、行サイズを変更してみてください。