Самый эффективный (быстрый) T-SQL DELETE для многих строк? - PullRequest
5 голосов
/ 03 апреля 2009

Наше серверное приложение получает информацию о строках для добавления в базу данных со скоростью 1000-2000 строк в секунду в течение всего дня. В таблице есть два взаимоисключающих столбца, которые однозначно идентифицируют строку: один представляет собой числовой идентификатор с именем «тег», а другой - строку из 50 символов, называемую «longTag». Строка может иметь тег или longTag; не оба.

Каждая строка, которая выходит из сокета, может или не может уже существовать в таблице. Если он существует, эта строка должна быть обновлена ​​с новой информацией. Если он не существует, он должен быть добавлен. Мы используем SQL 2005, а в некоторых случаях даже SQL 2000, поэтому мы не можем использовать новое ключевое слово MERGE.

То, как я делаю это сейчас, заключается в создании гигантского оператора DELETE, который выглядит следующим образом:

DELETE from MyRecords
WHERE tag = 1
OR tag = 2
OR longTag = 'LongTag1'
OR tag = 555

... где каждая входящая строка имеет свое собственное предложение «ИЛИ тег = n» или «ИЛИ longTag =« x »».

Затем я выполняю массовую загрузку XML, используя ISQLXMLBulkLoad, чтобы загрузить все новые записи одновременно.

Гигантский оператор DELETE иногда прерывается, занимая 30 секунд или дольше. Я не уверен почему.

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

РЕДАКТИРОВАТЬ : отношение новых строк к строкам замены будет очень сильно наклонено к новым строкам. В данных, полученных из производства, для каждой коррекции обычно будет 100-1000 новых строк.

РЕДАКТИРОВАТЬ 2 : и вставки, и удаления должны обрабатываться как одна транзакция. Если при вставке или удалении происходит сбой, их необходимо откатить, оставив таблицу в том же состоянии, в котором она находилась до начала операций вставки и удаления.

EDIT 3 : Относительно пустых тегов. Сначала мне нужно кратко описать систему. Это база данных для торговой системы. MyTable - это таблица сделок, содержащая два вида сделок: так называемые «дневные сделки» и так называемые «открытие позиций». Дневные сделки - это просто сделки - если вы были трейдером опционов и совершали сделку, эта сделка была бы дневной сделкой в ​​этой системе. Открытые позиции - это, в основном, сводка вашего портфеля до сегодняшнего дня. И открытые позиции, и дневные сделки хранятся в одной таблице. Дневные сделки имеют теги (либо longTags, либо числовые теги), а открывающие позиции - нет. Для открытия позиций могут быть повторяющиеся строки - это нормально и нормально. Но не может быть повторяющихся строк для дневных сделок. Если дневная сделка приходит с тем же тегом, что и некоторая запись, уже имеющаяся в базе данных, то данные в таблице заменяются новыми данными.

Таким образом, есть 4 возможности для значений в теге & longTag:

1) тег не равен нулю, а longTag пуст: это дневная сделка с числовым идентификатором. 2) тег равен нулю, а longTag имеет непустое символьное значение. Это дневная сделка с буквенно-цифровым идентификатором. 3) тег равен нулю, а longTag пуст: это открытая позиция. 4) тег ненулевой и longTag имеет непустое символьное значение. Это предотвращается от всего, что происходит с помощью нашего серверного программного обеспечения, но если это произойдет, longTag будет проигнорирован, и он будет рассматриваться так же, как и в случае №1. Опять же этого не происходит.

Ответы [ 8 ]

5 голосов
/ 03 апреля 2009

Я думаю, что разделение гигантского оператора DELETE на 2 DELETE может помочь.

1 DELETE для работы с тегом и отдельное DELETE для работы с longTag. Это поможет серверу SQL выбрать эффективное использование индексов.

Конечно, вы все равно можете запустить 2 оператора DELETE за 1 БД.

Надеюсь, это поможет

4 голосов
/ 03 апреля 2009

OR (или in) почти работает так, как если бы каждый операнд OR был отдельным запросом. То есть он превращается в сканирование таблицы, и для каждой строки база данных должна проверять каждый операнд ИЛИ как предикат, пока не найдет совпадение или не закончатся операнды.

Единственная причина упаковать это состоит в том, чтобы сделать это одной логической единицей работы Вы также можете обернуть кучу удалений в транзакции и зафиксировать только тогда, когда все успешно завершится.

Quassnoi предлагает интересное предложение - использовать таблицу - но поскольку он затем использует IN и OR, получается то же самое.

Но попробуйте это.

Создайте новую таблицу, которая отражает вашу реальную таблицу. Назовите это u_real_table. Индексируйте его по тегу и longTag.

Поместите все ваши входящие данные в u_real_table.

Теперь, когда вы будете готовы к массовым действиям, вместо этого присоединитесь к зеркальному столу к реальной таблице на теге. Из реальной таблицы удалите все строки тегов в таблице u_real_table:

delete real_table from real_table a 
   join u_real_table b on (a.tag = b.tag);
insert into real_table select * 
   from u_real_table where tag is not null;

Видите, что мы здесь сделали? Поскольку мы присоединяемся к только по тегу , существует большая вероятность использования индекса тега.

Сначала мы удалили все новое, затем вставили новые замены. Мы также могли бы сделать обновление здесь. Что быстрее, зависит от структуры вашей таблицы и ее показателей.

