Настроить
У меня есть огромная таблица ~ 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]
из каждого исходного устройства.
Вопрос
Мне нужно найти способ решить эту проблему хорошим способом, не доводя БД до предела.
Усилие 1
Первая попытка была очевидна GROUP BY
или SELECT OVER PARTITION BY
запрос. Проблема здесь также очевидна, каждый запрос должен сканировать по порядку самого раздела / находить верхнюю строку. Таким образом, запрос очень медленный и имеет очень большое влияние ввода-вывода.
Пример запроса 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]
.Ответы:
Если у вас есть таблица
SourceID
значений и индекс главной таблицы(SourceID, End) include (othercolumns)
, просто используйтеOUTER APPLY
.Если вы знаете, что вы только после вашего нового раздела, вы можете включить фильтр на конец, как
AND d.[End] > DATEADD(day, -1, GETDATE())
Изменить: так как ваш кластерный индекс
SourceID, Source, End)
включен, поместите источник в свою таблицу источников и присоединиться к нему также. Тогда вам не нужен новый индекс.источник
Source
таблица, ссылающаяся наsourceID
колонку. Источник столбца - это только имя файла. Это немного сбивает с толку имен. Для каждогоSource
устройства (sourceID) может быть только одна запись для одного файлаsource
(столбца) в одной временной отметке. Также я не могу удалить разделы, потому что самые новыеEnd
фрагментированы. Вот почему я придумал триггерное решение. Я думаю, что живой запрос здесь не будет работать.