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

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

  • хозяева: (300.000 записей)
    • id (UNSIGNED INT) первичный ключ
    • имя (VARCHAR 100)
  • пути: (6.000.000 записей)
    • id (UNSIGNED INT) первичный ключ
    • имя (VARCHAR 100)
  • urls:(7.000.000 записей)
    • host (UNSIGNED INT) PRIMARY Ключ
    • путь (UNSIGNED INT) первичный ключ

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

вот запрос, который я запускаю:

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 минут. Кто-нибудь знает, как я могу ускорить этот запрос?

спасибо заранее. Николя!--2-->

14 ответов


во-первых, я бы не сделал КОНКАТ в запросе. Сделай это снаружи.

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


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


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

  • хозяева :

    • имя (VARCHAR 100) первичный ключ
  • пути :

    • имя (VARCHAR 100) первичный ключ
  • urls:

    • host (VARCHAR 100) первичный ключ
    • путь (VARCHAR 100) Первичный ключ

тогда ваш запрос не потребует никаких соединений вообще:

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

True, URL-адреса таблиц занимают больше места на диске , но имеет ли это значение?

EDIT: во-вторых, какой смысл в этой таблице путей? Как часто разные хосты используют одни и те же пути?

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

  • хозяева :

    • имя (VARCHAR 100) ПЕРВИЧНЫЙ КЛЮЧ
  • urls:

    • host (VARCHAR 100) первичный ключ
    • путь (VARCHAR 100) первичный ключ

EDIT2: или, если вы действительно нужно суррогатный ключ для хостов:

  • хозяева :

    • id целочисленный первичный ключ
    • имя (VARCHAR 100)
  • urls:

    • host integer PRIMARY KEY
    • путь (VARCHAR 100) первичный ключ

    выберите CONCAT (H. name, U. path) из urls U Присоединяйтесь к хостам H на H. id = U. host;


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

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

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

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


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

конечно, вы должны сделать обновление (ежедневно или что-то еще) этой таблицы.


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

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

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

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


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

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

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


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

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


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

optimize table hosts, paths, urls;

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


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

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


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

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


Я понимаю, что вам нужен полный список URL - адресов, который составляет 7 миллионов записей. Возможно!--5-->как подслащенный Митчем вы должны рассмотреть возможность использования предложения 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, например.

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

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

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

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

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