SQL: внутреннее соединение двух больших таблиц - PullRequest
23 голосов
/ 17 ноября 2009

У меня есть две массивные таблицы с примерно 100 миллионами записей в каждой, и я боюсь, что мне нужно было выполнить внутреннее соединение между ними. Теперь обе таблицы очень просты; вот описание:

Таблица BioEntity:

  • BioEntityId (int)
  • Имя (nvarchar 4000, хотя это перебор)
  • TypeId (int)

Таблица EGM (фактически, дополнительная таблица, полученная в результате операций массового импорта):

  • EMGId (int)
  • PId (int)
  • Имя (nvarchar 4000, хотя это перебор)
  • TypeId (int)
  • LastModified (дата)

Мне нужно получить соответствующее Имя, чтобы связать BioEntityId с PId, находящимся в таблице EGM. Первоначально я пытался сделать все с одним внутренним объединением, но запрос, казалось, занимал слишком много времени, и лог-файл базы данных (в простом режиме восстановления) сумел уничтожить все доступное дисковое пространство (это чуть более 200 ГБ, если база данных занимает 18 ГБ) и запрос не будет выполнен через два дня ожидания, если я не ошибаюсь. Мне удалось предотвратить рост журнала (всего 33 МБ сейчас), но запрос выполнялся безостановочно уже 6 дней, и не похоже, что он скоро остановится.

Я использую его на довольно приличном компьютере (4 ГБ ОЗУ, Core 2 Duo (E8400) 3GHz, Windows Server 2008, SQL Server 2008), и я заметил, что компьютер иногда зависает каждые 30 секунд (да или нет) ) на пару секунд. Это делает его довольно трудным для чего-то другого, что действительно действует мне на нервы.

Теперь вот запрос:

 SELECT EGM.Name, BioEntity.BioEntityId INTO AUX
 FROM EGM INNER JOIN BioEntity 
 ON EGM.name LIKE BioEntity.Name AND EGM.TypeId = BioEntity.TypeId

Я вручную настроил некоторые индексы; и EGM, и BioEntity имели некластеризованный индекс покрытия, содержащий TypeId и Name. Однако запрос выполнялся в течение пяти дней, и он не заканчивался , поэтому я попытался запустить помощник по настройке базы данных, чтобы заставить его работать. Он предложил удалить мои старые индексы и вместо этого создать статистику и два кластеризованных индекса (по одному на каждую таблицу, просто содержащий TypeId, который я нахожу довольно странным - или просто тупым - но я все равно попробовал).

Он работает уже 6 дней, и я до сих пор не знаю, что делать ... Есть идеи, ребята? Как я могу сделать это быстрее (или, по крайней мере, конечно)?

Обновление: - Хорошо, я отменил запрос и перезагрузил сервер, чтобы снова запустить и запустить ОС - Я перезапускаю рабочий процесс с вашими предлагаемыми изменениями, в частности, обрезая поле nvarchar до гораздо меньшего размера и заменяя «как» на «=». Это займет не менее двух часов, поэтому я буду публиковать дальнейшие обновления позже

Обновление 2 (13:00 по Гринвичу, 18.11.09): - Предполагаемый план выполнения показывает 67% затрат на сканирование таблиц с последующим совпадением хэшей на 33%. Далее идет параллелизм 0% (не странно ли это? Это первый раз, когда я использую примерный план выполнения, но этот конкретный факт просто поднял мою бровь), 0% совпадения хэша, больше параллелизма 0%, 0% вершины, 0 % table insert и, наконец, еще 0% выберите. Кажется, что индексы, как и ожидалось, дерьмовые, поэтому я буду делать ручные индексы и откажусь от дерьмовых предложенных.

Ответы [ 16 ]

1 голос
/ 18 ноября 2009

Я вручную настроил некоторые индексы; и EGM, и BioEntity имели некластеризованный индекс покрытия, содержащий TypeId и Name. Тем не менее, запрос выполнялся в течение пяти дней, и он также не заканчивался, поэтому я попытался запустить помощник по настройке базы данных, чтобы заставить его работать. Он предложил удалить мои старые индексы и вместо этого создать статистику и два кластеризованных индекса (по одному на каждую таблицу, просто содержащий TypeId, который я нахожу довольно странным - или просто тупым - но я все равно попробовал).

