web-dev-qa-db-ja.com

テーブルの未使用領域を解放する方法

この質問は何十回も尋ねられますが、驚いたことに、このような単純な要件は非常に難しくなります。しかし、私はこの問題を解決できません。

SQL Server 2014 Expressエディションを使用しており、データベースサイズが10 GBに制限されています(ファイルグループサイズやデータベースサイズではありません)。

ニュースをクロールし、HTMLをテーブルに挿入しました。テーブルのスキーマは次のとおりです。

Id bigint identity(1, 1) primary key,
Url varchar(250) not null,
OriginalHtml nvarchar(max),
...

データベースのサイズが不足し、insufficient disk spaceを受け取りました

もちろん、データベースとファイルグループを縮小しても効果はありませんでした。 DBCC SHRINKDATABASEは役に立ちませんでした。したがって、各レコードを読み取り、ヘッドセクションやサイドとフッターなどのOriginalHtmlの不要な部分を取り除いて本体のみを保持する単純なアプリケーションを作成しました。ディスクの使用状況のレポートを取得すると、この画像が表示されます。トップテーブル:

enter image description here

この画像を理解すると、未使用のスペースは合計サイズの50%になります。つまり、5GBの未使用スペースができました。しかし、私はそれを取り戻すことはできません。インデックスの再構築は役に立ちませんでした。 truncateonlyオプションは役に立ちません。レコードが削除されないことがわかったので、各レコードのサイズのみが縮小されます。

この時点で行き詰まっています。助けてください、どうしたらいいですか?

クラスタ化インデックスは列Idにあります。

これはEXECUTE sys.sp_spaceused @objname = N'dbo.Articles', @updateusage = 'true';の結果です

name        rows     reserved     data        index_size   unused
----------- -------- ------------ ----------- ------------ -----------
Articles    112258   8079784 KB   5199840 KB  13360 KB     2866584 KB 
8
Saeed Neamati

すべてが同じであれば、ラージオブジェクト(LOB)列OriginalHTMLを圧縮するだけで十分です。質問ではクラスター化インデックス名を指定しないので、次のようにします。

_ALTER INDEX ALL
ON dbo.Articles
REORGANIZE 
WITH (LOB_COMPACTION = ON);
_

参照 ALTER INDEX (Transact-SQL)

(クラスター化された列だけでなく)クラスター化インデックス名がある場合は、上記のALLをその名前に置き換えます。

_LOB_COMPACTION_オプションのデフォルトはONですが、明示的に指定しても害はありません。 REORGANIZEを繰り返し実行して、すべての未使用スペースの回収を完了する必要がある場合があります。

残念ながら、LOBデータの編成方法とLOB圧縮の実装方法は、何回実行しても、この方法で未使用の領域をすべて再利用できるとは限らないことを意味します。非常に遅くなることもあります。

また、関連するQ&Aでメソッドを試すこともできます 未使用のスペースSQL Serverテーブルの解放

何らかの理由で上記がうまくいかない場合は、exportデータをファイルに保存し、truncateテーブル、次にreloadテーブル。これを実現する方法はいくつかあります。たとえば bcpユーティリティ などです。

次の例では、幅が10,000行のテーブルを作成します。

_CREATE TABLE dbo.Test 
(
    c1 bigint IDENTITY NOT NULL, 
    c2 nvarchar(max) NOT NULL,

    CONSTRAINT PK_dbo_Test
        PRIMARY KEY CLUSTERED (c1)
);

-- Load 10,000 wide rows
INSERT dbo.Test WITH (TABLOCKX)
    (c2)
SELECT TOP (10000)
    REPLICATE(CONVERT(nvarchar(max), 'X'), 50000)
FROM master.sys.columns AS C1
CROSS JOIN master.sys.columns AS C2;
_

_sys.dm_db_index_physical_stats_ DMVを使用してスペース使用量を確認できます。

_SELECT
    DDIPS.index_id,
    DDIPS.partition_number,
    DDIPS.index_type_desc,
    DDIPS.index_depth,
    DDIPS.index_level,
    DDIPS.page_count,
    DDIPS.avg_page_space_used_in_percent
FROM sys.dm_db_index_physical_stats
(
    DB_ID(),
    OBJECT_ID(N'dbo.Test', N'U'),
    1,
    NULL,
    'DETAILED'
) AS DDIPS
WHERE 
    DDIPS.alloc_unit_type_desc = N'LOB_DATA';
_

DMV output

次に、LOBコンテンツをより小さいサイズに更新します(ただし、行外ストレージを必要とするものです)。

_-- Change LOB data to a smaller value (that will not move in-row)
UPDATE dbo.Test WITH (TABLOCKX)
SET c2 = REPLICATE(CONVERT(nvarchar(max), 'Y'), 5000);
_

DMV output

一部のスペースが再利用されましたが、残りのページは、実際よりもいっぱいになっていることに注意してください。

以下を使用してLOBスペースを圧縮できます。

_ALTER INDEX PK_dbo_Test ON dbo.Test 
REORGANIZE 
WITH (LOB_COMPACTION = ON);
_

DMV output

これにより、圧縮とスペースの節約になりますが、完全ではありません。圧縮を再度実行すると、状況が改善される場合と改善されない場合があります。私のテストでは、何回再実行しても、テストは行われませんでした。

エクスポート、トランケート、リロード

これをManagement Studioから完全に行う1つの方法は、_xp_cmdshell_を使用してテーブルデータをファイルにエクスポートすることです。 _xp_cmdshell_が現在有効になっていない場合は、次のようにします。

_-- Enable xp_cmdshell if necessary
EXECUTE sys.sp_configure
    @configname = 'show advanced options',
    @configvalue = 1;

RECONFIGURE;

EXECUTE sys.sp_configure
    @configname = 'xp_cmdshell',
    @configvalue = 1;

RECONFIGURE;
_

これでエクスポートを実行できます:

_-- Export table
EXECUTE sys.xp_cmdshell
    'bcp Sandpit.dbo.Test out c:\temp\Test.bcp -n -S .\SQL2017 -T';
_

パスと_-S_サーバー名を変更し、場合によってはログイン認証情報を提供する必要があることに注意してください。

テーブルをトランケートし、_BULK INSERT_を使用してリロードする方法:

_-- Truncate
TRUNCATE TABLE dbo.Test;

-- Switch to BULK_LOGGED recovery model if currently set to FULL
-- Bulk load
BULK INSERT dbo.Test
FROM 'c:\temp\Test.bcp' 
WITH 
(
    DATAFILETYPE = 'widenative', 
    ORDER (c1), 
    TABLOCK,
    KEEPIDENTITY
);
_

最後のステップは、アイデンティティシードをリセットすることです。

_-- Check and reseed identity
DBCC CHECKIDENT('dbo.Test', RESEED);
_

この一連の操作は、通常、LOB圧縮よりも高速であり、常に最適な結果を生成します。

DMV output

上記は、長期にわたるバグが原因である可能性があるため、それほど効率的ではありません。 IDENTITY列を使用したBULK INSERTは、SORTを使用したクエリプランを作成します 。そこに記載されている回避策は効果的ですが、テーブルが非常に大きい場合にのみ問題が発生します。

エクスポートされたデータを保持するために使用される一時ファイルを削除することを忘れないでください。

もちろん、最も便利な一括エクスポート/インポートアプローチを自由に使用できます。 _xp_cmdshell_またはbcpを使用する必要はありません。

その他の注意事項:

  • FILLFACTORindex pagesにのみ適用されます。行外のLOBストレージ(インデックスページに格納されない)には影響しません。
  • 行とページ compression は、行外ストレージでは使用できません。
  • 別の方法として、SQL Server 2016から利用可能な COMPRESS および DECOMPRESS 関数を使用して、データを明示的に圧縮および解凍できます。

    COMPRESSおよびDECOMPRESS組み込み関数によって提供されるのと同じ圧縮機能を取得するためにSQL Server 2014(ここではこの場合)またはそれ以前(SQL Server 2005まで)を使用する場合のオプションは、SQLCLRを使用することです。これだけを実行するビルド済みの関数は、 Solomon Rutzky によって記述された無料バージョンの SQL# で使用できます。 Util_GZipおよびUtil_GUnzip関数は、それぞれCOMPRESSおよびDECOMPRESSと同等である必要があります。また、SQL Server 2012以降を使用している場合は、SQL Serverを実行しているサーバーを.NET Frameworkバージョン4.5以降で更新し、大幅に改善された圧縮アルゴリズムが使用されるようにする必要があります。

10
Paul White 9

SQL Server Express 2016 SP1以降にアップグレードできる場合は、 DATA COMPRESSION を使用することにより、大幅にスペースを節約できます。

データベースが肥大化している他の要素があるかもしれませんが、Dan Guzmanのコメントが示唆しているように、すべてのインデックスのFILL FACTORを確認する必要があります。

0(ゼロ)または100以外の値は、インデックスが作成(または再構築)されたときに、SQL Serverが各ページをFILL FACTORのパーセンテージまでしか満たさなかったことを意味します。したがって、たとえば、FILL FACTORが50の場合、インデックスの作成/再構築中にページの50%のみが埋められ、実際にデータを保持するために必要なスペースの量は基本的に2倍になります。

投稿からクエリを取得する SQL ServerデータベースのインデックスのFILL FACTORを検索する

SQL Serverデータベース内の0または100以外のFILL FACTORを持つすべてのユーザーテーブルのすべてのインデックスを検索する場合:

SELECT DB_NAME() AS Database_Name
, sc.name AS Schema_Name
, o.name AS Table_Name
, o.type_desc
, i.name AS Index_Name
, i.type_desc AS Index_Type
, i.fill_factor
FROM sys.indexes i
INNER JOIN sys.objects o ON i.object_id = o.object_id
INNER JOIN sys.schemas sc ON o.schema_id = sc.schema_id
WHERE i.name IS NOT NULL
AND o.type = 'U'
AND i.fill_factor not in (0, 100)
ORDER BY i.fill_factor DESC, o.name

曲線因子に関連する追加の有益な情報は、次の場所にあります。

Fillfactorに関する5つのこと

ブリッツ結果:フィルファクター(%)

1
Scott Hodgin