Sql Server Производительность удаления и слияния - PullRequest
7 голосов
/ 02 октября 2011

У меня есть таблица, которая содержит некоторые данные о покупке / продаже, в которых содержится около 8 миллионов записей:

CREATE TABLE [dbo].[Transactions](
[id] [int] IDENTITY(1,1) NOT NULL,
[itemId] [bigint] NOT NULL,
[dt] [datetime] NOT NULL,
[count] [int] NOT NULL,
[price] [float] NOT NULL,
[platform] [char](1) NOT NULL
) ON [PRIMARY]

Каждые X минут моя программа получает новые транзакции для каждого itemId, и мне нужно обновить его. Мое первое решение - это два шага DELETE + INSERT:

delete from Transactions where platform=@platform and itemid=@itemid
insert into Transactions (platform,itemid,dt,count,price) values (@platform,@itemid,@dt,@count,@price)
[...]
insert into Transactions (platform,itemid,dt,count,price) values (@platform,@itemid,@dt,@count,@price)

Проблема в том, что этот оператор DELETE занимает в среднем 5 секунд. Это слишком долго.

Второе решение, которое я нашел, это использовать MERGE. Я создал такую ​​хранимую процедуру, которая принимает табличный параметр:

CREATE PROCEDURE [dbo].[sp_updateTransactions]
@Table dbo.tp_Transactions readonly,
@itemId bigint,
@platform char(1)
AS
BEGIN
MERGE Transactions AS TARGET
USING @Table AS SOURCE  
ON (    
TARGET.[itemId] = SOURCE.[itemId] AND
TARGET.[platform] = SOURCE.[platform] AND 
TARGET.[dt] = SOURCE.[dt] AND 
TARGET.[count] = SOURCE.[count] AND
TARGET.[price] = SOURCE.[price] ) 


WHEN NOT MATCHED BY TARGET THEN 
INSERT VALUES (SOURCE.[itemId], 
                SOURCE.[dt],
                SOURCE.[count],
                SOURCE.[price],
                SOURCE.[platform])

WHEN NOT MATCHED BY SOURCE AND TARGET.[itemId] = @itemId AND TARGET.[platform] = @platform THEN 
DELETE;

END

Эта процедура занимает около 7 секунд с таблицей с записями 70 КБ. Так что с 8М это, вероятно, займет несколько минут. Узким местом является «Когда не сопоставлено» - когда я комментировал эту строку, эта процедура выполняется в среднем 0,01 секунды.

Итак, вопрос в том, как улучшить производительность оператора delete?

Удаление необходимо, чтобы убедиться, что в таблице нет транзакций, которые были удалены из приложения. Но в реальном сценарии это происходит очень редко, и истинная потребность в удалении записей составляет менее 1 на 10000 обновлений транзакций.

Мой теоретический обходной путь состоит в том, чтобы создать дополнительный столбец, такой как «actionDeleted bit », и использовать UPDATE вместо DELETE, затем выполнить очистку таблицы с помощью пакетного задания каждые X минут или часов и выполнить

delete from transactions where transactionDeleted=1

Это должно быть быстрее, но мне нужно обновить все операторы SELECT в других частях приложения, чтобы использовать только записи TransactionsDeleted = 0, и это также может повлиять на производительность приложения.

Знаете ли вы лучшее решение?

ОБНОВЛЕНИЕ: текущие индексы:

CREATE NONCLUSTERED INDEX [IX1] ON [dbo].[Transactions] 
(
[platform] ASC,
[ItemId] ASC
) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF,   IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON, FILLFACTOR = 50) ON [PRIMARY]


CONSTRAINT [IX2] UNIQUE NONCLUSTERED 
(
[ItemId] DESC,
[count] ASC,
[dt] DESC,
[platform] ASC,
[price] ASC
) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

Ответы [ 3 ]

5 голосов
/ 23 декабря 2016

ОК, здесь есть и другой подход.Для аналогичной проблемы (большое сканирование при отсутствии совпадения по источнику, а затем при удалении) я сократил время выполнения MERGE с 806 мс до 6 мс!

Одна проблема, связанная с вышеуказанной проблемой, заключается в том, что предложение «WHEN NOT MATCHED BY SOURCE»сканирование всей таблицы TARGET.

