Гость
Форумы / Microsoft SQL Server [игнор отключен] [закрыт для гостей] / ускорить удаление с отбором по времяшке / 22 сообщений из 22, страница 1 из 1
01.12.2020, 12:01
    #40023581
0wl
0wl
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ускорить удаление с отбором по времяшке
Всем добрый день!

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

Получилась конструкция из скрипта ниже: сначала создаем временную таблицу со списком id на удаление, потом удаляем в цикле с отбором по этой времяшке – так получается стабильное время транзакций и минимальное число логических чтений.

В целом, меня тут все устраивает. Единственное, за что цепляется глаз: сортировка перед Merge Join. (полный план - во вложении)




Понятно, почему так получается: сиквел не знает, что num и id растут «в одну сторону», он ожидает примерно такой ситуации:

NUM ID10 515 2
ну и перестраховывается дополнительной сортировкой.

Собственно, вопрос: может быть я что-то пропустил и от этой сортировки можно избавиться? Как-то все-таки подсказать движку, что значения постоянно равномерно растут и выбрав растущие num, он выберет так же растущие id? Сразу добавлю: индекс по num, id или id, num не помогает

Код:


Хотел приложить csv с примером таблицы ids, но даже архив оказался слишком большим. Поэтому выложил его снаружи:
https://drive.google.com/file/d/1-ddrz3fKwvw70jBeUzan-tB91GWvAnT9/view?usp=sharing


Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
/*
create table ids (id bigint, obj int, [guid] nvarchar(256))

bulk insert ids 
from 'D:\tmp\ids.csv' with (fieldterminator = ';', FIRSTROW  = 2)

create unique clustered index ci on ids(id)
create index nci on ids(obj, id, [guid])
*/


DECLARE @cur_id BIGINT

SELECT t1.id
	,t1.obj
	,row_number() OVER (
		ORDER BY t1.id
		) AS num
INTO #to_del
FROM ids t1 WITH (NOLOCK)
INNER JOIN (
	SELECT max(id) [id]
		,obj
	FROM ids WITH (NOLOCK)
	GROUP BY obj
	) AS t2 ON t1.obj = t2.obj
	AND t1.id < t2.id

CREATE UNIQUE CLUSTERED INDEX ndx ON #to_del (num)
CREATE UNIQUE INDEX ndx3 ON #to_del (id) --чтобы была статистика по id

SELECT @cur_id = 0

WHILE 1 = 1
BEGIN
	DELETE ids
	FROM ids t2
	INNER JOIN #to_del td ON t2.id = td.id
		AND t2.obj = td.obj
		AND td.num >= @cur_id
		AND td.num < @cur_id + 10000

	IF @@ROWCOUNT = 0
		BREAK

	SET @cur_id = @cur_id + 10000
END

DROP TABLE #to_del


...
Рейтинг: 0 / 0
01.12.2020, 12:07
    #40023584
msLex
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ускорить удаление с отбором по времяшке
0wl
Собственно, вопрос: может быть я что-то пропустил и от этой сортировки можно избавиться? Как-то все-таки подсказать движку, что значения постоянно равномерно растут и выбрав растущие num, он выберет так же растущие id? Сразу добавлю: индекс по num, id или id, num не помога


сделайте на # таблице кластерый индекс по полю сортировки, тогда на # будет ordered scan, выдающий уже правильно отсортированный датасет.
...
Рейтинг: 0 / 0
01.12.2020, 12:10
    #40023587
Ennor Tiegael
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ускорить удаление с отбором по времяшке
0wl,

У вас поле obj никак не проиндексировано, а в запросе на удаление оно используется в джойне.
...
Рейтинг: 0 / 0
01.12.2020, 12:17
    #40023590
fkthat
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ускорить удаление с отбором по времяшке
0wl
много добавлений-изменений от бизнес-логики

Если их действительно много, то без партиционирования с чисткой будет задница, какие бы ты индексы не поприкручивал - сами уже это проходили.
...
Рейтинг: 0 / 0
01.12.2020, 12:50
    #40023606
invm
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ускорить удаление с отбором по времяшке
0wl,

У вас на сортировку тратится мизер, по сравнению со всем остальным.
Потенциально гораздо хуже clustered index scan по "горячей" таблице в цикле.
...
Рейтинг: 0 / 0
01.12.2020, 13:05
    #40023611
aleks222
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ускорить удаление с отбором по времяшке
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
create table #to_del(id, obj, primary key(id, obj) );

declare @rc int = 1;