Вы сказали, что сделали кластеризованный индекс для TypeId в обеих таблицах, хотя, похоже, у вас уже есть первичный ключ для каждой таблицы (BioEntityId и EGMId, соответственно). Вы не хотите, чтобы ваш TypeId был кластеризованным индексом для этих таблиц. Вы хотите, чтобы BioEntityId & EGMId были кластеризованы (это будет физически сортировать ваши данные в порядке кластерного индекса на диске. Вы хотите некластеризованных индексов для внешних ключей, которые вы будете использовать для поиска. Т.е. TypeId. Попробуйте сделать первичные ключи кластеризованными и добавить некластеризованный индекс для обеих таблиц, который ТОЛЬКО СОДЕРЖИТ TypeId.

В нашей среде у нас есть таблицы с примерно 10-20 миллионами записей в каждой. Мы делаем много запросов, аналогичных вашим, где мы объединяем два набора данных в один или два столбца. Добавление индекса для каждого внешнего ключа должно сильно помочь с вашей производительностью.

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

K. У Скотта есть довольно хорошая статья здесь , которая более подробно объясняет некоторые проблемы.

1 голос
/ 18 ноября 2009

есть ли у вас первичные ключи или индексы? Вы можете выбрать его поэтапно? то есть где имя, например, «A%», где имя, например, «B%» и т. д.

1 голос
/ 17 ноября 2009

100 миллионов записей ОГРОМНЫ. Я бы сказал, что для работы с такой большой базой данных вам потребуется выделенный тестовый сервер. Использование той же машины для выполнения другой работы при выполнении подобных запросов нецелесообразно.

Ваше аппаратное обеспечение достаточно работоспособно, но для таких больших соединений, чтобы прилично работать, вам потребуется еще больше энергии. Хорошо бы начать с четырехъядерной системы с 8 ГБ. Кроме того, вы должны убедиться, что ваши индексы настроены правильно.

0 голосов
/ 19 ноября 2009

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

Использование пакетов также в простом режиме восстановления предотвратит рост вашего транша. Даже в простом режиме восстановления огромное объединение, которое вы делаете, будет занимать много места, потому что оно должно держать всю транзакцию открытой, тогда как при выполнении пакетов оно может повторно использовать файл журнала для каждого пакета, ограничивая его размер до самого большого, необходимого для одна партия.

Если вам действительно нужно объединиться по имени, то вы можете рассмотреть некоторые вспомогательные таблицы, которые преобразуют имена в идентификаторы, в основном восстанавливая денормализованный дизайн временно (если вы не можете восстановить его навсегда).

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

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

0 голосов
/ 18 ноября 2009

Интересно, заняло ли время выполнения соединение или передача данных.

Предполагается, что средний размер данных в столбце «Имя» составляет 150 символов, в действительности у вас будет 300 байт плюс другие столбцы на запись. Умножьте это на 100 миллионов записей, и вы получите около 30 ГБ данных для передачи вашему клиенту. Вы запускаете клиент удаленно или на самом сервере? Возможно, вы ждете 30 ГБ данных, передаваемых вашему клиенту ...

РЕДАКТИРОВАТЬ: Хорошо, я вижу, вы вставляете в таблицу Aux. Какова настройка модели восстановления базы данных?

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

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

0 голосов
/ 17 ноября 2009

Поскольку вы не просите БД выполнять какие-либо необычные реляционные операции, вы можете легко написать это. Вместо того, чтобы убивать БД с помощью массивного, но простого запроса, попробуйте экспортировать две таблицы (вы можете получить автономные копии из резервных копий?).

Как только вы экспортируете таблицы, напишите сценарий для выполнения этого простого объединения. Это займет примерно столько же времени, но не убьет БД.

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

Для сценария вы захотите проиндексировать больший набор данных, затем выполнить итерацию по меньшему набору данных и выполнить поиск по большому индексу набора данных. Это будет O (n * m), чтобы бежать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...