УДАЛИТЬ SQL с коррелированным подзапросом для таблицы с 42 миллионами строк? - PullRequest
6 голосов
/ 07 августа 2010

У меня есть таблица cats с 42 795 120 строками.

Видимо, это много строк.Поэтому, когда я делаю:

/* owner_cats is a many-to-many join table */
DELETE FROM cats
WHERE cats.id_cat IN (
SELECT owner_cats.id_cat FROM owner_cats
WHERE owner_cats.id_owner = 1)

время ожидания запроса истекает: (

(редактировать: мне нужно увеличить значение CommandTimeout по умолчанию только 30 секунд)

Я не могу использовать TRUNCATE TABLE cats, потому что я не хочу выбрасывать кошек от других владельцев.

Яиспользуя SQL Server 2005 с параметром «Модель восстановления», установленным на «Простой».

Итак, я подумал о том, чтобы сделать что-то вроде этого (выполнить этот SQL из приложения):

DELETE TOP (25) PERCENT FROM cats
WHERE cats.id_cat IN (
SELECT owner_cats.id_cat FROM owner_cats
WHERE owner_cats.id_owner = 1)

DELETE TOP(50) PERCENT FROM cats
WHERE cats.id_cat IN (
SELECT owner_cats.id_cat FROM owner_cats
WHERE owner_cats.id_owner = 1)

DELETE FROM cats
WHERE cats.id_cat IN (
SELECT owner_cats.id_cat FROM owner_cats
WHERE owner_cats.id_owner = 1)

У меня вопрос: какой порог количества строк, которые я могу DELETE в SQL Server 2005?

Или, если мой подход не является оптимальным, предложите лучший подход. Спасибо.

Этот пост мне не помог достаточно:

РЕДАКТИРОВАТЬ (06.08.2010):

Хорошо, я только что понял после прочтения вышеупомянутой ссылки, что у меня не было индексов на этихстолы. Также, сомВы уже указали на эту проблему в комментариях ниже.Имейте в виду, что это фиктивная схема, поэтому даже id_cat не является PK, потому что в моей реальной жизненной схеме это не уникальное поле.

Я добавлю индексы:

  1. cats.id_cat
  2. owner_cats.id_cat
  3. owner_cats.id_owner

Полагаю, я все еще изучаю это хранилище данных, и, очевидно, янужны индексы для всех полей JOIN, верно?

Однако мне требуется несколько часов, чтобы выполнить этот процесс пакетной загрузки.Я уже делаю это как SqlBulkCopy (кусками, а не 42 милами одновременно).У меня есть несколько индексов и ПК.Я прочитал следующие посты, которые подтверждают мою теорию о том, что индексы замедляются даже при массовом копировании:

Итак, я собираюсь DROP мои индексы перед копированием, а затем повторно CREATE их, когдаэто сделано.

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

ОБНОВЛЕНИЕ (8/7/2010):

Том предложил:

DELETE
FROM cats c
WHERE EXISTS (SELECT 1
FROM owner_cats o
WHERE o.id_cat = c.id_cat
AND o.id_owner = 1)

И все жебез индексов для 42 миллионов строк это заняло 13:21 мин: сек против 22:08, как описано выше.Однако за 13 миллионов строк он занял 2:13 против 2:10 по-старому.Это хорошая идея, но мне все еще нужно использовать индексы!

Обновление (08.08.2010):

Что-то ужасно неправильно!Теперь при включенных индексах мой первый запрос на удаление, приведенный выше, занял 1: 9 часов: мин (да, час!) против 22:08 мин: с и 13:21 мин: спротив 2:10 мин: сек для 42 милов строк и 13 мил рядов соответственно.Я собираюсь попробовать запрос Тома с индексами сейчас, но это движется в неправильном направлении.Пожалуйста, помогите.

Обновление (9/9/2010):

Удаление Тома заняло 1:06 часа: мин для 42 млн строк и 10:50 мин: секдля 13 мил строк с индексами против 13:21 мин: сек и 2:13 мин: сек соответственно. Удаление занимает больше времени в моей базе данных, когда я использую индексы на порядок! Мне кажется, я знаю, почему, моя база данных .mdf и .ldf выросла с 3,5 ГБ до 40,6 ГБ за времяпервое (42 мил) удаление! Что я делаю не так?

Обновление (10/10/2010):

Из-за отсутствия каких-либо других опций я подошелс тем, что я считаю слабым решением (надеюсь, временным) :

  1. Увеличение времени ожидания для подключения к базе данных до 1 часа (CommandTimeout=60000; по умолчанию было 30 секунд)
  2. Используйте запрос Тома: DELETE FROM WHERE EXISTS (SELECT 1 ...), потому что он выполнялся немного быстрее
  3. DROP всех индексов и PK перед запуском оператора удаления (???)
  4. Выполнить DELETE оператор
  5. CREATE все индексы и PK

Кажется сумасшедшим, но по крайней мере это быстрее, чем использовать TRUNCATE и начинать с моей нагрузки с самого начала с первого owner_id, потому что одному из моих owner_id требуется 2:30 часа: мин для загрузки против 17:22min: sec для процесса удаления, который я только что описал с 42 млн строк.(Примечание: если мой процесс загрузки выдает исключение, я начинаю заново для этого owner_id, но я не хочу отбрасывать предыдущий owner_id, поэтому я не хочу TRUNCATE таблицу owner_cats,вот почему я пытаюсь использовать DELETE.)

Больше помощи все равно будет приветствоваться:)