WHILE @rc > 0 BEGIN

    truncate table #to_del;

    insert INTO #to_del with(tablockx)
      SELECT top(4999) id, obj
        FROM ids t1 WITH (NOLOCK)
        where not exists( select * from ids WITH (NOLOCK) where obj = t1.obj AND id < t1.id)
        order by id, obj;
    set @rc = @rowcount;

	DELETE t2 FROM ids as t2 with(rowlock) INNER JOIN #to_del td ON t2.id = td.id AND t2.obj = td.obj;

END
...
Рейтинг: 0 / 0
01.12.2020, 13:10
    #40023612
komrad
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ускорить удаление с отбором по времяшке
aleks222,

@@rowcount
...
Рейтинг: 0 / 0
01.12.2020, 13:17
    #40023614
0wl
0wl
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ускорить удаление с отбором по времяшке
aleks222,

нет, к сожалению, получается хуже:
куча перечитываний ids на каждой итерации для того, чтобы сформировать очередную #to_del
И после этого - практически тот же план, что я привел в начале.

...
Рейтинг: 0 / 0
01.12.2020, 13:18
    #40023615
0wl
0wl
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ускорить удаление с отбором по времяшке
Спасибо всем, перепробовал еще несколько вариантов.

Можно сделать индекс во временной таблице по obj, id, num – доп. сортировка, естественно, пропадает. Но тогда для получения очередной пачки на удаление нужно делать сканирование всей временной таблицы. И это то, от чего я изначально бежал: на большой временной таблице у меня может быть много итераций. И чем ближе к концу цикла, тем дальше в таблице будут нужные значения num – то есть, удаление со временем будет деградировать. С тем же успехом можно просто сделать DELETE TOP (N) в цикле и не городить весь этот огород.

Можно сделать кластерный индекс по obj, id и некластерный по num. Тогда сиквел отбирает записи по индексу с num и тут понимает, что ключи кластерного индекса у него же уже считаны – и опять уходит в сортировку (не говорю, что это хуже, чем key lookup, но это и не лучше исходного варианта)

Ну и изначальный вариант, по сути мало отличающийся от предыдущего.

В волшебном мире розовых пони я бы вообще выкинул поле num и фильтровал пачки по id (ну и сделал бы соответствующий индекс на таблице ids). Но в реальности id распределён неравномерно, в распределении возможны дыры, так что надёжнее все-таки принудительно пронумеровать времяшку.

В целом, сортировка на стабильно коротком объёме видится меньшим злом, чем замедляющееся на каждой итерации сканирование таблиц. Всем еще раз спасибо, думаю, можно закрывать.
...
Рейтинг: 0 / 0
01.12.2020, 13:33
    #40023618
msLex
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ускорить удаление с отбором по времяшке
Лучший результат должен получиться при следующем подходе.

1. Выбираем в отдельную # таблицу очередные 1000 записей из общей # таблицы
2. Находим среди этих данных max_obj и min_obj
3. В запросе на удаление
- используем # таблицу с 1000 записями,
- добавляем условия с диапазоном min_obj - max_obj (obj >= @min_obj and obj <= @max_obj) на основную таблицу
- для надежности, добавляем option(merge join)

по идее, должен получиться план
- со сканом # таблицы из 1000 записей
- в зависимости от ключа кластерного индекса # таблицы из 1000 записей, возможно сортировка
- range seek под основной таблице
- merge join этих dataset-ов
...
Рейтинг: 0 / 0
01.12.2020, 15:25
    #40023661
Oleg_SQL
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ускорить удаление с отбором по времяшке
а если использовать DELETE TOP(100...) ? должны удаляться "первые попавшиеся" строки.
...
Рейтинг: 0 / 0
01.12.2020, 15:29
    #40023664
aleks222
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ускорить удаление с отбором по времяшке
Oleg_SQL
а если использовать DELETE TOP(100...) ? должны удаляться "первые попавшиеся" строки.

Да, но тредстартер не ищет легких путей....
...
Рейтинг: 0 / 0
01.12.2020, 15:34
    #40023666
aleks222
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ускорить удаление с отбором по времяшке
msLex
Лучший результат должен получиться при следующем подходе.

1. Выбираем в отдельную # таблицу очередные 1000 записей из общей # таблицы
2. Находим среди этих данных max_obj и min_obj
3. В запросе на удаление
- используем # таблицу с 1000 записями,
- добавляем условия с диапазоном min_obj - max_obj (obj >= @min_obj and obj <= @max_obj) на основную таблицу
- для надежности, добавляем option(merge join)