Нам не нужно было писать скрипт для этого, нам просто нужно было вставить записи в u_real_table.

Теперь мы делаем то же самое для longTags:

delete real_table from real_table a 
   join u_real_table b on (a.longTag = b.longTag);
insert into real_table select * 
   from u_real_table where longTag is not null;

Наконец, мы очищаем u_real_table:

delete from u_real_table;

Очевидно, что мы заключаем в пару всю транзакцию удаления / вставки, так что удаление становится действительным только тогда, когда последующая вставка завершается успешно, а затем мы заключаем все это в другую транзакцию. Потому что это логическая единица работы.

Этот метод уменьшает вашу ручную работу, уменьшает вероятность ошибки вручную и имеет некоторый шанс ускорить удаление.

Обратите внимание, что это основано на том, что отсутствующие теги и longTags правильно равны нулю, а не нулю или пустой строке.

3 голосов
/ 03 апреля 2009

Посмотрите это видео, в котором показано, как сделать «ниппельное» удаление. Процесс работает хорошо и может определенно уменьшить проблемы блокировки / столкновения, которые вы видите:

http://www.sqlservervideos.com/video/nibbling-deletes

3 голосов
/ 03 апреля 2009

Нечто подобное может упростить процесс (вы просто вставите строки, независимо от того, существуют ли они уже - нет необходимости в предварительном утверждении DELETE):

CREATE TRIGGER dbo.TR_MyTable_Merge 
   ON  dbo.MyTable 
   INSTEAD OF INSERT
AS 
BEGIN
  SET NOCOUNT ON;

  BEGIN TRANSACTION

  DELETE MyTable 
  FROM   MyTable t INNER JOIN inserted i ON t.tag = i.tag 

  DELETE MyTable 
  FROM   MyTable t INNER JOIN inserted i ON t.longTag = i.longTag

  INSERT MyTable 
  SELECT * FROM inserted

  COMMIT TRANSACTION

  SET NOCOUNT OFF;
END

РЕДАКТИРОВАТЬ: ранее объединенный оператор DELETE, разбитый на два отдельных оператора, чтобы обеспечить оптимальное использование индекса.

Не использовать DELETE вообще, а ОБНОВЛЯТЬ, что затронутые / повторяющиеся строки на месте будут легче в индексах.

3 голосов
/ 03 апреля 2009

Может быть:

DELETE FROM MyRecords
WHERE  tag IN (1, 2, 555) -- build a list
OR longTag IN ('LongTag1')

Я подозреваю, что индексы помогут вам удалить, но резко замедлить вставку, так что я бы не стал слишком много с этим играть. Но тогда моя интуиция еще не совсем совершенна, возможно, вы сможете настроить FillFactor или другие элементы, чтобы обойти эту проблему, и одно, что я точно знаю, это то, что вы действительно хотите профилировать оба в любом случае.

Другой вариант - загрузить новые вставки во временную таблицу (с именем что-то вроде InputQueue), а затем присоединиться к временной таблице на MyRecords для обработки обновлений фильтрации. Это также упростит обновление в два этапа: вы можете удалить теги и длинные теги как отдельные операции, что может оказаться гораздо более эффективным.

2 голосов
/ 03 апреля 2009

Использование OR может вызвать сканирование таблицы - можете ли вы разбить его на четыре оператора? Завершение каждого в транзакции также может ускорить процесс.

DELETE from MyRecords
WHERE tag = 1

DELETE from MyRecords
WHERE tag = 2

DELETE from MyRecords
WHERE tag = 555

DELETE from MyRecords
WHERE longTag = 'LongTag1'
2 голосов
/ 03 апреля 2009

Кажется, что ваша таблица не проиндексирована на (tag) и (longTag)

Построить два индекса: один на (tag), один на (longTag)

Если вы планируете удалить действительно большое количество записей, объявите две табличные переменные, заполните их значениями и удалите так:

DECLARE @tag TABLE (id INT);
DECLARE @longTag TABLE (id VARCHAR(50));

INSERT
INTO  @tag
VALUES (`tag1`)

INSERT
INTO  @tag
VALUES (`tag2`)

/* ... */

INSERT INTO @longTag
VALUES ('LongTag1')

/* ... */


DELETE
FROM    MyRecords r
WHERE   r.tag IN (SELECT * FROM @tag)
        OR r.longTag IN (SELECT * FROM @longTag)

Вы также можете попробовать выполнить два прохода DELETE:

DELETE
FROM    MyRecords r
WHERE   r.tag IN (SELECT * FROM @tag)

DELETE
FROM    MyRecords r
WHERE   r.longTag IN (SELECT * FROM @longTag)

и посмотрите, какие операторы выполняются дольше, чтобы увидеть, есть ли проблема с индексами.

1 голос
/ 04 апреля 2009

Индексация:

Рассмотрите возможность использования индексированного постоянного вычисляемого столбца для longTag, в котором хранится контрольная сумма longTag. Вместо того, чтобы индексировать LongTag1, вы индексируете 4-байтовое значение типа int (86939596).

Тогда ваш поиск [надеюсь *] будет быстрее, и вам просто нужно включить значение longTag в запрос / удалить. Ваш код будет немного сложнее, но индексация, вероятно, будет гораздо более эффективной.

* Требуется тестирование

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...