web-dev-qa-db-ja.com

毎日の動的テーブル分割

AcksLogsの2つのテーブルを含むSQL Serverデータベースがあります。

これら2つのテーブルは、論理的には関連していますが、リレーショナルデータベースの方法では関連していません。基本的に、受信したすべてのメッセージはLogテーブルに保存され、サーバーがそれを確認すると、そのackはAckテーブルに保存されます。

1日に約500万のACKと300万のログを保存しています。これらの2つのテーブルを毎日の境界でパーティション分割して、古いパーティションをテーブルから簡単に削除し、クエリのパフォーマンスを向上させようとしています。

これまでテーブルの分割を行ったことがないので、オンラインチュートリアルをいくつか読んでいましたが、1つのことにこだわっています。私が従ったすべてのチュートリアルは、手動でファイルグループを追加し、境界を手動で追加しているようです。

SQL Serverに何らかの方法でこれを毎日実行させたいのですが、これが私の質問です。翌日、たとえば22:00の新しいファイルグループを作成するために必要です。次に、24:00に挿入が新しい日のパーティションを埋め始めます。

これを達成する方法について誰かが私を正しい方向に向けることができますか?包括的なチュートリアルまたはいくつかの古き良きアドバイスのいずれかも同様に行います。

2番目の質問:どういうわけか、同じパーティション関数を2つの異なるテーブルに適用できますか?

どちらにもdatetime(2)列があり、そこにパーティションを設定します。同じルールが適用されます。

それが私のファイルグループにどのように適合するのでしょうか?その日に単一のファイルグループが必要ですか?各テーブルはそのファイルグループにファイルを持っていますか、それとも両方のテーブルがファイルグループの同じファイルに保存されますか?

.mdfおよび.ldf各ファイルグループ?または、データベース全体のログファイルが1つ残っていますか?

4
Zapnologica

SQL Server 2008 SP2およびSQL Server 2008 R2 SP1から 15,000パーティション のサポートがあるため、正直に言うと、それほど動的に実行する必要はありません。失敗する可能性のある複雑な毎日のプロセス(ファイルグループと境界を動的に追加する)を用意する代わりに、今から2020年までにパーティションを事前に作成するだけで、制限の範囲内にあり、将来性がかなり高まります。

couldすべてのパーティションを1つのファイルグループに割り当てるか(必ずしも優れたパターンとは限りません)、または限られた数の間でラウンドロビンします。別の言い方をすれば、1日あたりのファイルグループを使用するなどの技術的な必要はありません。

-- Assign all to one filegroup; ok not necessarily great
CREATE PARTITION SCHEME ps_test AS PARTITION pf_test ALL TO ( [FG1] )

-- Or round-robin
CREATE PARTITION SCHEME ps_test AS PARTITION pf_test ALL TO ( [FG1], [FG2], [FG3], [FG4], [FG5], [FG6], [FG7], [FG1] ... etc )

明らかに、Excelまたは何らかのツールを使用してスクリプトを生成します-スクリプトを入力する必要はありません:)

DMV sys.partition_range_values およびメタデータ関数 $ PARTITION を使用して、どのデータがどこにあるかに関する情報を計算します。最も古いパーティションを切り替えて切り捨てる毎日のジョブを作成します。私はこれを毎日の追加よりもリスクが低いと考えています。

警告!!これを有効にする必要があり、このアプローチにはいくつかの問題があるため、ホワイトペーパーを注意深く読んでください(例:テーブルの非整列インデックスの作成と再構築1,000を超えるパーティションはサポートされません)。リスクを嫌う場合でも、1,000パーティションという標準の制限では、3年弱で事前に割り当てることができます。

本当にDATETIME2ではなくDATEでパーティション分割したいので、計算列を検討してください。私はおそらくこれを最初に性能テストしたいと思うでしょう。

Codeplex( SQL Server Partition Management )にもツールがありますが、これは使用していませんが一見の価値があります。

他の質問に答えるには、データベースのログファイルは1つだけにしてください。 .mdfではなく.ndfとして他のファイルを追加します。同じテーブルで同じパーティションスキーム(機能ではない)を使用するには、同じスキームで単純に作成し、ファイルグループの下のファイルにデータを分割します。

CREATE TABLE dbo.yourTable (    ...

    CONSTRAINT PK_yourTable PRIMARY KEY ( rowId, someDate )

    ) ON ps_test(someDate)

