SQL-запрос :оптимизация внутренних соединений между большими таблицами

StackOverflow https://stackoverflow.com/questions/511452

Вопрос

У меня есть 3 следующие таблицы в базе данных MySQL 4.x :

  • хосты: (300.000 записей)
    • ПЕРВИЧНЫЙ КЛЮЧ id (UNSIGNED INT)
    • имя (VARCHAR 100)
  • пути: (6.000.000 записей)
    • ПЕРВИЧНЫЙ КЛЮЧ id (UNSIGNED INT)
    • имя (VARCHAR 100)
  • URL - адреса: (7.000.000 записей)
    • ПЕРВИЧНЫЙ КЛЮЧ хоста (UNSIGNED INT) <--- ссылки на hosts.id
    • ПЕРВИЧНЫЙ КЛЮЧ path (UNSIGNED INT) <--- ссылки на paths.id

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

Вот запрос, который я выполняю :

SELECT CONCAT(H.name, P.name)
FROM hosts AS H
INNER JOIN urls as U ON H.id = U.host
INNER JOIN paths AS P ON U.path = P.id;

Этот запрос работает отлично, но его выполнение занимает 50 минут.У кого-нибудь есть какие-нибудь идеи о том, как я мог бы ускорить этот запрос?

Заранее благодарю.Николас

Это было полезно?

Решение

Во-первых, я бы не стал выполнять ОБЪЕДИНЕНИЕ в запросе.Сделай это снаружи.

Но на самом деле ваш запрос выполняется медленно, потому что вы извлекаете миллионы строк.

Другие советы

Возможно, вам следует включить предложение WHERE?Или вам действительно нужны ВСЕ данные?

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

  • хосты :

    • имя (VARCHAR 100) ПЕРВИЧНОГО КЛЮЧА
  • пути :

    • имя (VARCHAR 100) ПЕРВИЧНОГО КЛЮЧА
  • URL - адреса :

    • ПЕРВИЧНЫЙ КЛЮЧ хоста (VARCHAR 100) <--- ссылки на hosts.name
    • путь (VARCHAR 100) ПЕРВИЧНЫЙ КЛЮЧ <--- ссылки на paths.name

Тогда ваш запрос вообще не потребовал бы объединения:

SELECT CONCAT(U.host, U.path) FROM urls U;

Правда, URL-адреса таблиц заняли бы больше места на диске - но имеет ли это значение?

Редактировать: Если подумать, то в чем вообще смысл этой таблицы ПУТЕЙ?Как часто разные хосты используют одни и те же пути?

Почему бы и нет:

  • хосты :

    • имя (VARCHAR 100) ПЕРВИЧНОГО КЛЮЧА
  • URL - адреса :

    • ПЕРВИЧНЫЙ КЛЮЧ хоста (VARCHAR 100) <--- ссылки на hosts.name
    • путь (VARCHAR 100) ПЕРВИЧНЫЙ КЛЮЧ <--- нигде нет ссылки

РЕДАКТИРОВАТЬ 2: Или если вы действительно потребность суррогатный ключ для хостов:

  • хосты :

    • идентификатор целого числа ПЕРВИЧНОГО КЛЮЧА
    • имя (VARCHAR 100)
  • URL - адреса :

    • ПЕРВИЧНЫЙ КЛЮЧ ЦЕЛОГО ЧИСЛА ХОСТА <--- ссылки на hosts.name
    • путь (VARCHAR 100) ПЕРВИЧНЫЙ КЛЮЧ <--- нигде нет ссылки

    ВЫБЕРИТЕ CONCAT(H.name, U.path) ИЗ URL-адресов U ПРИСОЕДИНИТЕ хосты H К H.id = U.host;

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

(1) Я бы сказал, что вы хотите убедиться, что индексы НЕ используются при выполнении этого запроса.Поскольку у вас нет условий фильтрации, более эффективным должно быть полное сканирование всех таблиц, а затем объединение их вместе с помощью операции сортировки-слияния или хэширования.

(2) Конкатенация строк, безусловно, занимает некоторое время, но я не понимаю, почему люди рекомендуют удалить ее.Предположительно, затем вам нужно было бы выполнить конкатенацию в другом фрагменте кода, где это все равно заняло бы примерно столько же времени (если только конкатенация строк MySQL по какой-то причине не происходит особенно медленно).

(3) Передача данных с сервера клиенту, вероятно, занимает значительное время, вполне возможно, больше, чем время, необходимое серверу для извлечения данных.Если у вас есть инструменты для отслеживания подобных вещей, используйте их.Если вы можете увеличить размер массива выборки в вашем клиенте, поэкспериментируйте с различными размерами (напримерв JDBC используйте инструкцию.setFetchSize() ).Это может быть существенно, даже если клиент и сервер находятся на одном хосте.

Я бы попытался создать новую таблицу с данными, которые вы хотите получить.Это означает, что вы потеряете некоторые реальные данные, но выиграете в скорости.Может ли эта идея быть похожа на OLAP или что-то в этом роде?

