Имея SourceTable
записи> 15MM и записи Bad_Phrase
> 3K, для выполнения следующего запроса на SQL Server 2005 SP4 требуется почти 10 часов.
UPDATE [SourceTable]
SET
Bad_Count=
(
SELECT
COUNT(*)
FROM Bad_Phrase
WHERE
[SourceTable].Name like '%'+Bad_Phrase.PHRASE+'%'
)
В английском языке этот запрос подсчитывает количество различных фраз, перечисленных в Bad_Phrase, которые являются подстрокой поля Name
в, SourceTable
и затем помещает этот результат в поле Bad_Count
.
Я хотел бы получить несколько советов о том, как сделать этот запрос значительно быстрее.
sql-server
sql-server-2005
update
subquery
Мэтью Лерер
источник
источник
Ответы:
Хотя я согласен с другими комментаторами в том, что это вычислительно дорогая проблема, я думаю, что есть много возможностей для улучшения путем настройки используемого вами SQL. Чтобы проиллюстрировать это, я создал поддельный набор данных с именами по 15 ММ и фразами по 3 КБ, применил старый подход и применил новый подход.
Полный сценарий для создания поддельного набора данных и опробовать новый подход
TL; DR
На моей машине и на этом поддельном наборе данных первоначальный подход занимает около 4 часов . Предлагаемый новый подход занимает около 10 минут , значительное улучшение. Вот краткое резюме предложенного подхода:
Оригинальный подход: алгоритмический анализ
Из плана исходного
UPDATE
утверждения видно, что объем работы линейно пропорционален как количеству имен (15 мм), так и количеству фраз (3 КБ). Таким образом, если мы умножим количество имен и фраз на 10, общее время выполнения будет примерно в 100 раз медленнее.Запрос на самом деле пропорционален длине
name
; хотя это и немного скрыто в плане запроса, в «количестве выполнений» он находит поиск в спуле таблицы. В фактическом плане мы можем видеть, что это происходит не один раз для каждогоname
, а фактически один раз для каждого смещения символа в пределахname
. Таким образом, этот подход O (# names
*# phrases
*name length
) в сложности времени выполнения.Новый подход: код
Этот код также доступен в полном Pastebin , но я скопировал его здесь для удобства. Pastebin также имеет полное определение процедуры, который включает
@minId
и@maxId
переменные , которые вы видите ниже , чтобы определить границы текущей партии.Новый подход: планы запросов
Сначала мы генерируем подстроку, начиная с каждого смещения символа
Затем создайте кластерный индекс для этих подстрок
Теперь для каждой плохой фразы мы ищем эти подстроки, чтобы определить совпадения. Затем мы вычисляем количество различных плохих фраз, которые соответствуют одной или нескольким подстрокам этой строки. Это действительно ключевой шаг; из-за того, как мы проиндексировали подстроки, нам больше не нужно проверять полный перекрестный продукт плохих фраз и имен. Этот шаг, который выполняет фактические вычисления, составляет только около 10% фактического времени выполнения (остальное - предварительная обработка подстрок).
Наконец, выполните фактический оператор обновления, используя a,
LEFT OUTER JOIN
чтобы присвоить счетчик 0 любым именам, для которых мы не нашли плохих фраз.Новый подход: алгоритмический анализ
Новый подход можно разделить на две фазы: предварительная обработка и сопоставление. Давайте определим следующие переменные:
N
= количество именB
= количество плохих фразL
= средняя длина имени в символахФаза предварительной обработки состоит
O(N*L * LOG(N*L))
в том, чтобы создатьN*L
подстроки и затем отсортировать их.Фактическое соответствие -
O(B * LOG(N*L))
это поиск подстрок для каждой плохой фразы.Таким образом, мы создали алгоритм, который не масштабируется линейно с количеством плохих фраз, ключевая разблокировка производительности при масштабировании до 3K фраз и выше. Иными словами, исходная реализация занимает примерно 10 раз, если перейти от 300 плохих фраз к 3K плохим фразам. Точно так же потребовалось бы еще 10x, если бы мы перешли с 3K плохих фраз до 30K. Новая реализация, однако, будет увеличиваться сублинейно и на самом деле занимает менее чем в 2 раза больше времени, измеренного для 3K плохих фраз, когда масштабируется до 30K плохих фраз.
Предположения / предостережения
SORT
подстроки были независимыми для каждого пакета и легко помещались в памяти. Вы можете манипулировать размером пакета по мере необходимости, но было бы неразумно пробовать все 15-миллиметровые строки в одном пакете.Связанное сообщение в блоге
Аарон Бертран более подробно рассматривает этот тип решения в своем посте в блоге. Один из способов получить индексный поиск по ведущему символу% .
источник
Давайте на секунду отложим очевидную проблему, поднятую Аароном Бертраном в комментариях:
Тот факт, что ваш подзапрос использует подстановочные знаки с обеих сторон, сильно влияет на саргиваемость . Чтобы взять цитату из этого сообщения в блоге:
Замените слово «орех» для каждого «плохого слова» и «Продукт» для
SourceTable
, затем объедините это с комментарием Аарона, и вы должны начать понимать, почему чрезвычайно трудно (читать невозможно) заставить его работать быстро, используя ваш текущий алгоритм.Я вижу несколько вариантов:
В зависимости от ваших требований я бы порекомендовал вариант 3 или 4.
источник
во-первых, это просто странное обновление
Как '%' + [Bad_Phrase]. [PHRASE] убивает вас,
который не может использовать индекс
Дизайн данных не является оптимальным для скорости
Можете ли вы разбить [Bad_Phrase]. [PHRASE] на одну фразу (фразы) / слово?
Если одна и та же фраза / слово встречаются более одного раза, вы можете ввести их несколько раз, если хотите, чтобы они имели большее число.
Таким образом, число строк в неправильной фазе будет увеличиваться.
Если вы сможете, то это будет намного быстрее.
Не уверен, что 2005 поддерживает, но полнотекстовый индекс и использовать Содержит
источник