Ответы [ 9 ]

6 голосов
/ 11 августа 2010

Вы не пробовали подзапрос и использовали вместо этого объединение?

DELETE cats 
FROM
 cats c
 INNER JOIN owner_cats oc
 on c.id_cat = oc.id_cat
WHERE
   id_owner =1

А если у вас уже были, вы также пробовали разные подсказки, например

DELETE cats 
FROM
 cats c
 INNER HASH JOIN owner_cats oc
 on c.id_cat = oc.id_cat
WHERE
   id_owner =1
6 голосов
/ 07 августа 2010

Если удаление удалит «значительное количество» строк из таблицы, это может быть альтернативой УДАЛЕНИЮ: поместить записи в другое место, обрезать исходную таблицу, вернуть «хранители». Что-то вроде:

SELECT *
INTO #cats_to_keep
FROM cats
WHERE cats.id_cat NOT IN (    -- note the NOT
SELECT owner_cats.id_cat FROM owner_cats
WHERE owner_cats.id_owner = 1)

TRUNCATE TABLE cats

INSERT INTO cats
SELECT * FROM #cats_to_keep
6 голосов
/ 07 августа 2010

Практического порога нет. Это зависит от того, какое время ожидания вашей команды установлено для вашего соединения.

Имейте в виду, что время, необходимое для удаления всех этих строк, зависит от:

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

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

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

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

4 голосов
/ 07 августа 2010

Если вы используете EXISTS вместо IN, вы должны получить намного лучшую производительность. Попробуйте это:

DELETE
  FROM cats c
 WHERE EXISTS (SELECT 1
                 FROM owner_cats o
                WHERE o.id_cat = c.id_cat
                  AND o.id_owner = 1)
3 голосов
/ 30 сентября 2011

Может быть стоит попробовать MERGE например,

MERGE INTO cats 
   USING owner_cats
      ON cats.id_cat = owner_cats.id_cat
         AND owner_cats.id_owner = 1
WHEN MATCHED THEN DELETE;
3 голосов
/ 07 августа 2010

Как уже упоминали другие, когда вы удаляете 42 миллиона строк, БД должна регистрировать 42 миллиона удалений в базе данных.Таким образом, журнал транзакций должен существенно увеличиваться.То, что вы можете попробовать, это разбить удаление на куски.В следующем запросе я использую функцию ранжирования NTile, чтобы разбить строки на 100 сегментов.Если это слишком медленно, вы можете увеличить количество сегментов так, чтобы каждое удаление было меньше.Это очень поможет, если есть индекс для owner_cats.id_owner, owner_cats.id_cats и cats.id_cat (который я предположил, первичный ключ и числовой).это не транзакционный.Таким образом, если это не удастся на 40-м блоке, вы удалите 40% строк, а остальные 60% все еще будут существовать.

3 голосов
/ 07 августа 2010

Порог как таковой отсутствует - вы можете УДАЛИТЬ все строки из любой таблицы , если достаточно места в журнале транзакций - именно там ваш запрос, скорее всего, падает.Если вы получаете какие-то результаты от вашего DELETE TOP (n) PERCENT FROM CATS WHERE ... тогда вы можете заключить его в цикл, как показано ниже:

SELECT 1
WHILE @@ROWCOUNT <> 0
BEGIN
 DELETE TOP (somevalue) PERCENT FROM cats
 WHERE cats.id_cat IN (
 SELECT owner_cats.id_cat FROM owner_cats
 WHERE owner_cats.id_owner = 1)
END
1 голос
/ 05 августа 2011

(28.09.2011)
Мой ответ работает в основном так же, как и решение Томаса (6 августа 10 года). Я пропустил его, когда опубликовал свой ответ, потому что он использует реальный КУРСОР, поэтому я подумал про себя "плохо" из-за количества записей. Однако, когда я перечитываю его ответ только сейчас, я понимаю, что ПУТЬ, который он использует, на самом деле «хорош». Очень умно. Я только что проголосовал за его ответ и, вероятно, буду использовать его подход в будущем. Если вы не понимаете почему, взгляните на это еще раз. Если вы все еще не видите его, оставьте комментарий к этому ответу, и я вернусь и постараюсь объяснить подробно. Я решил оставить свой ответ, потому что у кого-то может быть администратор БД, который отказывается разрешить им использовать реальный КУРСОР, независимо от того, насколько он «хорош». :-)

