web-dev-qa-db-ja.com

SQL Server:データファイルの最後に最も近いオブジェクトの特定

TL\DR

end -に最も近いオブジェクトをefficiently識別する方法を探していますSQL Serverデータファイル。このアプローチは、大きなデータファイルに対してもパフォーマンスを維持する必要があります。

私が今持っているもの

次のクエリは、SQL 2012に同梱されている、文書化されていない動的管理関数を利用しています: sys.dm_db_database_page_allocations ;このDMFは DBCC IND コマンド。

次のクエリは、特定のデータファイルの最後のオブジェクトを識別します(警告:ある時点でキャンセルしない限り、25 GBを超えるデータベースに対してこれを実行しないでください/):

-- Return object with highest Extent Page ID
SELECT   files.name as logical_file_name
        , files.physical_name as physical_file_name
        , OBJECT_SCHEMA_NAME(object_id) + N'.' + OBJECT_NAME(object_id) AS object_name
        , alloc.*
FROM sys.dm_db_database_page_allocations(DB_ID(), NULL, NULL, NULL, NULL) alloc
    INNER JOIN sys.database_files files
        ON alloc.extent_file_id = files.file_id
WHERE is_allocated = 1
    AND files.name = 'Logical_FileName'
ORDER BY files.name , files.physical_name, extent_page_id DESC

このアプローチの何が問題になっていますか

上記のWarningが示すように、この関数は実際には特定のオブジェクトではなく特定のオブジェクトを参照するための先の尖ったアプローチ用に設計されているため、データベースのサイズが大きくなるとこのクエリの実行速度が遅くなります問題の特定のデータファイルを確認します。私がしたようにNULLパラメータを渡すと、この関数はデータベース内のすべてのオブジェクトをバックグラウンドで反復処理し、結合された出力を吐き出します。これは私が必要とすることを実現しますが、最適化に役立たない非常にbrute-forceの方法で実現します。

私が求めていること

GAM、SGAMIAMチェーン を繰り返し処理して、特定のデータファイルの最後にあるオブジェクトをすばやく特定する方法があることを願っています。最後のオブジェクトの目的を見つけるためにページ割り当てマップをトラバースする DBCC PAGE 呼び出し、またはそのような性質を使用して、TSQLの外にこのアプローチをプッシュしてPowerShellのようなものにプッシュする必要があると思います特定のデータファイルです。 ...そして、誰かがそのコードをすでに一緒にスローしたり、これらの構造やドキュメント化されていないプロシージャの出力を私よりもよく理解していることを願っています。

なぜこれが必要なのですか?

これは多くの人が尋ねる避けられない質問なので、すぐに答えます。私が取り組んでいるプロジェクトは、レガシーシステムを高速化することですが、 ヒープテーブルの束 を統合した後(他の理由で悪夢でした)、今はデータファイル内の空き領域の束。このスペースを解放してOSに戻したいのですが、 オブジェクトを別のデータファイルに移行する従来のアプローチ は、操作するのに十分な空きスペースがないため、この段階では実行できませんシステム上(このデータファイルからより多くの領域を解放できるまで)。

ファイルの拡張を無効にして DBCC SHRINKFILE TRUNCATEONLY コマンドファイルを毎晩開いて、データファイルの終わりに向かって開いているページを解放しますが、これはmayと同じくらい頻繁に機能する、時間のかかる困難なプロセスですしません。ファイルの終わりに近いオブジェクトが何であるかを識別して、手動で再構築し、より速いタイムテーブルでスペースを解放できるようにしたいと思っています。

要約すれば

quickly特定のデータファイルの最後にあるオブジェクトの名前を特定する方法はありますか?私が現在採用している方法は私のニーズを満たしていないため、利用可能なあらゆるアプローチを使用することにオープンです。

5
John Eisbrener

これでうまくいくと思います。このコードは基本的に次のことを行います。

  1. ファイル内の最後のGAM間隔から割り当てられた最高のページIDを取得します
  2. ファイル内の最後のSGAM間隔から割り当てられた最大のページIDを取得します
  3. 2つの値を比較して最も高いページを見つける
  4. 最後に割り当てられたページから最後のObjectId(テーブル)を識別します
  5. オブジェクトとそのパーティションに定義されたインデックスを特定する
  6. ファイルの最後にある残りの空白のみをOSに解放するDBCC SHRINKFILEコマンドを提供し(即時に実行する必要があります)、実質的にDBCC SHRINKFILEと同等です TRUNCATEONLY

これは、データベース内のデータファイルのページIDを反復処理するカーソルにネストされ、私のローカライズされたテストに基づいて非常に迅速に実行されます。 IAMやPFSページなど、テーブルやインデックスで予約されていないページがデータファイルの終わりを占めているかどうかを識別する機能も追加しました。

SET NOCOUNT ON;

-- Create Temp Table to Push DBCC PAGE results into
CREATE TABLE #dbccPage_output(
      ID                INT IDENTITY(1,1)
    , [ParentObject]    VARCHAR(255)
    , [Object]          VARCHAR(255)
    , [Field]           VARCHAR(255)
    , [Value]           VARCHAR(255)
)
GO

-- Variables to hold pointer information for traversing GAM and SGAM pages
DECLARE @GAM_maxPageID INT, @SGAM_maxPageID INT, @maxPageID INT,
        @GAM_page INT, @SGAM_page INT
DECLARE @stmt VARCHAR(2000)

-- Final Output Table
DECLARE @myOutputTable TABLE
(
      LogicalFileName   VARCHAR(255)
    , ObjectID          BIGINT
    , IndexID           BIGINT
    , PartitionID       BIGINT
    , MaxPageID         BIGINT
)

-- Cursor to iterate through each file
DECLARE cursorFileIds CURSOR
FOR
        SELECT file_id, size
        FROM sys.database_files
        WHERE type = 0

-- Variable to hold fileID
DECLARE @fileID INT, @size INT, @interval INT

-- Inject the data into the cursor
OPEN cursorFileIds
FETCH NEXT FROM cursorFileIds
INTO @fileID, @size