Это не так очевидно, но Microsoft разрешает фильтровать таблицу TARGET (используя CTE) ПЕРЕД выполнением слияния.Так что в моем случае количество строк TARGET было уменьшено с 250 КБ до менее чем 10 строк.БОЛЬШАЯ разница.

Если предположить, что описанная выше проблема работает с фильтром TARGET по @itemid и @platform, то код MERGE будет выглядеть следующим образом.Изменения, внесенные в индексы, также помогут этой логике.

WITH Transactions_CTE (itemId
                        ,dt
                        ,count
                        ,price
                        ,platform
                        )
AS
-- Define the CTE query that will reduce the size of the TARGET table.  
(  
    SELECT itemId
        ,dt
        ,count
        ,price
        ,platform
    FROM Transactions  
    WHERE itemId = @itemId
      AND platform = @platform  
)  
MERGE Transactions_CTE AS TARGET
USING @Table AS SOURCE
    ON (
        TARGET.[itemId] = SOURCE.[itemId]
        AND TARGET.[platform] = SOURCE.[platform]
        AND TARGET.[dt] = SOURCE.[dt]
        AND TARGET.[count] = SOURCE.[count]
        AND TARGET.[price] = SOURCE.[price]
        )
WHEN NOT MATCHED BY TARGET  THEN
        INSERT
        VALUES (
            SOURCE.[itemId]
            ,SOURCE.[dt]
            ,SOURCE.[count]
            ,SOURCE.[price]
            ,SOURCE.[platform]
            )
WHEN NOT MATCHED BY SOURCE THEN
        DELETE;
2 голосов
/ 02 октября 2011

Использование поля BIT для IsDeleted (или IsActive, как это делают многие люди) допустимо, но требует внесения изменений в весь код и создания отдельного задания SQL для периодического прохождения и удаления «удаленных» записей. Это может быть путь, но сначала стоит попробовать что-то менее навязчивое.

В вашем наборе из 2 индексов я заметил, что ни один из них не является КЛАСТЕРНЫМ. Можно ли предположить, что поле IDENTITY есть? Вы могли бы подумать о том, чтобы сделать индекс [IX2] UNIQUE индексом CLUSTERED и изменить PK (опять же, я предполагаю, что поле IDENTITY является CLUSTERED PK), чтобы он НЕКЛЮЧЕН. Я бы также изменил порядок полей IX2, чтобы поставить [Platform] и [ItemID] на первое место. Поскольку ваша основная операция ищет [Platform] и [ItemID] как набор, физическое упорядочение их таким образом может помочь. И так как этот индекс уникален, это хороший кандидат для того, чтобы быть кластеризованным. Это, безусловно, стоит проверить, так как это повлияет на все запросы к таблице.

Кроме того, если изменение индексов, как я предлагал, помогает, все же, возможно, стоит попробовать обе идеи и, следовательно, также выполнить поле IsDeleted, чтобы посмотреть, не увеличивает ли это производительность еще больше.

EDIT: Я забыл упомянуть, что, сделав индекс IX2 CLUSTERED и переместив поле [Платформа] вверх, вы должны избавиться от индекса IX1.

EDIT2:

Просто чтобы быть очень ясным, я предлагаю что-то вроде:

CREATE UNIQUE CLUSTERED  INDEX [IX2]
(
[ItemId] DESC,
[platform] ASC,
[count] ASC,
[dt] DESC,
[price] ASC
)

И, честно говоря, изменение индекса CLUSTERED может также негативно повлиять на запросы, в которых JOIN выполняется в поле [id], поэтому вам необходимо тщательно протестировать. В конце концов, вам нужно настроить систему на наиболее частые и / или дорогостоящие запросы и, возможно, придется признать, что в результате некоторые запросы будут выполняться медленнее, но это может стоить того, чтобы эта операция была намного быстрее.

0 голосов
/ 02 октября 2011

Смотрите это /3817895/kak-povysit-proizvoditelnost-pri-udalenii-suschnostei-iz-bazy-dannyh....

будет ли обновление стоить столько же, сколько удаление? Нет, обновление будет гораздо более легкая операция, особенно если у вас был индекс на ПК (errrr, это гид, а не int). Дело в том, что обновление битовое поле намного дешевле. (Массовое) удаление приведет к перестановка данных.

В свете этой информации ваша идея использовать битовое поле очень верна.

...