Я понимаю, что этому вопросу год, но у меня недавно была похожая ситуация. Я пытался сделать «массовые» обновления для большой таблицы с объединением другой таблицы, также довольно большой. Проблема заключалась в том, что объединение приводило к такому количеству «объединенных записей», что его обработка занимала слишком много времени и могла привести к возникновению конфликтов. Поскольку это было одноразовое обновление, я придумал следующий «хак». Я создал WHILE LOOP, который прошел таблицу для обновления и выбрал 50 000 записей для обновления за один раз. Выглядело это примерно так:

DECLARE @RecId bigint
DECLARE @NumRecs bigint
SET @NumRecs = (SELECT MAX(Id) FROM [TableToUpdate])
SET @RecId = 1
WHILE @RecId < @NumRecs
BEGIN
    UPDATE [TableToUpdate]
    SET UpdatedOn = GETDATE(),
        SomeColumn = t2.[ColumnInTable2]
    FROM    [TableToUpdate] t
    INNER JOIN [Table2] t2 ON t2.Name = t.DBAName 
        AND ISNULL(t.PhoneNumber,'') = t2.PhoneNumber 
        AND ISNULL(t.FaxNumber, '') = t2.FaxNumber
    LEFT JOIN [Address] d ON d.AddressId = t.DbaAddressId 
        AND ISNULL(d.Address1,'') = t2.DBAAddress1
        AND ISNULL(d.[State],'') = t2.DBAState
        AND ISNULL(d.PostalCode,'') = t2.DBAPostalCode
    WHERE t.Id BETWEEN @RecId AND (@RecId + 49999)
    SET @RecId = @RecId + 50000
END

Ничего особенного, но он сделал свою работу. Поскольку одновременно обрабатывалось только 50 000 записей, любые созданные блокировки были недолговечными. Кроме того, оптимизатор понял, что ему не нужно выполнять всю таблицу, поэтому он лучше справился с выбором плана выполнения.

(28.09.2011)
ОГРОМНОЕ предостережение к предложению, которое было упомянуто здесь более одного раза и опубликовано повсюду в Интернете, касающемуся копирования «хороших» записей в другую таблицу, выполнения TRUNCATE (или DROP и reCREATE, или DROP и переименовать), а затем снова заполнить таблицу.

Вы не можете сделать это, если таблица является таблицей PK в отношении PK-FK (или другой CONSTRAINT). Конечно, вы могли бы УДАЛИТЬ отношения, выполнить очистку и восстановить отношения, но вам также придется очистить таблицу FK. Вы можете сделать это ПЕРЕД восстановлением отношений, что означает больше «простоев», или вы можете отказаться от ОБЕСПЕЧЕНИЯ ОГРАНИЧЕНИЯ при создании и последующей очистки. Я думаю, вы также можете очистить таблицу FK ДО того, как вы очистите таблицу PK. Суть в том, что вам нужно явно очистить таблицу FK, так или иначе.

Мой ответ - гибридный процесс на основе SET / квази-КУРСОР. Еще одним преимуществом этого метода является то, что, если отношение PK-FK настроено на CASCADE DELETES, вам не нужно выполнять очистку, о которой я упоминал выше, потому что сервер позаботится об этом за вас. Если ваша компания / администратор БД отказывается от каскадного удаления, вы можете попросить, чтобы он был включен только во время выполнения этого процесса, а затем отключен после его завершения. В зависимости от уровней разрешений учетной записи, которая выполняет очистку, операторы ALTER для включения / отключения каскадного удаления могут быть прикреплены к началу и концу оператора SQL.

0 голосов
/ 12 августа 2010

Ответ Билла Карвина на другой вопрос относится и к моей ситуации:

"Если ваш DELETE предназначен для устранения подавляющего большинства строк в этой таблице, люди часто делают одну вещь: скопировать только те строки, которые вы хотите сохранить, в дублирующуюся таблицу, а затем использовать DROP TABLE или TRUNCATE чтобы быстрее уничтожить исходный стол. "

Мэтт в этом ответе говорит это так:

"Если в автономном режиме и удаление большого%, возможно, имеет смысл просто создать новую таблицу с данными для хранения, удалить старую таблицу и переименовать."

ammoQ в этом ответе (из того же вопроса) рекомендует (перефразируя):

  • выдавать блокировку таблицы при удалении большого количества строк
  • ставить индексы для любых столбцов внешнего ключа
...