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:
cats.id_cat
owner_cats.id_cat
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):
CommandTimeout=60000;
default was 30 sec)DELETE FROM WHERE EXISTS (SELECT 1 ...)
because it performed a little fasterDROP
all indexes and PKs before running delete statement (???)DELETE
statementCREATE
all indexes and PKsSeems 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 :)
Практического порога нет. Это зависит от того, какой тайм-аут вашей команды установлен в вашем соединении.
Имейте в виду, что время, необходимое для удаления всех этих строк, зависит от:
Последний пункт часто может быть наиболее важным. Выполните команду sp_who2 в другом окне запроса, чтобы убедиться, что не происходит конфликта блокировок, препятствующего выполнению вашей команды.
Неправильно настроенные серверы SQL не справятся с этим типом запросов. Журналы транзакций, которые слишком малы и / или находятся на тех же дисках, что и файлы данных, часто приводят к серьезным потерям производительности при работе с большими строками.
Что касается решения, ну, как и все, это зависит от обстоятельств. Вы собираетесь часто этим заниматься? В зависимости от того, сколько строк у вас осталось, самым быстрым способом может быть перестроение таблицы с другим именем, а затем ее переименование и воссоздание ограничений внутри транзакции. Если это просто специальная вещь, убедитесь, что ваш ADO CommandTimeout установлен достаточно высоким, и вы можете просто нести расходы на это большое удаление.
Если удаление приведет к удалению «значительного количества» строк из таблицы, это может быть альтернативой 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
Вы пробовали не использовать подзапрос и вместо этого использовали соединение?
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
Как уже упоминалось, когда вы удаляете 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% все еще будут существовать.
Если вы используете 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)
Как такового порога нет - вы можете УДАЛИТЬ все строки из любой таблицы , учитывая достаточно места в журнале транзакций - именно здесь ваш запрос, скорее всего, не выдержит . Если вы получаете какие-то результаты от вашего 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
Ответ Билла Карвина на другой вопрос применим и к моей ситуации:
«Если ваш DELETE
предназначен для удаления подавляющего большинства строк в этом table, люди часто делают то, что копируют только те строки, которые вы хотите сохранить, в дублирующую таблицу, а затем используют DROP TABLE
или TRUNCATE
, чтобы стереть исходную таблицу гораздо быстрее . "
Мэтт в этом ответе говорит об этом так:
" Если в автономном режиме и удаляется большой%, может иметь смысл просто создать новую таблицу с данными для хранения, отбросить старую таблицу и переименовать . "
ammoQ в этом ответе (из того же вопроса) рекомендует (перефразировано):