-- Enter the While Loop.  This loop will end when the
--  end of the data injected into the cursor is reached.
WHILE @@FETCH_STATUS = 0
BEGIN
        -- Truncate table (mainly used for 2nd pass and forward)
        TRUNCATE TABLE #dbccPage_output

        -- Referenced if we need to step back a GAM/SGAM interval
        STEPBACK:

        -- # of pages in a GAM interval
        SET @interval = @size / 511232
        -- Set GAM Page to read
        SET @GAM_page = CASE @interval WHEN 0 THEN 2 ELSE @interval * 511232 END
        -- Set SGAM page to read (always the next page after the GAM)
        SET @SGAM_page = CASE @interval WHEN 0 THEN 3 ELSE (@interval * 511232) + 1 END

        -- Search Last GAM Interval page
        SET @stmt = 'DBCC PAGE(0, ' + CAST(@fileID AS VARCHAR(10)) + ', ' + CAST(@GAM_page AS VARCHAR(20)) + ', 3) WITH TABLERESULTS, NO_INFOMSGS' -- GAM on Primary Datafile
        PRINT @stmt

        INSERT INTO #dbccPage_output ([ParentObject], [Object], [Field], [Value])
        EXEC (@stmt)

        -- Get Last Allocated Page Number
        SELECT TOP 1
                @GAM_maxPageID = REVERSE(SUBSTRING(REVERSE(Field), CHARINDEX(')', REVERSE(Field)) + 1, CHARINDEX(':', REVERSE(Field)) - CHARINDEX(')', REVERSE(Field)) - 1))
        FROM #dbccPage_output
        WHERE [Value] = '    ALLOCATED'
        ORDER BY ID DESC

        -- Truncate Table
        TRUNCATE TABLE #dbccPage_output

        -- Search Last SGAM Interval page
        SET @stmt = 'DBCC PAGE(0, ' + CAST(@fileID AS VARCHAR(10)) + ', ' + CAST(@SGAM_page AS VARCHAR(20)) + ', 3) WITH TABLERESULTS, NO_INFOMSGS' -- SGAM on Primary Datafile
        PRINT @stmt

        INSERT INTO #dbccPage_output ([ParentObject], [Object], [Field], [Value])
        EXEC (@stmt)

        -- Get Last Allocated Page Number
        SELECT TOP 1
                @SGAM_maxPageID = REVERSE(SUBSTRING(REVERSE(Field), CHARINDEX(')', REVERSE(Field)) + 1, CHARINDEX(':', REVERSE(Field)) - CHARINDEX(')', REVERSE(Field)) - 1))
        FROM #dbccPage_output
        WHERE [Value] = '    ALLOCATED'
        ORDER BY ID DESC

        -- Get highest page value between SGAM and GAM
        SELECT @maxPageID = MAX(t.value)
        FROM (VALUES (@GAM_maxPageID), (@SGAM_maxPageID)) t(value)

        TRUNCATE TABLE #dbccPage_output

        -- Check if GAM or SGAM is last allocated page in the chain, if so, step back one interval
        IF(@maxPageID IN (@GAM_page, @SGAM_page))
        BEGIN
            SET @size = ABS(@size - 511232)
            GOTO STEPBACK
        END

        -- Search Highest Page Number of Data File
        SET @stmt = 'DBCC PAGE(0, ' + CAST(@fileID AS VARCHAR(10)) + ', ' + CAST(CASE WHEN @maxPageID = @SGAM_maxPageID THEN @maxPageID + 7 ELSE @maxPageID END AS VARCHAR(50)) + ', 1) WITH TABLERESULTS, NO_INFOMSGS' -- Page ID of Last Allocated Object
        PRINT @stmt

        INSERT INTO #dbccPage_output ([ParentObject], [Object], [Field], [Value])
        EXEC (@stmt)

        -- Capture Object Name of DataFile
        INSERT INTO @myOutputTable
        SELECT (SELECT name FROM sys.database_files WHERE file_id = @fileID) AS LogicalFileName
            , CASE WHEN (SELECT [Value] FROM #dbccPage_output WHERE Field = 'm_type') IN ('1', '2') THEN -- If page type is data or index
                        CAST((SELECT [Value] FROM #dbccPage_output WHERE Field = 'Metadata: ObjectId') AS BIGINT)
                   ELSE CAST((SELECT [Value] FROM #dbccPage_output WHERE Field = 'm_type') AS BIGINT)
              END AS ObjectID
            , NULLIF(CAST((SELECT [Value] FROM #dbccPage_output WHERE Field = 'Metadata: IndexId') AS BIGINT), -1) AS IndexID
            , NULLIF(CAST((SELECT [Value] FROM #dbccPage_output WHERE Field = 'Metadata: PartitionId') AS BIGINT), 0) AS PartitionID
            , @maxPageID + 7 AS MaxPageID

        -- Reset Max Page Values
        SELECT @GAM_maxPageID = 0, @SGAM_maxPageID = 0, @maxPageID = 0

     -- Traverse the Data in the cursor
     FETCH NEXT FROM cursorFileIds
     INTO @fileID, @size
END

-- Close and deallocate the cursor because you've finished traversing all it's data
CLOSE cursorFileIds
DEALLOCATE cursorFileIds

-- page type values pt. 1: https://www.sqlskills.com/blogs/paul/inside-the-storage-engine-using-dbcc-page-and-dbcc-ind-to-find-out-if-page-splits-ever-roll-back/
-- page type values pt. 2: https://www.sqlskills.com/blogs/paul/inside-the-storage-engine-anatomy-of-a-page/
-- ObjectIDs of either 0 or 99: https://www.sqlskills.com/blogs/paul/finding-table-name-page-id/
-- Output Object Closest to the End
SELECT  t.LogicalFileName
    ,   CAST(CASE WHEN t.IndexID IS NULL THEN 
                CASE t.ObjectID
                    WHEN 0 THEN  '>>No MetaData Found<<'  -- This isn't m_type, rather ObjectID
                    WHEN 1 THEN  '>>Data Page<<'
                    WHEN 2 THEN  '>>Index Page<<'
                    WHEN 3 THEN  '>>Text Mix Page<<'
                    WHEN 4 THEN  '>>Text Tree Page<<'
                    WHEN 6 THEN  '>>DCM Page<<<'
                    WHEN 7 THEN  '>>Sort Page<<'
                    WHEN 8 THEN  '>>GAM Page<<'
                    WHEN 9 THEN  '>>SGAM Page<<'
                    WHEN 10 THEN '>>IAM Page<<'
                    WHEN 11 THEN '>>PFS Page<<'
                    WHEN 13 THEN '>>Boot Page<<'
                    WHEN 15 THEN '>>File Header Page<<'
                    WHEN 16 THEN '>>Diff Map Page<<'
                    WHEN 17 THEN '>>ML Map Page<<'
                    WHEN 18 THEN '>>Deallocated DBCC CHECKDB Repair Page<<'
                    WHEN 19 THEN '>>Temporary ALTER INDEX Page<<'
                    WHEN 20 THEN '>>Pre-Allocated BULK LOAD Page<<'
                    WHEN 99 THEN '>>Possible Page Corruption/Run DBCC CHECKDB<<'  -- This isn't m_type, rather ObjectID
                    ELSE CAST(t.ObjectID AS VARCHAR(50))
                END
            ELSE QUOTENAME(OBJECT_SCHEMA_NAME(t.ObjectID)) + '.' + QUOTENAME(OBJECT_NAME(t.ObjectID)) END AS VARCHAR(250)) AS TableName
    ,   QUOTENAME(i.name) AS IndexName
    ,   p.partition_number AS PartitionNumber
    ,   'DBCC SHRINKFILE(' + t.LogicalFileName + ', ' + CAST(CEILING((t.MaxPageID + 8) * 0.0078125) AS VARCHAR(50)) + ')' AS ShrinkCommand_Explicit
    ,   'DBCC SHRINKFILE(' + t.LogicalFileName + ', TRUNCATEONLY)' AS ShrinkCommand_TRUNCATEONLY
FROM @myOutputTable t
    LEFT JOIN sys.indexes i
        ON t.ObjectID = i.object_id
        AND t.IndexID = i.index_id
    LEFT JOIN sys.partitions p
        ON t.ObjectID = p.object_id
        AND t.PartitionID = p.partition_id

-- Cleanup
DROP TABLE #dbccPage_output
GO
3
John Eisbrener

次のコードは、最大のページ番号から最小のページ番号まで、各データベースページをチェックして、割り当てられているかどうかを確認します。最初に割り当てられたページが見つかると、そのページに関連付けられているオブジェクトが表示されます。最後に割り当てられたページが実際のオブジェクトを参照していない可能性があるため、動作が保証されていません。ただし、動作するはずですほとんどの場合

SET NOCOUNT ON;
IF OBJECT_ID(N'tempdb..#dbcrep', N'U') IS NOT NULL
DROP TABLE #dbcrep;
CREATE TABLE #dbcrep
(
        ParentObject VARCHAR(128)
        , [Object] VARCHAR(128)
        , [Field] VARCHAR(128)
        , VALUE VARCHAR(2000)
);
DECLARE @cmd nvarchar(max);
DECLARE @PageNum int;
DECLARE @PageCount int;
DECLARE @FileID int;
DECLARE @Status varchar(2000);

SET @FileID = 1;

SET @PageCount = (
    SELECT df.size
    FROM sys.database_files df
    WHERE df.file_id = @FileID
    );
SET @PageNum = @PageCount - 1;
WHILE @PageNum > 0
BEGIN
    SET @cmd = N'DBCC PAGE (''' + DB_NAME() + N''', ' + CONVERT(nvarchar(20), @FileID) + N', ' + CONVERT(nvarchar(20), @PageNum) + N', 0) WITH TABLERESULTS, NO_INFOMSGS;';
    DELETE FROM #dbcrep;
    INSERT INTO #dbcrep (ParentObject, [Object], [Field], [VALUE])
    EXEC sys.sp_executesql @cmd;
    SELECT @Status = VALUE
    FROM #dbcrep
    WHERE ParentObject = 'PAGE HEADER:'
        AND Object = 'Allocation Status'
        AND Field LIKE 'GAM %';
    SET @PageNum -= 1;
    PRINT @Status;
    IF @Status <> 'NOT ALLOCATED' BREAK
END

SELECT ObjectName = s.name + N'.' + o.name
    , d.*
FROM #dbcrep d
    LEFT JOIN sys.all_objects o ON d.VALUE = o.object_id
    LEFT JOIN sys.schemas s ON o.schema_id = s.schema_id
WHERE ParentObject = 'PAGE HEADER:'
    AND Object = 'Page @0x00000001BA28E000'
    AND Field = 'Metadata: ObjectId';

現在のデータベースで指定されたfile_idに割り当てられたページ数を取得し、ループを使用してDBCC PAGEで各ページを検査し、その出力を一時テーブルに保存します。次に、一時テーブルをsys.all_objectsに結合して、ページが割り当てられているオブジェクトの名前を取得します。

私のテストリグでは、次の結果が表示されます。

╔════════════════════╦══════════════╦═════════════ ═════════════╦════════════════════╦════════════╗
║ObjectName║ParentObject║Object║Field║VALUE║
╠════════════════════╬═════════ ═════╬══════════════════════════╬═════════════════ ═══╬════════════╣
║dbo.EmptyDatabases║ページヘッダー:║ページ@ 0x00000001BA28E000║メタデータ:ObjectId║1938105945║
╚═══ ═════════════════╩══════════════╩═════════════════ ═════════╩════════════════════╩════════════╝

#dbcrep tempテーブルには、次の詳細が含まれています。

╔══════════════╦══════════════════════════╦═══════ ════════════════════════╦════════════════════╗
║ParentObject║Object║Field║VALUE║
╠══════════════╬═══════════════════ ═══════╬═══════════════════════════════╬══════════ ══════════╣
║バッファ:║BUF @ 0x0000000200E95B80║bページ║0x00000001BA28E000║
║バッファ::BUF @ 0x0000000200E95B80║bhash║0x0000000000000000║
 ║バッファ:║BUF @ 0x0000000200E95B80║bpageno║(1:42743)║
║バッファ:║BUF @ 0x0000000200E95B80║bdbid║7║
║バッファ:║BUF @ 0x0000000200E95B80║breference 
║バッファ:║BUF @ 0x0000000200E95B80║bcputicks║0║
║バッファ:║BUF @ 0x0000000200E95B80║bsampleCount ║0║
║バッファ:║BUF @ 0x0000000200E95B80║bUse1║10982║
║バッファ:BUF BUF @ 0x0000000200E95B80║bstat║0x9║
║バッファ:║BUF @ 0Bx00000000ブログ║0x2121215a║
║バッファ:║BUF @ 0x0000000200E95B80║bnext║0x0000000000000000║
║ページヘッダー:║ページ@ 0x00000001BA28E000║m_pageId║(1:42743)║[.____。ヘッダー:║ページ@ 0x00000001BA28E000║m_headerVersion║1║
║ページヘッダー:║ページ@ 0x00000001BA28E000║m_type║20║
║ページヘッダー:║ページ@ 0x00000001BA28E000║m_typeFlagBits║m_typeFlagBits 。]║ページヘッダー::ページ@ 0x00000001BA28E000║m_level║0║
║ページヘッダー:║ページ@ 0 x00000001BA28E000║m_flagBits║0x204║[.____。 256║
║PAGE HEADER:║ページ@ 0x00000001BA28E000║メタデータ:AllocUnitId║72057594052804608║
║PAGE HEADER:║ページ@ 0x00000001BA28E000║メタデータ:PartitionId║72057594043301888║
 :║ページ@ 0x00000001BA28E000║メタデータ:IndexId║1║
║ページヘッダー:║ページ@ 0x00000001BA28E000║メタデータ:ObjectId║1938105945║
║PAGE HEADER:║ページ@ 0x00000001BA28E000║m_prev :0)║
║ページヘッダー:║ページ@ 0x00000001BA28E000║m_nextPage║(0:0)║
║ページヘッダー:║ページ@ 0x00000001BA28E000║pminlen║8 ║
║ページヘッダー:║ページ@ 0x00000001BA28E000║m_slotCnt║0║
║ページヘッダー:║ページ@ 0x00000001BA28E000║m_freeCnt║8096║
║PAGE HEADER:║ページ@ E000 ║m_freeData║96║
║PAGE HEADER:║ページ@ 0x00000001BA28E000║m_reservedCnt║0║
║PAGE HEADER:║ページ@ 0x00000001BA28E000║m_lsn║(321:6718:151)║
║PAGE HEADER:║ページ@ 0x00000001BA28E000║m_xactReserved║0║
║PAGE HEADER:║ページ@ 0x00000001BA28E000║m_xdesId║(0:0)║
║PAGE HEADER:║ページ@ 0x00000001 ║m_ghostRecCnt║0║
║PAGE HEADER:║ページ@ 0x00000001BA28E000║m_tornBits║1253867700║
║PAGE HEADER:║ページ@ 0x00000001BA28E000║DB F rag ID║1║
║PAGE HEADER:║割り当てステータス║GAM(1:2)║ALLOCATED║
║PAGE HEADER:║割り当てステータス║SGAM(1:3)║割り当てられていない║ 
║ページヘッダー:║割り当てステータス║PFS(1:40440)║0x0 0_PCT_FULL║
║ページヘッダー:║割り当てステータス║DIFF(1:6)║変更なし║
║ページヘッダー:║割り当てステータス║ML(1:7)║NOT MIN_LOGGED║
╚══════════════╩══════════ ════════════════╩═══════════════════════════════╩═ ═══════════════════╝
2
Max Vernon