по идее, должен получиться план
- со сканом # таблицы из 1000 записей
- в зависимости от ключа кластерного индекса # таблицы из 1000 записей, возможно сортировка
- range seek под основной таблице
- merge join этих dataset-ов


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

1. Выбираем ВСЕ, что НЕ нужно удалять.
2. Тупо удаляем все, что не входит в набор 1.
3. Пачками не более 5000 шт.
...
Рейтинг: 0 / 0
01.12.2020, 16:23
    #40023686
0wl
0wl
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ускорить удаление с отбором по времяшке
aleks222
Oleg_SQL
а если использовать DELETE TOP(100...) ? должны удаляться "первые попавшиеся" строки.

Да, но тредстартер не ищет легких путей....


Зачем делать просто, когда можно делать сложно? Тем более, что простые пути ведут к неожиданным последствиям: https://dba.stackexchange.com/questions/246833/deleting-millions-of-records-on-sql-server-14-0

aleks2221. Выбираем ВСЕ, что НЕ нужно удалять.
2. Тупо удаляем все, что не входит в набор 1.
3. Пачками не более 5000 шт.


Ну то есть у нас на каждой итерации удаления будет какой-нибудь Nested Loops для проверки на вхождение в "белый список" (из шага 1). А если этот список окажется большим? Как-то рискованно, я уж лучше с моим Merge join останусь

Сразу в сторону: пока проблемы с очисткой мусора выстреливают хоть и метко, но редко. Так что про партиционирование пока думать рано, но когда (и если) этот скрипт перестанет справляться, разбитие на секции будет следующим шагом.
...
Рейтинг: 0 / 0
01.12.2020, 16:33
    #40023690
aleks222
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ускорить удаление с отбором по времяшке
0wl

Ну то есть у нас на каждой итерации удаления будет какой-нибудь Nested Loops для проверки на вхождение в "белый список" (из шага 1). А если этот список окажется большим?


У вас, несомненно будет.
Прочие используют индексы.
...
Рейтинг: 0 / 0
01.12.2020, 16:35
    #40023693
msLex
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ускорить удаление с отбором по времяшке
aleks222
0wl

Ну то есть у нас на каждой итерации удаления будет какой-нибудь Nested Loops для проверки на вхождение в "белый список" (из шага 1). А если этот список окажется большим?


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


Индекс не избавляет от Nested Loops, а чаще его требует.
...
Рейтинг: 0 / 0
01.12.2020, 19:09
    #40023739
0wl
0wl
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ускорить удаление с отбором по времяшке
msLex
Лучший результат должен получиться при следующем подходе.

1. Выбираем в отдельную # таблицу очередные 1000 записей из общей # таблицы
2. Находим среди этих данных max_obj и min_obj
3. В запросе на удаление
- используем # таблицу с 1000 записями,
- добавляем условия с диапазоном min_obj - max_obj (obj >= @min_obj and obj <= @max_obj) на основную таблицу
- для надежности, добавляем option(merge join)

по идее, должен получиться план
- со сканом # таблицы из 1000 записей
- в зависимости от ключа кластерного индекса # таблицы из 1000 записей, возможно сортировка
- range seek под основной таблице
- merge join этих dataset-ов


Подумал над вашим вариантом и понял, что исходный план примерно так и работает:
1. из #to_del выбирается очередная пачка с отбором по num (номер по порядку)
2. выполняется та самая сортировка, из-за которой я написал пост. Находится минимальное и максимальное значение ключей
3. Дальше merge по найденным границам.



--
Как ни странно, последний вариант aleks222 оказался немножко лучше всех предыдущих попыток. Если сделать "белый список" и создать по нему индекс по obj, id - в плане выбирается Merge join. По суммарному числу чтений получается то же самое, что и мой исходный вариант, но нет сортировки, а число чтений из "белого списка" постепенно увеличивается от итерации к итерации - то есть, этот вариант действительно выгоден, когда в таблице удаляется на порядки больше строк, чем остается.

...
Рейтинг: 0 / 0
01.12.2020, 20:53
    #40023770
invm
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ускорить удаление с отбором по времяшке
0wl,

Вы не хотите понять простую вещь - ожидания при сканировании кластерного индекса нагруженной изменениями таблицы съедят весь ваш "выигрыш" от исключения сортировки.
msLex предложил вариант ухода от такого сканирования.
...
Рейтинг: 0 / 0
01.12.2020, 23:48
    #40023806
