現在、データベースをSQL Server 2008(SP4)からSQL Server 2017(CU3)に移行しています。重要な変更は、移行後にすべてのファイルグループに2つのデータファイルがあることです。このタスクを実行するには、バックアップを復元し、2つの同じサイズのファイルと同じ自動拡張設定を含む新しいファイルグループを追加し、次の構文を使用してデータを転送します。
CREATE UNIQUE CLUSTERED INDEX <PK of the table> ..... WITH (DROP_EXISTING = ON ,...) ON <new Filegroup>
残念ながら、いくつかのLOBも移動する必要があります。そうすると、状況が少し複雑になります。
このテクニックはキンバリートリップによって説明されています ここ そしてブラッドホフに戻ります。
しばらくお待ちいただき、ありがとうございます。
このようにインデックスを再構築することにより、ファイルグループに必要な空き容量はどれくらいですか?
例を挙げましょう:
'def'ファイルグループがいっぱいであるため、データベース 'abc'のオブジェクト 'xyz'。 'xyz_pk'にスペースを割り当てることができませんでした。不要なファイルを削除するか、ファイルグループにオブジェクトを削除するか、ファイルグループにファイルを追加するか、ファイルグループ内の既存のファイルの自動拡張をオンに設定して、ディスク領域を作成します。
ただし、各ファイルのサイズは220 GBで、空き領域の50%各ファイル内。
提案された 診断クエリ( ファイルサイズは合計で227,22GBになります。
今のところ、とんでもない大量の空き領域を取り除くためにDBCC SHRINKFILE
を実行する他の救済策はわかりません。しかし、それは私が特に誇りに思っていることではありません...それは潜在的に腐敗などを残すのに時間がかかります。
SQL Serverが多くの空き領域を割り当て、その後2つのファイルを比例的に埋める理由を理解するのに役立ちますか?
後でデモを準備しようと思います...申し訳ありませんが、今は時間が足りず、専門家の中にはその理由をすでに知っている人もいるかもしれません。
よろしくお願いします
マーティン
まず第一に、私があなたのコメントを軌道に乗せるのを手伝ってくれてありがとう。
私は今、例を試し、何が起こっているのかをよりよく理解しています。
この問題は、LOBデータ(VARCHAR(MAX)、XMLなど)を別のファイルグループに移動するときに発生します。別のファイルグループでクラスター化インデックスを再構築すると、LOBデータは以前の場所(CREATETABLEステートメントのTEXTIMAGE ON
コマンドで設定)にとどまります。
LOBデータを移動する古典的な方法の1つは、新しいファイルグループに同じ構造の2番目のテーブルを作成し、データをコピーして、古いテーブルを削除し、新しいテーブルの名前を変更することです。ただし、これにより、データの損失、データの無効化(チェック制約が欠落しているため)、エラー処理など、あらゆる種類の問題が発生する可能性があります。私は過去に1つのテーブルに対してこれを実行しましたが、IMHOは拡張性が低く、100個のテーブルを転送する必要があるという悪夢を考慮しており、テーブル15、33、88、および99を修正するためのエラーが発生しました。
したがって、パーティショニングに関してよく知られているトリックを使用します。 Kimberly Tripp LOBで説明されているように、パーティショニングを配置すると、データは新しいファイルグループに移動します。長期的にはパーティションを使用する予定はありませんが、そのLOBを移動するためのヘルパーとして使用するので、パーティションスキームは非常に鈍いです(すべてのパーティションを1つのファイルグループにスローします)。データがどのパーティションにあるかは気にしません。私は彼らを動かしたいだけなので。実際、この手法と実装は私自身が発明したものではありません...私は Mark White による手ごわいスクリプトを使用しています。私の間違いは、このスクリプトが何をするのか、そしてその意味が何であるのかを完全に理解していないことでした。
LOB-Dataの場合、テーブル(主にクラスター化されたインデックス)を2回再構築(または再作成)する必要があります。1回目はパーティションを配置し、2回目はパーティションを削除します。 SORT_IN_TEMPDB=ON
を使用するかどうかに関係なく、元のデータのスペースを2回提供する必要があります。元のテーブルに100MBがある場合、操作を成功させるには200MBを提供する必要があります。最初はかなり戸惑いましたが、操作が終了した後、新しいデータファイルに多くの空き領域ができてしまいました。
今、私は空きスペースを避けてごまかすことはできないことを受け入れました。ただし、後でファイルを縮小する必要はありません。したがって、私の解決策は、一時ファイルグループで最初の再構築を実行し、宛先ファイルグループで2番目の再構築(パーティショニングの削除)を実行することです。一時ファイルグループは後で削除できます(うまくいけば、「ファイルグループを削除できません」というエラーメッセージが表示されない場合(私の質問を見てください ここ )。
読んでくれてありがとう
マーティン
これが私の問題の再現スクリプトであり、私が思いついた解決策が含まれています。
/*============================================================================
Adapted the following file published by sqlskills to demonstrate filegrowth
after partitioning for StackOverflow Question.
Martin Guth, 02.02.2018
File: MovingLOBData.sql
Summary: Because 2012 supports online index rebuilds - even with LOB.
You might think this means you can move LOB data around
(one of the VERY cool things you can do with IN_ROW data to
actually move it). However, the behavior of LOB data is NOT
necessarily intuitive. This script will show you how/why/what!
SQL Server Versions: SQL Server 2012
------------------------------------------------------------------------------
Written by SQLskills.com
(c) SQLskills.com. All rights reserved.
For more scripts and sample code, check out
http://www.SQLskills.com
You may alter this code for your own *non-commercial* purposes. You may
republish altered code as long as you include this copyright and give due
credit, but you must obtain prior permission before blogging this code.
THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
============================================================================*/
SET NOCOUNT ON
go
USE master
go
CREATE DATABASE [TestLOB]
CONTAINMENT = NONE
ON PRIMARY
( NAME = N'TestLOBPrimary'
, FILENAME = N'U:\DB_DATA\TestLOBPrimary.mdf'
, SIZE = 100MB , FILEGROWTH = 1024KB ),
FILEGROUP [FG1]
( NAME = N'FG1File1'
, FILENAME = N'U:\DB_DATA\FG1File1.ndf'
, SIZE = 40MB , FILEGROWTH = 20480KB ),
( NAME = N'FG1File2'
, FILENAME = N'U:\DB_DATA\FG1File2.ndf'
, SIZE = 40MB , FILEGROWTH = 20480KB ),
FILEGROUP [FG2]
( NAME = N'FG2File1'
, FILENAME = N'U:\DB_DATA\FG2File1.ndf'
, SIZE = 20MB , FILEGROWTH = 0MB ),
( NAME = N'FG2File2'
, FILENAME = N'U:\DB_DATA\FG2File2.ndf'
, SIZE = 20MB , FILEGROWTH = 0MB ),
FILEGROUP [tempLOB]
( NAME = N'tempLOB1'
, FILENAME = N'U:\DB_DATA\templob1.ndf'
, SIZE = 20MB , FILEGROWTH = 0MB ),
( NAME = N'tempLOB2'
, FILENAME = N'U:\DB_DATA\templob2.ndf'
, SIZE = 20MB , FILEGROWTH = 0MB )
LOG ON
( NAME = N'TestLOBLog'
, FILENAME = N'U:\DB_DATA\TestLOBLog.ldf'
, SIZE = 10MB , FILEGROWTH = 10MB)
GO
USE TestLOB
go
ALTER DATABASE TestLOB
MODIFY FILEGROUP FG1 DEFAULT
go
--DROP TABLE TestLobTable;
CREATE TABLE dbo.TestLobTable
(
c1 int identity,
c2 char(8000) default 'this is a test',
c3 varchar(max) NULL
) -- will be created on FG1
go
INSERT INTO dbo.TestLobTable
(
c2,
c3
)
VALUES
(
'this is a test',
REPLICATE (convert(varchar(max), 'ABC'), 8000)
)
go 1000
CREATE UNIQUE CLUSTERED INDEX TestLobTableCL
ON TestLobTable (c1)
go
sp_blitzIndex
@databaseName = 'TestLOB',
@schemaName = 'dbo',
@tableName = 'TestLobTable'
go
-- size is roughly 40 MB: 1,000 rows; 39.2MB; 31.3MB LOB
SELECT
f.name AS [filename],
fu.total_page_count AS [pageCount],
fu.total_page_count/128.0 [sizeMBTotal],
(fu.total_page_count - unallocated_extent_page_count) /128.0 [sizeMBUsed],
fu.*
FROM sys.dm_db_file_space_usage fu
INNER JOIN sys.database_files f ON fu.file_id = f.file_id
INNER JOIN sys.filegroups fg ON fu.filegroup_id = fg.data_space_id
go
/*
filename pageCount sizeMBTotal sizeMBUsed
TestLOBPrimary 12800 100.000000 2.875000
FG1File1 5120 40.000000 19.687500
FG1File2 5120 40.000000 19.687500
FG2File1 2560 20.000000 0.062500
FG2File2 2560 20.000000 0.062500
--> 2*19,687 MB are occupied in Filegroup 1 ---> approx 40 MB in total in Filegroup 1
*/
/* moving Lob data using partitioning trick */
CREATE PARTITION FUNCTION PF_TestLobTable (int)
AS RANGE RIGHT FOR VALUES (0)
go
CREATE PARTITION SCHEME PS_TestLobTable
AS PARTITION PF_TestLobTable
TO ( fg2, fg2 )
go
CREATE UNIQUE CLUSTERED INDEX TestLobTableCL
ON TestLobTable (c1)
WITH (DROP_EXISTING = ON, SORT_IN_TEMPDB= OFF)
ON PS_TestLobTable (c1)
go
SELECT
f.name AS [filename],
fu.total_page_count AS [pageCount],
fu.total_page_count/128.0 [sizeMBTotal],
(fu.total_page_count - unallocated_extent_page_count) /128.0 [sizeMBUsed],
fu.*
FROM sys.dm_db_file_space_usage fu
INNER JOIN sys.database_files f ON fu.file_id = f.file_id
INNER JOIN sys.filegroups fg ON fu.filegroup_id = fg.data_space_id
go
/*
filename pageCount sizeMBTotal sizeMBUsed
TestLOBPrimary 12800 100.000000 3.062500
FG1File1 5120 40.000000 0.062500
FG1File2 5120 40.000000 0.062500
FG2File1 2688 21.000000 19.750000
FG2File2 2688 21.000000 19.687500
--> now Filegroup 2 has roughly 40 MB data...interestingly the create index would fail if having 2*20MB capacity available but would pass at 2*21MB
*/
-- try to recreate the index again to get rid of partitioning
CREATE UNIQUE CLUSTERED INDEX TestLobTableCL
ON TestLobTable (c1)
WITH (DROP_EXISTING = ON, SORT_IN_TEMPDB= OFF)
ON [FG2]
go
/* error message 1105
Could not allocate space for object 'dbo.TestLobTable'.'TestLobTableCL' in database 'TestLOB' because the 'FG2' filegroup is full. Create disk space by deleting unneeded files, dropping objects in the filegroup, adding additional files to the filegroup, or setting autogrowth on for existing files in the filegroup.
--> makes sense because no free space available in filegroup with partitioned clustered index already present
*/
ALTER DATABASE TestLOB MODIFY FILE (NAME = N'FG2File1', SIZE=41MB);
ALTER DATABASE TestLOB MODIFY FILE (NAME = N'FG2File2', SIZE=41MB);
-- rebuild again without sort in tempdb
CREATE UNIQUE CLUSTERED INDEX TestLobTableCL
ON TestLobTable (c1)
WITH (DROP_EXISTING = ON, SORT_IN_TEMPDB= OFF)
ON [FG2]
go
SELECT
f.name AS [filename],
fu.total_page_count AS [pageCount],
fu.total_page_count/128.0 [sizeMBTotal],
(fu.total_page_count - unallocated_extent_page_count) /128.0 [sizeMBUsed],
fu.*
FROM sys.dm_db_file_space_usage fu
INNER JOIN sys.database_files f ON fu.file_id = f.file_id
INNER JOIN sys.filegroups fg ON fu.filegroup_id = fg.data_space_id
go
/*
filename pageCount sizeMBTotal sizeMBUsed
TestLOBPrimary 12800 100.000000 3.062500
FG1File1 5120 40.000000 0.062500
FG1File2 5120 40.000000 0.062500
FG2File1 5248 41.000000 19.750000
FG2File2 5248 41.000000 19.625000
--> now the files of FG2 have 50% free space left
*/
-- try to shrink with truncateonly
DBCC SHRINKFILE('FG2File1', TRUNCATEONLY);
DBCC SHRINKFILE('FG2File2', TRUNCATEONLY);
SELECT
f.name AS [filename],
fu.total_page_count AS [pageCount],
fu.total_page_count/128.0 [sizeMBTotal],
(fu.total_page_count - unallocated_extent_page_count) /128.0 [sizeMBUsed],
fu.*
FROM sys.dm_db_file_space_usage fu
INNER JOIN sys.database_files f ON fu.file_id = f.file_id
INNER JOIN sys.filegroups fg ON fu.filegroup_id = fg.data_space_id
go
/*
filename pageCount sizeMBTotal sizeMBUsed
TestLOBPrimary 12800 100.000000 3.062500
FG1File1 5120 40.000000 0.062500
FG1File2 5120 40.000000 0.062500
FG2File1 5048 39.437500 19.750000
FG2File2 5040 39.375000 19.625000
--> no significant effect...still almost 50% free space
*/
-- recreate the table
DROP PARTITION SCHEME PS_TestLobTable;
DROP PARTITION FUNCTION PF_TestLobTable;
DROP TABLE TestLobTable;
CREATE TABLE dbo.TestLobTable
(
c1 int identity,
c2 char(8000) default 'this is a test',
c3 varchar(max) NULL
) -- will be created on FG1
go
INSERT INTO dbo.TestLobTable
(
c2,
c3
)
VALUES
(
'this is a test',
REPLICATE (convert(varchar(max), 'ABC'), 8000)
)
go 1000
CREATE UNIQUE CLUSTERED INDEX TestLobTableCL
ON TestLobTable (c1)
go
sp_blitzIndex
@databaseName = 'TestLOB',
@schemaName = 'dbo',
@tableName = 'TestLobTable'
GO
SELECT
f.name AS [filename],
fu.total_page_count AS [pageCount],
fu.total_page_count/128.0 [sizeMBTotal],
(fu.total_page_count - unallocated_extent_page_count) /128.0 [sizeMBUsed],
fu.*
FROM sys.dm_db_file_space_usage fu
INNER JOIN sys.database_files f ON fu.file_id = f.file_id
INNER JOIN sys.filegroups fg ON fu.filegroup_id = fg.data_space_id
go
/*
filename pageCount sizeMBTotal sizeMBUsed
TestLOBPrimary 12800 100.000000 3.125000
FG1File1 5120 40.000000 19.687500
FG1File2 5120 40.000000 19.687500
FG2File1 5048 39.437500 0.062500
FG2File2 5040 39.375000 0.062500
--> data on filegroup 1 again... move them to filegroup2 this time with SORT_IN_TEMPDB
*/
/* moving Lob data using partitioning trick */
CREATE PARTITION FUNCTION PF_TestLobTable (int)
AS RANGE RIGHT FOR VALUES (0)
go
CREATE PARTITION SCHEME PS_TestLobTable
AS PARTITION PF_TestLobTable
TO ( fg2, fg2 )
go
CREATE UNIQUE CLUSTERED INDEX TestLobTableCL
ON TestLobTable (c1)
WITH (DROP_EXISTING = ON, SORT_IN_TEMPDB = ON )
ON PS_TestLobTable (c1)
go
-- removing partitioning on table
CREATE UNIQUE CLUSTERED INDEX TestLobTableCL
ON TestLobTable (c1)
WITH (DROP_EXISTING = ON, SORT_IN_TEMPDB = ON )
ON [fg2]
go
-- now try to shrink with truncateonly
DBCC SHRINKFILE('FG2File1', 20,TRUNCATEONLY);
DBCC SHRINKFILE('FG2File2', 20,TRUNCATEONLY);
SELECT
f.name AS [filename],
fu.total_page_count AS [pageCount],
fu.total_page_count/128.0 [sizeMBTotal],
(fu.total_page_count - unallocated_extent_page_count) /128.0 [sizeMBUsed],
fu.*
FROM sys.dm_db_file_space_usage fu
INNER JOIN sys.database_files f ON fu.file_id = f.file_id
INNER JOIN sys.filegroups fg ON fu.filegroup_id = fg.data_space_id
go
/*
filename pageCount sizeMBTotal sizeMBUsed
TestLOBPrimary 12800 100.000000 3.062500
FG1File1 5120 40.000000 0.062500
FG1File2 5120 40.000000 0.062500
FG2File1 5376 42.000000 19.687500
FG2File2 5376 42.000000 19.687500
--> no significant effect...still almost 50% free space
*/
--- retry with separate filegroup
CREATE DATABASE [TestLOB]
CONTAINMENT = NONE
ON PRIMARY
( NAME = N'TestLOBPrimary'
, FILENAME = N'U:\DB_DATA\TestLOBPrimary.mdf'
, SIZE = 100MB , FILEGROWTH = 1024KB ),
FILEGROUP [FG1]
( NAME = N'FG1File1'
, FILENAME = N'U:\DB_DATA\FG1File1.ndf'
, SIZE = 40MB , FILEGROWTH = 20480KB ),
( NAME = N'FG1File2'
, FILENAME = N'U:\DB_DATA\FG1File2.ndf'
, SIZE = 40MB , FILEGROWTH = 20480KB ),
FILEGROUP [FG2]
( NAME = N'FG2File1'
, FILENAME = N'U:\DB_DATA\FG2File1.ndf'
, SIZE = 20MB , FILEGROWTH = 0MB ),
( NAME = N'FG2File2'
, FILENAME = N'U:\DB_DATA\FG2File2.ndf'
, SIZE = 20MB , FILEGROWTH = 0MB ),
FILEGROUP [tempLOB]
( NAME = N'tempLOB1'
, FILENAME = N'U:\DB_DATA\templob1.ndf'
, SIZE = 20MB , FILEGROWTH = 0MB ),
( NAME = N'tempLOB2'
, FILENAME = N'U:\DB_DATA\templob2.ndf'
, SIZE = 20MB , FILEGROWTH = 0MB )
LOG ON
( NAME = N'TestLOBLog'
, FILENAME = N'U:\DB_DATA\TestLOBLog.ldf'
, SIZE = 10MB , FILEGROWTH = 10MB)
GO
USE TestLOB
go
ALTER DATABASE TestLOB
MODIFY FILEGROUP FG1 DEFAULT
go
--DROP TABLE TestLobTable;
CREATE TABLE dbo.TestLobTable
(
c1 int identity,
c2 char(8000) default 'this is a test',
c3 varchar(max) NULL
) -- will be created on FG1
go
INSERT INTO dbo.TestLobTable
(
c2,
c3
)
VALUES
(
'this is a test',
REPLICATE (convert(varchar(max), 'ABC'), 8000)
)
go 1000
CREATE UNIQUE CLUSTERED INDEX TestLobTableCL
ON TestLobTable (c1)
go
sp_blitzIndex
@databaseName = 'TestLOB',
@schemaName = 'dbo',
@tableName = 'TestLobTable'
go
-- size is roughly 40 MB: 1,000 rows; 39.2MB; 31.3MB LOB
SELECT
f.name AS [filename],
fu.total_page_count AS [pageCount],
fu.total_page_count/128.0 [sizeMBTotal],
(fu.total_page_count - unallocated_extent_page_count) /128.0 [sizeMBUsed],
fu.*
FROM sys.dm_db_file_space_usage fu
INNER JOIN sys.database_files f ON fu.file_id = f.file_id
INNER JOIN sys.filegroups fg ON fu.filegroup_id = fg.data_space_id
go
/*
filename pageCount sizeMBTotal sizeMBUsed
TestLOBPrimary 12800 100.000000 2.875000
FG1File1 5120 40.000000 19.687500
FG1File2 5120 40.000000 19.687500
FG2File1 2560 20.000000 0.062500
FG2File2 2560 20.000000 0.062500
--> 2*19,687 MB are occupied in Filegroup 1 ---> approx 40 MB in total in Filegroup 1
*/
/* moving Lob data using partitioning trick */
CREATE PARTITION FUNCTION PF_TestLobTable (int)
AS RANGE RIGHT FOR VALUES (0)
go
CREATE PARTITION SCHEME PS_TestLobTable
AS PARTITION PF_TestLobTable
TO ( tempLOB, tempLOB )
go
CREATE UNIQUE CLUSTERED INDEX TestLobTableCL
ON TestLobTable (c1)
WITH (DROP_EXISTING = ON)
ON PS_TestLobTable (c1)
go
SELECT
f.name AS [filename],
fu.total_page_count AS [pageCount],
fu.total_page_count/128.0 [sizeMBTotal],
(fu.total_page_count - unallocated_extent_page_count) /128.0 [sizeMBUsed],
fu.*
FROM sys.dm_db_file_space_usage fu
INNER JOIN sys.database_files f ON fu.file_id = f.file_id
INNER JOIN sys.filegroups fg ON fu.filegroup_id = fg.data_space_id
go
/*
filename pageCount sizeMBTotal sizeMBUsed
TestLOBPrimary 12800 100.000000 3.062500
FG1File1 5120 40.000000 0.062500
FG1File2 5120 40.000000 0.062500
FG2File1 2560 20.000000 0.062500
FG2File2 2560 20.000000 0.062500
tempLOB1 2560 20.000000 19.687500
tempLOB2 2560 20.000000 19.687500
--> 2*19,687 MB are occupied in Filegroup 1 ---> approx 40 MB in total in Filegroup 1
*/
CREATE UNIQUE CLUSTERED INDEX TestLobTableCL
ON TestLobTable (c1)
WITH (DROP_EXISTING = ON)
ON [FG2]
go
SELECT
f.name AS [filename],
fu.total_page_count AS [pageCount],
fu.total_page_count/128.0 [sizeMBTotal],
(fu.total_page_count - unallocated_extent_page_count) /128.0 [sizeMBUsed],
fu.*
FROM sys.dm_db_file_space_usage fu
INNER JOIN sys.database_files f ON fu.file_id = f.file_id
INNER JOIN sys.filegroups fg ON fu.filegroup_id = fg.data_space_id
go
/*
filename pageCount sizeMBTotal sizeMBUsed
TestLOBPrimary 12800 100.000000 3.062500
FG1File1 5120 40.000000 0.062500
FG1File2 5120 40.000000 0.062500
FG2File1 2560 20.000000 19.625000
FG2File2 2560 20.000000 19.750000
tempLOB1 2560 20.000000 0.062500
tempLOB2 2560 20.000000 0.062500
--> data successfully moved to fg2, tempLOB empty
*/
DROP PARTITION SCHEME PS_TestLobTable
DROP PARTITION FUNCTION PF_TestLobTable
ALTER DATABASE TestLOB REMOVE FILE tempLOB1;
ALTER DATABASE TestLOB REMOVE FILE tempLOB2;
ALTER DATABASE TestLOB REMOVE FILEGROUP tempLOB;
/*
summary:
- Moving LOB Data with the help of partitioning results in twice the space needed of the original data for temporary rebuilds.
- To avoid problematic and long running shrinking of database files it's best to use a different filegroup for the first rebuild as this can be easily removed afterwards.
*/