OK、これは長い答えになりますが、私はこのようなものがどのように機能するかについてのデモを打ち上げました。パーティションの切り替えの優れた点は、メタデータのみの操作としてすぐに実行できることです。明確にするために、これは、原則といくつかの「ハウツー」コードの例を示すデモであり、製品品質ではありません。開発環境またはテスト環境で実行する前に、それを確認し、理解しておいてください。約200MBのスペースが必要です。

------------------------------------------------------------------------------------------------
-- Setup START
-- Demo runs on my laptop in < 1 minute (ok on SSD)
-- You'll need 200MB space
------------------------------------------------------------------------------------------------

USE master
GO

IF EXISTS ( SELECT * FROM sys.databases WHERE name = 'tooManyPartitionsTest' )
    ALTER DATABASE tooManyPartitionsTest SET SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
IF EXISTS ( SELECT * FROM sys.databases WHERE name = 'tooManyPartitionsTest' )
    DROP DATABASE tooManyPartitionsTest 
GO
CREATE DATABASE tooManyPartitionsTest
GO

ALTER DATABASE tooManyPartitionsTest SET RECOVERY SIMPLE
GO

-- Add 7 filegroups with 4 files each
-- Add 365 files and filegroup
DECLARE @fg INT = 0, @f INT = 0, @sql NVARCHAR(MAX)

WHILE @fg < 7
BEGIN

    SET @fg += 1
    SET @sql = 'ALTER DATABASE tooManyPartitionsTest ADD FILEGROUP tooManyPartitionsTestFg' + CAST( @fg AS VARCHAR(5) )

    -- Add the filegroup
    PRINT @sql
    EXEC(@sql)


    -- Initialise
    SET @f = 0

    WHILE @f < 4
    BEGIN

        SET @f += 1
        --!!WARNING!! DON'T USE THESE SETTINGS IN PRODUCTION.  3MB starting size and 1MB filegrowth are just for demo - would be extremely painful for live data
        SET @sql = 'ALTER DATABASE tooManyPartitionsTest ADD FILE ( NAME = N''tooManyPartitionsTestFile@f_@fg'', FILENAME = N''s:\temp\tooManyPartitionsTestFile@[email protected]'', SIZE = 3MB, FILEGROWTH = 1MB ) TO FILEGROUP [tooManyPartitionsTestFg@fg]'
        SET @sql = REPLACE ( @sql, '@fg', @fg )
        SET @sql = REPLACE ( @sql, '@f', @f )

        -- Add the file
        PRINT @sql
        EXEC(@sql)

    END

END
GO


USE tooManyPartitionsTest
GO

SELECT * FROM sys.filegroups
SELECT * FROM sys.database_files 
GO

-- Generate partition function with ~3 years worth of daily partitions from 1 Jan 2014.
DECLARE @bigString NVARCHAR(MAX) = ''

