DELETE SQL with correlated subquery for table with 42 million rows?

I have a table cats with 42,795,120 rows.

Apparently this is a lot of rows. So when I do:

/* 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)

the query times out :(

(edit: I need to increase my CommandTimeout value, default is only 30 seconds)

I can't use TRUNCATE TABLE cats because I don't want to blow away cats from other owners.

I'm using SQL Server 2005 with "Recovery model" set to "Simple."

So, I thought about doing something like this (executing this SQL from an application btw):

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)

My question is: what is the threshold of the number of rows I can DELETE in SQL Server 2005?

Or, if my approach is not optimal, please suggest a better approach. Thanks.

This post didn't help me enough:

EDIT (8/6/2010):

Okay, I just realized after reading the above link again that I did not have indexes on these tables. Also, some of you have already pointed out that issue in the comments below. Keep in mind this is a fictitious schema, so even id_cat is not a PK, because in my real life schema, it's not a unique field.

I will put indexes on:

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

I guess I'm still getting the hang of this data warehousing, and obviously I need indexes on all the JOIN fields right?

However, it takes hours for me to do this batch load process. I'm already doing it as a SqlBulkCopy (in chunks, not 42 mil all at once). I have some indexes and PKs. I read the following posts which confirms my theory that the indexes are slowing down even a bulk copy:

So I'm going to DROP my indexes before the copy and then re CREATE them when it's done.

Because of the long load times, it's going to take me awhile to test these suggestions. I'll report back with the results.

UPDATE (8/7/2010):

Tom suggested:

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

And still with no indexes, for 42 million rows, it took 13:21 min:sec versus 22:08 with the way described above. However, for 13 million rows, took him 2:13 versus 2:10 my old way. It's a neat idea, but I still need to use indexes!

Update (8/8/2010):

Something is terribly wrong! Now with the indexes on, my first delete query above took 1:9 hrs:min (yes an hour!) versus 22:08 min:sec and 13:21 min:sec versus 2:10 min:sec for 42 mil rows and 13 mil rows respectively. I'm going to try Tom's query with the indexes now, but this is heading in the wrong direction. Please help.

Update (8/9/2010):

Tom's delete took 1:06 hrs:min for 42 mil rows and 10:50 min:sec for 13 mil rows with indexes versus 13:21 min:sec and 2:13 min:sec respectively. Deletes are taking longer on my database when I use indexes by an order of magnitude! I think I know why, my database .mdf and .ldf grew from 3.5 GB to 40.6 GB during the first (42 mil) delete! What am I doing wrong?

Update (8/10/2010):

For lack of any other options, I have come up with what I feel is a lackluster solution (hopefully temporary):

  1. Increase timeout for database connection to 1 hour (CommandTimeout=60000; default was 30 sec)
  2. Use Tom's query: DELETE FROM WHERE EXISTS (SELECT 1 ...) because it performed a little faster
  3. DROP all indexes and PKs before running delete statement (???)
  4. Run DELETE statement
  5. CREATE all indexes and PKs

Seems crazy, but at least it's faster than using TRUNCATE and starting over my load from the beginning with the first owner_id, because one of my owner_id takes 2:30 hrs:min to load versus 17:22 min:sec for the delete process I just described with 42 mil rows. (Note: if my load process throws an exception, I start over for that owner_id, but I don't want to blow away previous owner_id, so I don't want to TRUNCATE the owner_cats table, which is why I'm trying to use DELETE.)

Anymore help would still be appreciated :)

6
задан Community 23 May 2017 в 12:24
поделиться

7 ответов

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

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

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

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

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

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

6
ответ дан 8 December 2019 в 02:52
поделиться

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

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
ответ дан 8 December 2019 в 02:52
поделиться

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

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
ответ дан 8 December 2019 в 02:52
поделиться

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

Declare @Cats Cursor
Declare @CatId int  --assuming an integer PK here
Declare @Start int
Declare @End int
Declare @GroupCount int

Set @GroupCount = 100

Set @Cats = Cursor Fast_Forward For
    With CatHerd As
        (
        Select cats.id_cat
            , NTile(@GroupCount) Over ( Order By cats.id_cat ) As Grp
        From cats
            Join owner_cats
                On owner_cats.id_cat = cats.id_cat
        Where owner_cats.id_owner = 1
        )
        Select Grp, Min(id_cat) As MinCat, Max(id_cat) As MaxCat
        From CatHerd
        Group By Grp
Open @Cats
Fetch Next From @Cats Into @CatId, @Start, @End

While @@Fetch_Status = 0
Begin
    Delete cats
    Where id_cat Between @Start And @End

    Fetch Next From @Cats Into @CatId, @Start, @End
End 

Close @Cats
Deallocate @Cats

Примечательная загвоздка описанного выше подхода заключается в том, что он не является транзакционным. Таким образом, если произойдет сбой на 40-м блоке, вы удалите 40% строк, а остальные 60% все еще будут существовать.

3
ответ дан 8 December 2019 в 02:52
поделиться

Если вы используете 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)
4
ответ дан 8 December 2019 в 02:52
поделиться

Как такового порога нет - вы можете УДАЛИТЬ все строки из любой таблицы , учитывая достаточно места в журнале транзакций - именно здесь ваш запрос, скорее всего, не выдержит . Если вы получаете какие-то результаты от вашего 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
3
ответ дан 8 December 2019 в 02:52
поделиться

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

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

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

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

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

  • блокировать таблицу при удалении большого количества строк
  • помещать индексы в любые столбцы внешнего ключа
0
ответ дан 8 December 2019 в 02:52
поделиться
Другие вопросы по тегам:

Похожие вопросы: