SQL-запрос: оптимизация внутренних соединений между большими таблицами - PullRequest
6 голосов
/ 04 февраля 2009

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

  • хосты: (300 000 записей)
    • id (НЕ ПОДПИСАНО INT) ПЕРВИЧНЫЙ КЛЮЧ
    • имя (VARCHAR 100)
  • путей: (6.000.000 записей)
    • id (НЕ ПОДПИСАНО INT) ПЕРВИЧНЫЙ КЛЮЧ
    • имя (VARCHAR 100)
  • URL: (7.000.000 записей)
    • host (UNSIGNED INT) PRIMARY KEY <--- ссылки на hosts.id </li>
    • путь (UNSIGNED INT) ПЕРВИЧНЫЙ КЛЮЧ <--- ссылки на paths.id </li>

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

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

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

Заранее спасибо. Nicolas

Ответы [ 14 ]

6 голосов
/ 04 февраля 2009

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

4 голосов
/ 04 февраля 2009

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

  • хосты:

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

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

    • host (VARCHAR 100) PRIMARY KEY <--- ссылки на hosts.name </li>
    • путь (VARCHAR 100) ПЕРВИЧНЫЙ КЛЮЧ <--- ссылки на пути. Имя </li>

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

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

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

РЕДАКТИРОВАТЬ: Если подумать, какой смысл в этой таблице PATHS в любом случае? Как часто разные хосты используют одни и те же пути?

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

  • хосты:

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

    • host (VARCHAR 100) PRIMARY KEY <--- ссылки на hosts.name </li>
    • путь (VARCHAR 100) ПЕРВИЧНЫЙ КЛЮЧ <--- нет ссылки никуда </li>

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

  • хосты:

    • id целое число ПЕРВИЧНЫЙ КЛЮЧ
    • имя (VARCHAR 100)
  • URL-адреса:

    • host integer PRIMARY KEY <--- ссылки на hosts.name </li>
    • путь (VARCHAR 100) ПЕРВИЧНЫЙ КЛЮЧ <--- нет ссылки никуда </li>

    ВЫБРАТЬ КОНКАТ (H.name, U.path) ОТ URL U ПРИСОЕДИНЯЙТЕСЬ к хостам H ON H.id = U.host;

2 голосов
/ 04 февраля 2009

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

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

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

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

2 голосов
/ 04 февраля 2009

Во-первых, я бы не стал использовать CONCAT в запросе. Сделай это снаружи.

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

1 голос
/ 06 февраля 2009

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

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

1 голос
/ 04 февраля 2009

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

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

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

1 голос
/ 04 февраля 2009

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

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

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

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

1 голос
/ 04 февраля 2009

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

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

1 голос
/ 04 февраля 2009

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

optimize table hosts, paths, urls;

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

1 голос
/ 04 февраля 2009

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

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

...