;WITH cte AS (
SELECT CAST( '30 Apr 2014' AS DATE ) testDate
UNION ALL
SELECT DATEADD( day, 1, testDate )
FROM cte
WHERE testDate < '31 Dec 2016'
)
SELECT @bigString += ',' + QUOTENAME( CONVERT ( VARCHAR, testDate, 106 ), '''' )
FROM cte
OPTION ( MAXRECURSION 1100 )

SELECT @bigString = 'CREATE PARTITION FUNCTION pf_test (DATE) AS RANGE RIGHT FOR VALUES ( ' + STUFF( @bigString, 1, 1, '' ) + ' )'
SELECT @bigString bs

-- Create the partition function
PRINT @bigString
EXEC ( @bigString )
GO

/*
-- Look at the boundaries
SELECT *
FROM sys.partition_range_values
WHERE function_id = ( SELECT function_id FROM sys.partition_functions WHERE name = 'pf_test' )
GO
*/

DECLARE @bigString NVARCHAR(MAX) = ''

;WITH cte AS (
SELECT ROW_NUMBER() OVER( ORDER BY boundary_id ) rn
FROM sys.partition_range_values
WHERE function_id = ( SELECT function_id FROM sys.partition_functions WHERE name = 'pf_test' )
UNION ALL 
SELECT 1    -- additional row required for fg
)
SELECT @bigString += ',' + '[tooManyPartitionsTestFg' + CAST( ( rn % 7 ) + 1 AS VARCHAR(5) ) + ']'
FROM cte
OPTION ( MAXRECURSION 1100 )

SELECT @bigString = 'CREATE PARTITION SCHEME ps_test AS PARTITION pf_test TO ( ' + STUFF( @bigString, 1, 1, '' ) + ' )'
PRINT @bigString
EXEC ( @bigString )
GO




IF OBJECT_ID('dbo.yourLog') IS NULL
CREATE TABLE dbo.yourLog ( 
    logId       INT IDENTITY,
    someDate    DATETIME2 NOT NULL,
    someData    UNIQUEIDENTIFIER DEFAULT NEWID(),
    dateAdded   DATETIME DEFAULT GETDATE(), 
    addedBy     VARCHAR(30) DEFAULT SUSER_NAME(), 

    -- Computed column for partitioning?
    partitionDate AS CAST( someDate AS DATE ) PERSISTED,

    CONSTRAINT pk_yourLog PRIMARY KEY ( logId, partitionDate )  -- << !!TODO try other way round

    ) ON [ps_test]( partitionDate )
GO


IF OBJECT_ID('dbo.yourAcks') IS NULL
CREATE TABLE dbo.yourAcks ( 
    ackId           INT IDENTITY(100000,1),
    logId           INT NOT NULL,
    partitionDate   DATE NOT NULL

    CONSTRAINT pk_yourAcks PRIMARY KEY ( ackId, logId, partitionDate )  

    ) ON [ps_test]( partitionDate )
GO



IF OBJECT_ID('dbo.yourLogSwitch') IS NULL
CREATE TABLE dbo.yourLogSwitch ( 
    logId       INT IDENTITY,
    someDate    DATETIME2 NOT NULL,
    someData    UNIQUEIDENTIFIER DEFAULT NEWID(),
    dateAdded   DATETIME DEFAULT GETDATE(), 
    addedBy     VARCHAR(30) DEFAULT SUSER_NAME(), 

    -- Computed column for partitioning?
    partitionDate AS CAST( someDate AS DATE ) PERSISTED,

    CONSTRAINT pk_yourLogSwitch PRIMARY KEY ( logId, partitionDate )

    ) ON [ps_test]( partitionDate )
GO
-- Setup END
------------------------------------------------------------------------------------------------



------------------------------------------------------------------------------------------------
-- Data START
------------------------------------------------------------------------------------------------

-- OK load up data for Jan 2014 to today.
DECLARE @startDate DATETIME = '1 Jan 2014', @Rand INT 

WHILE @startDate < GETDATE()
BEGIN

    -- Add between 1 and 10,000 rows to dbo.yourLog for today
    SET @Rand = Rand() * 10000

    ;WITH cte AS (
    SELECT TOP 10000 ROW_NUMBER() OVER ( ORDER BY ( SELECT 1 ) ) rn
    FROM master.sys.columns c1
        CROSS JOIN master.sys.columns c2
        CROSS JOIN master.sys.columns c3
    )
    INSERT INTO dbo.yourLog (someDate)
    SELECT TOP(@Rand) DATEADD( second, rn % 30000, @startDate )
    FROM cte

    -- Add most of the Acks
    INSERT INTO dbo.yourAcks ( logId, partitionDate )
    SELECT TOP 70 PERCENT logId, partitionDate
    FROM dbo.yourLog
    WHERE partitionDate = @startDate

    SET @startDate = DATEADD( day, 1, @startDate )

    CHECKPOINT

END
GO

-- Have a look at the data we've loaded
SELECT 'before yourLog' s, COUNT(*) records, MIN(someDate) minDate, MAX(someDate) maxDate FROM dbo.yourLog 
SELECT 'before yourAcks' s, COUNT(*) records, MIN(partitionDate) minDate, MAX(partitionDate) maxDate FROM dbo.yourAcks

-- You'll see how pre-May data is initially clumped together
SELECT 'before $partition' s, $PARTITION.pf_test( partitionDate ) p, MIN(partitionDate) xMinDate, MAX(partitionDate) xMaxDate, COUNT(*) AS records
FROM dbo.yourLog WITH(NOLOCK) 
GROUP BY $PARTITION.pf_test( partitionDate ) 
ORDER BY xMinDate
GO

-- Data END
------------------------------------------------------------------------------------------------


------------------------------------------------------------------------------------------------
-- Maintenance START
------------------------------------------------------------------------------------------------

-- Oh man, we're behind with our switching and truncation.
-- Create a job that sweeps up.  Do we get blocking?

-- ALTER TABLE dbo.yourLog SWITCH PARTITION 1 TO dbo.yourLogSwitch PARTITION 1
-- TRUNCATE TABLE dbo.yourLogSwitch

-- Let's pretend we only want to maintain up to 30 days ago
DECLARE @testDate DATE
SET @testDate = DATEADD( day, -30, GETDATE() )

-- Create local fast_forward ( forward-only, read-only ) cursor 
DECLARE partitions_cursor CURSOR FAST_FORWARD LOCAL FOR 
SELECT boundary_id, CAST( value AS DATE )
FROM sys.partition_range_values
WHERE function_id = ( SELECT function_id FROM sys.partition_functions WHERE name = 'pf_test' )
  AND value < @testDate

-- Cursor variables
DECLARE @boundary_id INT, @value DATE, @sql NVARCHAR(MAX)

OPEN partitions_cursor

FETCH NEXT FROM partitions_cursor INTO @boundary_id, @value
WHILE @@fetch_status = 0
BEGIN

    -- Switch out and truncate old partition
    SET @sql = 'ALTER TABLE dbo.yourLog SWITCH PARTITION ' + CAST( @boundary_id AS VARCHAR(5) ) + ' TO dbo.yourLogSwitch PARTITION ' + CAST( @boundary_id AS VARCHAR(5) )

    PRINT @sql
    EXEC(@sql)

    -- You could move the data elsewhere from here or just empty it out
    TRUNCATE TABLE dbo.yourLogSwitch

    --!!TODO yourAcks table

    FETCH NEXT FROM partitions_cursor INTO @boundary_id, @value
END

CLOSE partitions_cursor
DEALLOCATE partitions_cursor
GO

-- Maintenance END
------------------------------------------------------------------------------------------------



-- Have a look at the data we've maintained
SELECT 'after yourLog' s, COUNT(*) records, MIN(someDate) minDate, MAX(someDate) maxDate FROM dbo.yourLog 
SELECT 'after yourAcks' s, COUNT(*) records, MIN(partitionDate) minDate, MAX(partitionDate) maxDate FROM dbo.yourAcks

-- You'll see how pre-May data is initially clumped together
SELECT 'after $partition' s, $PARTITION.pf_test( partitionDate ) p, MIN(partitionDate) xMinDate, MAX(partitionDate) xMaxDate, COUNT(*) AS records
FROM dbo.yourLog WITH(NOLOCK) 
GROUP BY $PARTITION.pf_test( partitionDate ) 
ORDER BY xMinDate



-- Remember, date must always be part of query now to get partition elimination
SELECT *
FROM dbo.yourLog
WHERE partitionDate = '1 August 2014'
GO


-- Cleanup
USE master
GO

IF EXISTS ( SELECT * FROM sys.databases WHERE name = 'tooManyPartitionsTest' )
    ALTER DATABASE tooManyPartitionsTest SET SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
IF EXISTS ( SELECT * FROM sys.databases WHERE name = 'tooManyPartitionsTest' )
    DROP DATABASE tooManyPartitionsTest 
GO
6
wBob

新しいパーティションを追加するには、SPLIT RANGEを使用します。次のパーティションがあると仮定します。

CREATE PARTITION FUNCTION pfTest(int) AS RANGE LEFT FOR VALUES (10, 20, 30);
CREATE PARTITION SCHEME psTest AS PARTITION pfTest TO ([GRP1], [GRP2], [GRP3]);

..(30から無限大)から(30-39)および(40から無限大)の最後の範囲を「分割」することにより、新しいパーティションを追加できます。構文は次のとおりです。

ALTER PARTITION FUNCTION pfTest() SPLIT RANGE (40);
ALTER PARTITION SCHEME psTest NEXT USED [GRP4];

動的SQLを生成して、それをSQL Serverエージェントジョブなどのスケジュールに従って実行する以外に、これを自動的に行う方法が他にありません。

パーティション関数は、必要な数のテーブルに適用できます。各テーブルは、パーティションスキームに配置できます。パーティションスキームは、パーティション関数に接続されます。

--- Create the partition function:
CREATE PARTITION FUNCTION fn_part_left(int)
AS RANGE LEFT FOR VALUES (100, 110, 120, 130);

--- Create the partition scheme:
CREATE PARTITION SCHEME ps_part_left AS
PARTITION fn_part_left TO
    ([GROUP_A], [GROUP_B], [GROUP_C], [GROUP_A], [GROUP_B]);

--- Create the table
CREATE TABLE myTable (
    someColumn int NOT NULL,
    ....
) ON [ps_part_left](someColumn);

私の例ではデータ型として「int」を使用しましたが、datetime2も同様に機能します。

必要に応じて、同じファイルグループに複数のパーティションを配置できます。ここでは、負荷が異なるパーティション間でどのように分散されるかについて、いくつかの計画を立てる必要があります。そのため、すべてのI/O負荷を単一のファイルグループに配置しないでください。

filegroupには、1つ以上の.mdfファイルを含めることができます。

データベースには、1つ以上の.ldfファイルを含めることができます。

3

それで、これは私がしばらくの間実験しているものです。これが、テーブルで動的スクリプトを作成する際のショットです...

-- DROP TABLE #VT_TEMP;

DECLARE @CURRENTDATE AS DATETIME;
DECLARE @DATEADD AS INTEGER;
DECLARE @ENDDATE DATETIME2;

SET @DATEADD = 1;
SET @ENDDATE = '21070702'; -- NEW END DATE 

-- GET CURRENT BOUNDARIES 
SELECT ROW_NUMBER() OVER(ORDER BY DATEKEY) AS ROW_RANK
       , *
INTO #VT_TEMP
FROM STARDW.DBO.DATEDIM
WHERE DATEKEY >= 20150101 -- there is a 1007 month beginning from 2015 to     2107
AND DAYOFMONTH = 1
ORDER BY 1

;
WITH MYCTE ( R_RANK, TXT)
AS (

SELECT ROW_RANK 
        , CAST('CREATE PARTITION FUNCTION HZPFUN_DATEDIM (DATETIME) AS RANGE RIGHT FOR VALUES ( ' + '''' + convert(VARCHAR(10), DATEVALUE, 120) + '''' AS VARCHAR(MAX))

FROM #VT_TEMP
WHERE ROW_RANK = 1

UNION ALL

SELECT V.ROW_RANK 
        , CAST(TXT + ', ' + '''' +convert(VARCHAR(10), DATEVALUE, 120) + '''' AS VARCHAR(MAX))

FROM #VT_TEMP V INNER JOIN MYCTE C
ON V.ROW_RANK = C.R_RANK+1
WHERE C.R_RANK < 2000

) 
SELECT *
FROM MYCTE M INNER JOIN ( SELECT MAX(R_RANK) AS MXR_RANK
                          FROM MYCTE
                        ) MX
ON M.R_RANK = MX.MXR_RANK
option (maxrecursion 2000)

;

SELECT 'CREATE PARTITION SCHEME HZPSE_DATEDIM AS PARTITION HZPFUN_DATEDIM     TO (PRIMARY);'



CREATE TABLE [dbo].[OPPEProviderSummaryFact](
    [OPPEProviderSummaryFactKey] [bigint] NOT NULL,
    [ID] [varchar](50) NOT NULL,
    [IDType] [varchar](50) NOT NULL,
    [ProvId] [varchar](50) NOT NULL,
    [NPI] [varchar](20) NULL,
    [ProviderName] [varchar](50) NULL,
    [DateKey] [bigint] NOT NULL,
    [FiscalReportYrMth] [varchar](20) NOT NULL,
    [ReportMonth] [datetime] NOT NULL,
    [CostCenterKey] [bigint] NULL,
    [DepartmentKey] [bigint] NULL,
    [DepartmentName] [varchar](100) NULL,
    [RevLocId] [bigint] NULL,
    [RowId] [int] NULL,
    [EncounterProfileKey] [bigint] NULL,
    [EncounterCategoryName] [varchar](100) NULL,
    [EncounterTypeCd] [varchar](20) NULL,
    [TotalEncountersCnt] [int] NULL,
    [DiagnsosisKey] [bigint] NULL,
    [DiagnosisId] [int] NULL,
    [DiagnosisName] [varchar](100) NULL,
    [TotalDiagnosisCnt] [int] NULL,
    [PrimaryLOSProcedureId] [int] NULL,
    [ProcedureName] [varchar](100) NULL,
    [TotalLOSCPTCnt] [int] NULL,
    [MedicationKey] [bigint] NULL,
    [MedicationId] [int] NULL,
    [GenericMedicationName] [varchar](100) NULL,
    [TotalMedicationCnt] [int] NULL,
    [OrderClassCd] [varchar](20) NULL,
    [OrderName] [varchar](100) NULL,
    [TotalProceduresCnt] [int] NULL,
    [InsertDt] [datetime] NULL,
    [StartDt] [datetime] NOT NULL,
    [EndDate] [datetime] NULL,
    [isCurrent] [char](1) NULL,
    [HashedCheckField] [nvarchar](50) NULL
CONSTRAINT HPPK_OPPEProviderSummaryKey PRIMARY KEY (id,ProvId,StartDt)
) ON HZPSE_DATEDIM (StartDt)
 ;

注:まだ完全にはテストしていませんが、近いと思います。

1
Timothy Ultican