Конечно, вы должны обновлять (ежедневно или что-то еще) эту таблицу.

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

Однако есть одна вещь - я не верю, что у вас может быть два "первичных" ключа в любой таблице;по этой причине ваша таблица urls кажется мне довольно подозрительной.Прежде всего, вы должны быть абсолютно уверены, что эти два столбца в таблице urls полностью проиндексированы - одного числового индекса для каждого должно быть достаточно - потому что вы объединяете их, поэтому СУБД должна знать, как их быстро найти;возможно, именно это и происходит в вашем случае.Если вы просматриваете всю таблицу с таким количеством строк, то да, вы могли бы сидеть там довольно долго, пока сервер пытается найти все, что вы просили.

Я бы также предложил удалить эту функцию CONCAT из инструкции select и посмотреть, как это влияет на ваши результаты.Я был бы удивлен, если бы это каким-то образом не стало способствующим фактором.Просто извлеките оба столбца и обработайте конкатенацию позже, и посмотрите, как это произойдет.

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

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

Сгруппированы ли основные ключи?Это гарантирует, что данные хранятся на диске в порядке индексации, что позволяет избежать скачкообразного перемещения по разным частям диска.

Кроме того, вы можете распределить данные по нескольким дискам.Если у вас есть URL-адреса на ПЕРВИЧНОМ сервере и ПУТИ / ХОСТЫ на ВТОРИЧНОМ, то вы получите лучшую пропускную способность дисков.

Вам нужно посмотреть на конфигурацию вашего сервера.Параметры памяти по умолчанию для MySQL приведут к снижению производительности таблицы такого размера.Если вы используете значения по умолчанию, вам нужно поднять как минимум key_buffer_size и join_buffer_size по крайней мере, в 4 раза, возможно, гораздо больше.Посмотрите в документации;есть и другие параметры памяти, которые вы можете настроить.

У MySQL есть забавная особенность производительности, когда, если ваши таблицы превышают определенный размер с запросами, которые будут возвращать большую часть данных, производительность летит в унитаз.К сожалению, он не может сообщить вам, когда будет достигнут этот порог.Хотя, мне кажется, что так оно и есть.

Попробуйте оптимизировать свои таблицы перед выполнением запроса:

optimize table hosts, paths, urls;

Это может сэкономить вам некоторое время, особенно если строки были удалены из таблиц.(см . здесь для получения дополнительной информации об ОПТИМИЗАЦИИ)

Вы уже объявили некоторые индексы в атрибутах соединения?

PS:Видишь здесь [неработающая ссылка] для индексов в MySQL 4.x

Конкат определенно замедляет вас.Можем ли мы увидеть результаты объяснения mysql по этому поводу? Ссылка на документацию

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

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

проверьте время для этого запроса

select count(*)
FROM hosts AS H
INNER JOIN urls as U ON H.id = U.host
INNER JOIN paths AS P ON U.path = P.id

Если это все еще медленно, я бы пошел и проверил время для выберите count (*) из URL-адресов

тогда

select count(*) 
from urls u 
inner join hosts h on u.host = h.id

тогда

select count(*) 
from urls u 
inner join hosts h on u.host = h.id
inner join paths p on u.path = p.id

просто для того, чтобы определить источник замедления

Кроме того, иногда может помочь изменение порядка вашего запроса

SELECT CONCAT(u.host, u.path)
from urls u 
inner join hosts h on u.host = h.id
inner join paths p on u.path = p.id

Я не могу сказать наверняка о MySQL, но я знаю, что в SQL Server первичные ключи создают индекс автоматически, а внешние ключи - нет.Обязательно проверьте, есть ли индекс в ваших полях внешнего ключа.

Поскольку я не большой поклонник MySQL, я бы спросил, пробовали ли вы PostgreSQL.В этой базе данных вы хотели бы убедиться, что ваше значение work_mem было достаточно высоким, но вы можете установить его для каждого подключения к БД, например, с помощью SET work_mem = 64MB .

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

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

Если вы используете PostgreSQL, это позволит вам использовать JOIN USING, но даже в MySQL мне это нравится больше:назовите свое поле id одинаковым в каждой таблице.Вместо id в hosts и host в URL-адресах назовите его host_id в обоих местах.

Теперь еще несколько комментариев.:) Этот макет данных, который у вас здесь есть, очень полезен, когда вы выбираете небольшой набор строк, возможно, каждый URL-адрес из одного и того же домена.Это также может помочь лот если вашим запросам часто требуется выполнять последовательные проверки таблицы urls на наличие других хранящихся там данных, потому что сканирование может пропустить большие текстовые поля (если только это не имеет значения, потому что ваша база данных в любом случае хранит текст через указатели на связанную таблицу).

Однако, если вы почти всегда выбираете все данные домена и пути, то имеет смысл хранить их в одной таблице.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top