セットアップ
〜115,382,254行の巨大なテーブルがあります。テーブルは比較的単純で、アプリケーションプロセスの操作を記録します。
CREATE TABLE [data].[OperationData](
[SourceDeciveID] [bigint] NOT NULL,
[FileSource] [nvarchar](256) NOT NULL,
[Size] [bigint] NULL,
[Begin] [datetime2](7) NULL,
[End] [datetime2](7) NOT NULL,
[Date] AS (isnull(CONVERT([date],[End]),CONVERT([date],'19000101',(112)))) PERSISTED NOT NULL,
[DataSetCount] [bigint] NULL,
[Result] [int] NULL,
[Error] [nvarchar](max) NULL,
[Status] [int] NULL,
CONSTRAINT [PK_OperationData] PRIMARY KEY CLUSTERED
(
[SourceDeviceID] ASC,
[FileSource] ASC,
[End] ASC
))
CREATE TABLE [model].[SourceDevice](
[ID] [bigint] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NULL,
CONSTRAINT [PK_DataLogger] PRIMARY KEY CLUSTERED
(
[ID] ASC
))
ALTER TABLE [data].[OperationData] WITH CHECK ADD CONSTRAINT [FK_OperationData_SourceDevice] FOREIGN KEY([SourceDeviceID])
REFERENCES [model].[SourceDevice] ([ID])
テーブルは、約500クラスターで、毎日ベースでクラスター化されます。
また、テーブルはPKによって適切にインデックスが作成され、統計は最新であり、INDEXerは毎晩デフラグされます。
インデックスベースのSELECTは非常に高速で、問題はありませんでした。
問題
最後の(TOP)行を知り、[End]
で分割する必要があり[SourceDeciveID]
ます。[OperationData]
すべてのソースデバイスの最後を取得します。
質問
私はこれを良い方法で、DBを限界まで引き上げずに解決する方法を見つける必要があります。
努力1
最初の試みは明白であるGROUP BY
かSELECT OVER PARTITION BY
クエリでした。ここでの問題も明白です。すべてのクエリは、非常にパーティションの順序をスキャンする必要があります。そのため、クエリは非常に遅く、IOへの影響が非常に大きくなります。
クエリ例1
;WITH cte AS
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY [SourceDeciveID] ORDER BY [End] DESC) AS rn
FROM [data].[OperationData]
)
SELECT *
FROM cte
WHERE rn = 1
クエリ2の例
SELECT *
FROM [data].[OperationData] AS d
CROSS APPLY
(
SELECT TOP 1 *
FROM [data].[OperationData]
WHERE [SourceDeciveID] = d.[SourceDeciveID]
ORDER BY [End] DESC
) AS ds
失敗しました!
努力2
TOP行への参照を常に保持するヘルプテーブルを作成しました。
CREATE TABLE [data].[LastOperationData](
[SourceDeciveID] [bigint] NOT NULL,
[FileSource] [nvarchar](256) NOT NULL,
[End] [datetime2](7) NOT NULL,
CONSTRAINT [PK_LastOperationData] PRIMARY KEY CLUSTERED
(
[SourceDeciveID] ASC
)
ALTER TABLE [data].[LastOperationData] WITH CHECK ADD CONSTRAINT [FK_LastOperationData_OperationData] FOREIGN KEY([SourceDeciveID], [FileSource], [End])
REFERENCES [data].[OperationData] ([SourceDeciveID], [FileSource], [End])
テーブルを埋めるために、より高い[End]
列が挿入された場合に常にソース行を追加/更新するトリガーを作成しました。
CREATE TRIGGER [data].[OperationData_Last]
ON [data].[OperationData]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
MERGE [data].[LastOperationData] AS [target]
USING (SELECT [SourceDeciveID], [FileSource], [End] FROM inserted) AS [source] ([SourceDeciveID], [FileSource], [End])
ON ([target].[SourceDeciveID] = [FileSource].[SourceDeciveID])
WHEN MATCHED AND [target].[End] < [source].[End] THEN
UPDATE SET [target].[FileSource] = source.[FileSource], [target].[End] = source.[End]
WHEN NOT MATCHED THEN
INSERT ([SourceDeciveID], [FileSource], [End])
VALUES (source.[SourceDeciveID], source.[FileSource], source.[End]);
END
ここでの問題は、IOへの影響も非常に大きく、その理由がわかりません。
クエリプランで確認できるように、[OperationData]
テーブル全体に対してスキャンも実行します。
失敗しました!
CREATE TABLE
スクリプトにそれを含めませんが、クエリプラン内にパーティションが表示されます。質問を編集します。
PRIMARY KEY CLUSTERED
あなたの中に含まれているのでそれが役立つかもしれないと思うので、余分なインデックスではありませんか?
SELECT [SourceID], [Source], [End] FROM inserted
いくつかの方法でテーブルスキャンを実行します[OperationData]
。