Configuração
Eu tenho uma tabela enorme de ~ 115.382.254 linhas. A tabela é relativamente simples e registra as operações do processo de aplicativo.
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])
A tabela está agrupada em cerca de 500 clusters e diariamente.
Além disso, a tabela é bem indexada pelo PK, as estatísticas estão atualizadas e o INDEXer é exibido diariamente todas as noites.
Os SELECTs baseados em índices são extremamente rápidos e não tivemos nenhum problema com isso.
Problema
Preciso conhecer a última linha (TOP) [End]
e particionada por [SourceDeciveID]
. Para obter o último [OperationData]
de todos os dispositivos de origem.
Questão
Preciso encontrar uma maneira de resolver isso de uma maneira boa e sem levar o DB ao limite.
Esforço 1
A primeira tentativa foi óbvia GROUP BY
ou SELECT OVER PARTITION BY
consulta. O problema aqui também é óbvio: todas as consultas precisam varrer muito a ordem das partições / encontrar a linha superior. Portanto, a consulta é muito lenta e tem um impacto de IO muito alto.
Consulta de exemplo 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
Consulta de exemplo 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
FALHOU!
Esforço 2
Criei uma tabela de ajuda para manter sempre uma referência à linha 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])
Para preencher a tabela, criou um gatilho para sempre adicionar / atualizar a linha de origem se uma [End]
coluna superior for inserida.
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
O problema aqui é que ele também tem um enorme impacto de IO e não sei por que.
Como você pode ver aqui no plano de consulta, ele também executa uma verificação em toda a [OperationData]
tabela.
Ele tem um enorme impacto geral no meu banco de dados.
FALHOU!
fonte
CREATE TABLE
script, mas dentro do plano de consulta você verá as partições. Vou editar a pergunta.PRIMARY KEY CLUSTERED
você acha que pode ajudar?SELECT [SourceID], [Source], [End] FROM inserted
alguns de como fazer uma varredura de tabela no[OperationData]
.Respostas:
Se você possui uma tabela de
SourceID
valores e um índice em sua tabela principal(SourceID, End) include (othercolumns)
, basta usarOUTER APPLY
.Se você souber que está apenas após a sua partição mais recente, poderá incluir um filtro no End, como
AND d.[End] > DATEADD(day, -1, GETDATE())
Editar: como o índice em cluster está ativado
SourceID, Source, End)
, coloque o código-fonte na tabela de fontes também e participe também. Então você não precisa do novo índice.fonte
Source
tabela referenciando asourceID
coluna. A fonte da coluna é apenas um nome de arquivo. É um nome um pouco confuso. Para cadaSource
dispositivo (sourceID), pode haver apenas uma única entrada para um arquivosource
(coluna) em um registro de data e hora. Também não posso eliminar a partição porque o mais novoEnd
é amplamente fragmentado. É por isso que eu vim com a solução de gatilho. Acho que uma consulta ao vivo não funcionará aqui.