flexgen
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ускорить удаление с отбором по времяшке
fkthat
0wl
много добавлений-изменений от бизнес-логики

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

Аналогично, есть у нас база, в которой в две таблицы пишется в среднем 30 миллионов записей в день в каждую. Во второй таблице присутствует поле xml. Данные храним 60 дней. Каждый partition в отдельной файловой группе. Весит это чудо больше 20 ТБ. Без partitions процесс удаления работал 24/7 и все равно не успевал удалить все. С partitions - процесс удаления занимает 1 секунду, делаем switch partition в STAGE таблицу с той же структурой что и основная таблица, потом truncate STAGE таблицы. Ну а дальше alter partition scheme next used для повторного использования освободившейся файловой группы, alter partition function split range для добавления нового partition и alter partition function merge range для удаления старого.
...
Рейтинг: 0 / 0
02.12.2020, 11:36
    #40023883
msLex
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ускорить удаление с отбором по времяшке
0wl
msLex
Лучший результат должен получиться при следующем подходе.

1. Выбираем в отдельную # таблицу очередные 1000 записей из общей # таблицы
2. Находим среди этих данных max_obj и min_obj
3. В запросе на удаление
- используем # таблицу с 1000 записями,
- добавляем условия с диапазоном min_obj - max_obj (obj >= @min_obj and obj <= @max_obj) на основную таблицу
- для надежности, добавляем option(merge join)

по идее, должен получиться план
- со сканом # таблицы из 1000 записей
- в зависимости от ключа кластерного индекса # таблицы из 1000 записей, возможно сортировка
- range seek под основной таблице
- merge join этих dataset-ов


Подумал над вашим вариантом и понял, что исходный план примерно так и работает:
1. из #to_del выбирается очередная пачка с отбором по num (номер по порядку)
2. выполняется та самая сортировка, из-за которой я написал пост. Находится минимальное и максимальное значение ключей
3. Дальше merge по найденным границам.



--
Как ни странно, последний вариант aleks222 оказался немножко лучше всех предыдущих попыток. Если сделать "белый список" и создать по нему индекс по obj, id - в плане выбирается Merge join. По суммарному числу чтений получается то же самое, что и мой исходный вариант, но нет сортировки, а число чтений из "белого списка" постепенно увеличивается от итерации к итерации - то есть, этот вариант действительно выгоден, когда в таблице удаляется на порядки больше строк, чем остается.




фильтр по min max должен быть не в merge а в clustered index scan (который превратится в clustered index seek). Подсчет min max прям в запросе не поможет, т.к. при merge join оба набора данных читаются параллельно, и к моменту получения этих min max из # таблицы, применить их в качестве seek предиката уже не возможно.
Именно поэтому требуется предварительный расчет этих значений (с вычеткой в отдельную # таблицу или без).
...
Рейтинг: 0 / 0
02.12.2020, 12:14
    #40023913
0wl
0wl
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ускорить удаление с отбором по времяшке
msLex, invm,

Да, спасибо. Я сначала думал, что merge передаст границы в нижнюю таблицу, поэтому и не заподозрил разницы. Проверил вживую - действительно, у обращения к ids появляются предикаты, он превращается в Seek и читает гораздо меньше строк.

Ну и к слову, на какой-то итерации merge все-таки превращается в hash match - но это уже детали. Посмотрю подробнее, если будет мешать - можно действительно хинт добавить.
...
Рейтинг: 0 / 0
02.12.2020, 12:54
    #40023933
aleks222
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ускорить удаление с отбором по времяшке
Судя по косвенным признакам, у тредстартера всеж есть кластерный индекс (obj, id) в основной таблице
Тредстартеру показано такое лечение:
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
SELECT obj, max(id) [id] into #oi FROM ids WITH (NOLOCK) GROUP BY obj;
declare oi cursor local fast_forward for select obj, id from #oi;
declare @obj int, @id int;
fetch next from oi into @obj, @id;
while @@FETCH_STATUS = 0 begin
    declare @rc int = 1;
    while @rc  > 0 begin
       DELETE top(3000) ids where obj = @obj and @id > id;
       set @rc = @@rowcount;
    end;
    fetch next from oi into @obj, @id;
end;



и будет ему щастье.
...
Рейтинг: 0 / 0
Форумы / Microsoft SQL Server [игнор отключен] [закрыт для гостей] / ускорить удаление с отбором по времяшке / 22 сообщений из 22, страница 1 из 1
Целевая тема:
Создать новую тему:
Автор:
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]