Попытка оптимизировать запрос и правильно индексировать таблицы - PullRequest
1 голос
/ 03 января 2011

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

Прежде чем публиковать sql таблиц и используемый мной запрос, позвольте мне объяснить, что к чему. У меня есть таблица пользователя, которая заполнена 100 000 записей. Большинство столбцов в нем имеют тип enum, например цвет волос, Looking_for и т. Д. Первый запрос, который я имею, генерируется, когда поиск завершен. Запрос будет состоять из оператора where, в котором ищутся некоторые или все значения столбцов и извлекаются только идентификаторы, ограниченные 20.

Тогда у меня есть еще 3 таблицы, которые содержат примерно 50 - 1000 записей на каждого пользователя, так что число может действительно возрасти. в этих таблицах хранится информация о том, кто посещал профиль кого, кто пометил кто в избранном, кто заблокировал кого, и таблица сообщений. Моя цель - получить 20 записей, которые соответствуют критериям поиска, но также определить, есть ли у меня (пользователя, просматривающего):

  1. заблокировал их
  2. одобрил их
  3. им понравились
  4. имеют непрочитанные сообщения от них
  5. отправил или получил от них какие-либо сообщения

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

CREATE TABLE user (id BIGINT AUTO_INCREMENT, dname VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL UNIQUE, email_code VARCHAR(255), email_confirmed TINYINT(1) DEFAULT '0', password VARCHAR(255) NOT NULL, gender ENUM('male', 'female'), description TEXT, dob DATE, height MEDIUMINT, looks ENUM('thin', 'average', 'athletic', 'heavy'), looking_for ENUM('marriage', 'dating', 'friends'), looking_for_age1 BIGINT, looking_for_age2 BIGINT, color_hair ENUM('black', 'brown', 'blond', 'red'), color_eyes ENUM('black', 'brown', 'blue', 'green', 'grey'), marital_status ENUM('single', 'married', 'divorced', 'widowed'), smokes ENUM('no', 'yes', 'sometimes'), drinks ENUM('no', 'yes', 'sometimes'), has_children ENUM('no', 'yes'), wants_children ENUM('no', 'yes'), education ENUM('school', 'college', 'university', 'masters', 'phd'), occupation ENUM('no', 'yes'), country_id BIGINT, city_id BIGINT, lastlogin_at DATETIME, deleted_at DATETIME, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, INDEX country_id_idx (country_id), INDEX city_id_idx (city_id), INDEX image_id_idx (image_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = INNODB;

CREATE TABLE block (id BIGINT AUTO_INCREMENT, blocker_id BIGINT, blocked_id BIGINT, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, INDEX blocker_id_idx (blocker_id), INDEX blocked_id_idx (blocked_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = INNODB;

CREATE TABLE city (id BIGINT AUTO_INCREMENT, name_eng VARCHAR(30), name_geo VARCHAR(30), name_geo_shi VARCHAR(30), name_geo_is VARCHAR(30), country_id BIGINT NOT NULL, active TINYINT(1) DEFAULT '0', INDEX country_id_idx (country_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = INNODB;

CREATE TABLE country (id BIGINT AUTO_INCREMENT, code VARCHAR(2), name_eng VARCHAR(30), name_geo VARCHAR(30), name_geo_shi VARCHAR(30), name_geo_is VARCHAR(30), active TINYINT(1) DEFAULT '1', PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = INNODB;

CREATE TABLE favorite (id BIGINT AUTO_INCREMENT, favoriter_id BIGINT, favorited_id BIGINT, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, INDEX favoriter_id_idx (favoriter_id), INDEX favorited_id_idx (favorited_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = INNODB;

CREATE TABLE message (id BIGINT AUTO_INCREMENT, body TEXT, sender_id BIGINT, receiver_id BIGINT, read_at DATETIME, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, INDEX sender_id_idx (sender_id), INDEX receiver_id_idx (receiver_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = INNODB;

CREATE TABLE visitor (id BIGINT AUTO_INCREMENT, visitor_id BIGINT, visited_id BIGINT, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, INDEX visitor_id_idx (visitor_id), INDEX visited_id_idx (visited_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = INNODB;


SELECT s.id AS s__id FROM user s WHERE (s.gender = 'female' AND s.marital_status = 'single' AND s.smokes = 'no' AND s.deleted_at IS NULL) LIMIT 20

SELECT s.id AS s__id, s.dname AS s__dname, s.gender AS s__gender, s.height AS s__height, s.dob AS s__dob, s3.id AS s3__id, s3.code AS s3__code, s3.name_geo AS s3__name_geo, s4.id AS s4__id, s4.name_geo AS s4__name_geo, s5.id AS s5__id, s6.id AS s6__id, s7.id AS s7__id, s8.id AS s8__id, s9.id AS s9__id FROM user s LEFT JOIN country s3 ON s.country_id = s3.id LEFT JOIN city s4 ON s.city_id = s4.id LEFT JOIN block s5 ON ((s.id = s5.blocked_id AND s5.blocker_id = '1')) LEFT JOIN favorite s6 ON ((s.id = s6.favorited_id AND s6.favoriter_id = '1')) LEFT JOIN favorite s7 ON ((s.id = s7.favoriter_id AND s7.favorited_id = '1')) LEFT JOIN message s8 ON ((s.id = s8.sender_id AND s8.receiver_id = '1' AND s8.read_at IS NULL)) LEFT JOIN message s9 ON (((s.id = s9.sender_id AND s9.receiver_id = '1') OR (s.id = s9.receiver_id AND s9.sender_id = '1'))) WHERE (s.id IN ('22', '36', '53', '105', '152', '156', '169', '182', '186', '192', '201', '215', '252', '287', '288', '321', '330', '351', '366', '399')) GROUP BY s.id ORDER BY s.id

Вот результаты ОБЪЯСНЕНИЯ из 2 запросов выше:

Первое:

1   SIMPLE  s   ALL NULL    NULL    NULL    NULL    100420  Using Where

Второе:

1   SIMPLE  s   range   PRIMARY PRIMARY 8   NULL    20  Using where; Using temporary; Using filesort
1   SIMPLE  s2  eq_ref  PRIMARY PRIMARY 8   sagule.s.image_id   1   Using index
1   SIMPLE  s3  eq_ref  PRIMARY PRIMARY 8   sagule.s.country_id 1
1   SIMPLE  s4  eq_ref  PRIMARY PRIMARY 8   sagule.s.city_id    1
1   SIMPLE  s5  ref blocker_id_idx,blocked_id_idx   blocked_id_idx  9   sagule.s.id 5
1   SIMPLE  s6  ref favoriter_id_idx,favorited_id_idx   favorited_id_idx    9   sagule.s.id 6
1   SIMPLE  s7  ref favoriter_id_idx,favorited_id_idx   favoriter_id_idx    9   sagule.s.id 6
1   SIMPLE  s8  ref sender_id_idx,receiver_id_idx   sender_id_idx   9   sagule.s.id 7
1   SIMPLE  s9  index_merge sender_id_idx,receiver_id_idx   receiver_id_idx,sender_id_idx   9,9 NULL    66  Using union(receiver_id_idx,sender_id_idx); Using where

Ответы [ 3 ]

3 голосов
/ 03 января 2011

Я - парень MSSQL и не использовал mysql, но концепции должны быть одинаковыми.

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

SELECT  s.id AS s__id, 
        s.dname AS s__dname, 
        s.gender AS s__gender, 
        s.height AS s__height, 
        s.dob AS s__dob
--      s3.id AS s3__id, 
--      s3.code AS s3__code, 
--      s3.name_geo AS s3__name_geo, 
--      s4.id AS s4__id, 
--      s4.name_geo AS s4__name_geo, 
--      s5.id AS s5__id, 
--      s6.id AS s6__id, 
--      s7.id AS s7__id, 
--      s8.id AS s8__id, 
--      s9.id AS s9__id 
FROM    user s --LEFT JOIN 
--      country s3 ON s.country_id = s3.id LEFT JOIN 
--      city s4 ON s.city_id = s4.id LEFT JOIN 
--      block s5 ON ((s.id = s5.blocked_id AND s5.blocker_id = '1')) LEFT JOIN 
--      favorite s6 ON ((s.id = s6.favorited_id AND s6.favoriter_id = '1')) LEFT JOIN 
--      favorite s7 ON ((s.id = s7.favoriter_id AND s7.favorited_id = '1')) LEFT JOIN 
--      message s8 ON ((s.id = s8.sender_id AND s8.receiver_id = '1' AND s8.read_at IS NULL)) LEFT JOIN 
--      message s9 ON (((s.id = s9.sender_id AND s9.receiver_id = '1') OR (s.id = s9.receiver_id AND s9.sender_id = '1'))) 
        WHERE (s.id IN ('22', '36', '53', '105', '152', '156', '169', '182', '186', '192', '201', '215', '252', '287', '288', '321', '330', '351', '366', '399')) 

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

SELECT  s.id AS s__id, 
        s.dname AS s__dname, 
        s.gender AS s__gender, 
        s.height AS s__height, 
        s.dob AS s__dob,
        s3.id AS s3__id, 
        s3.code AS s3__code, 
        s3.name_geo AS s3__name_geo 
--      s4.id AS s4__id, 
--      s4.name_geo AS s4__name_geo, 
--      s5.id AS s5__id, 
--      s6.id AS s6__id, 
--      s7.id AS s7__id, 
--      s8.id AS s8__id, 
--      s9.id AS s9__id 
FROM    user s LEFT JOIN 
        country s3 ON s.country_id = s3.id --LEFT JOIN 
--      city s4 ON s.city_id = s4.id LEFT JOIN 
--      block s5 ON ((s.id = s5.blocked_id AND s5.blocker_id = '1')) LEFT JOIN 
--      favorite s6 ON ((s.id = s6.favorited_id AND s6.favoriter_id = '1')) LEFT JOIN 
--      favorite s7 ON ((s.id = s7.favoriter_id AND s7.favorited_id = '1')) LEFT JOIN 
--      message s8 ON ((s.id = s8.sender_id AND s8.receiver_id = '1' AND s8.read_at IS NULL)) LEFT JOIN 
--      message s9 ON (((s.id = s9.sender_id AND s9.receiver_id = '1') OR (s.id = s9.receiver_id AND s9.sender_id = '1'))) 
        WHERE (s.id IN ('22', '36', '53', '105', '152', '156', '169', '182', '186', '192', '201', '215', '252', '287', '288', '321', '330', '351', '366', '399')) 

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

Для таблицы блоков: Можете ли вы удалить один из индексов и изменить другой, чтобы он был чем-то похожим на(Я не уверен в синтаксисе, но вы поймете смысл)

INDEX blocker_id_idx (blocker_id,blocked_id),

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

INDEX blocker_id_idx (blocked_id,blocker_id),

Для любимой таблицы измените индексы на

INDEX favoriter_id_idx (favoriter_id,favorited_id), 
INDEX favorited_id_idx (favorited_id,favoriter_id), 

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

Сделайте это и дайте мне знать, если что-то улучшилось.Есть несколько других вещей, которые можно сделать, чтобы улучшить его дальше.- РЕДАКТИРОВАТЬ: Кажется, я солгал о нескольких других вещах, то, что я намеревался, не имело бы никакого значения.Но я могу ускорить ваш первый запрос, который приведен ниже.

РЕДАКТИРОВАТЬ Это для вашего первого запроса выбора.

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

Допустим, таблица содержит 100 000 строк.

Когда вы выбираете из него, это общий процесс, который он займет.

  • Существуют ли какие-либо индексы, которые покрывают или в основном покрывают нужные мне столбцы.(В вашем случае, нет, нет.)
  • Поэтому используйте Первичный индекс и сканируйте каждую строку в таблице, чтобы проверить на совпадение.
  • Каждая строка в таблице должначитать с диска, чтобы найти, какие столбцы соответствуют вашим критериям.Таким образом, чтобы вернуть примерно 10000 строк (это предположение), соответствующих вашим данным, ядро ​​базы данных прочитало все 100 000 строк.

У вас есть 20 лучших в вашем запросе, поэтому он ограничитколичество строк, которые двигатель будет читать с диска.Пример

  • чтение строки 1: совпадение, поэтому добавьте к результату
  • чтение строки 2: нет совпадений - пропуск
  • чтение строки 3: нет совпадений - пропуск
  • чтение строки 4: совпадение, поэтому добавьте к результату.
  • останов после 20 идентифицированных строк

Возможно, вы прочитали около 5000 строк с диска, чтобы вернуть 20.

Нам нужно создать индекс, который поможет нам читать как можно меньше записей из таблицы / диска, но при этом получать строки, которые нам нужны.Итак, пошли.

Ваш запрос использует 4 фильтра для доступа к данным.

s.gender = 'female' AND 
s.marital_status = 'single' AND 
s.smokes = 'no' AND 
s.deleted_at IS NULL

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

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

Теперь для семейного положения, поддерживает четыре значения, поэтому, если мы скажем, что данные имеют одинаковый разброс, это будет означать, что мы получим примерно 25 000 строк назад.Конечно, это зависит от фактических данных, и я бы сказал, что не слишком много овдовевших данных, поэтому лучшая оценка может составить 30% от остальных трех.Итак, допустим, 30 000 записей помечены как одиночные.

Теперь для столбца дымов.Я читал, что здесь, в Австралии, около 10% людей курят, что довольно мало по сравнению с другими странами.Так скажем, 25% или курят или курят иногда.Это оставляет нам около 75 000 некурящих.

Теперь для последнего столбца удалено.Честное предположение с моей стороны, но допустим, 5% помечены как удаленные.Это оставляет нам около 95 000 строк.

Итак, в заключение (помните, что это все чисто догадки с моей стороны, ваши данные могут отличаться) Пол 50 000 строк или 50% Семейное положение 30 000 строк или 30% курит75 000 строк или 75% удалено 95 000 строк или 95%

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

INDEX index01_idx (marital_status,gender,smokes,deleted_at),

Теперь это то, что произойдет, когда мы запустим select.

  • Сервер найдет индекс, охватывающий все столбцы в предложении WHERE
  • . Он сузитнабор результатов равен 30 000 «одиночных» записей.
  • Из этих 30 000 50% будут женщины, которые оставят 15 000 записей
  • Из этих 15 000 75% не будут курить, оставляя 11 250 записей
  • Из этих 11 250, 95% не будут удалены,

Это оставляет нам чуть более 10 000 записей из 100 000, которые мы определили как записи, которые мы хотим, но еще не прочиталис диска.У вас также есть ограничение 20 в запросе, поэтому ядру базы данных просто нужно прочитать первые 20 из 10000 и вернуть результат.Его супер быстрый, жесткий диск будет любить вас, и страшный администратор базы данных даже бормотать и хрюкать с одобрением.

1 голос
/ 03 января 2011

Без каких-либо данных для тестирования, это не так легко сделать хороший совет.

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

Я не знаю, какие инструменты вы используете, но в MySQL Workbench есть команда «Explain Current Statement» под «Query» -Menu. Там вы можете увидеть, какие действия были выполнены MySQL и какие ключи были использованы. Ваш запрос показывает "null", что означает, что ключ не использовался, и MySQL должен был пройти через все данные в сравнении с поисковым термином.

Надеюсь, это немного поможет.

1 голос
/ 03 января 2011

Во втором запросе SELECT вы можете удалить предложение GROUP BY, поскольку вы не используете никаких агрегатных функций (count, min, max ...) в своем предложении SELECT.

Я сомневаюсь, что это поможет значительно улучшить производительность.

В любом случае, я рекомендую посмотреть первую половину этого доклада «Взгляд на инструментарий администратора базы данных MySQL». (Первые две трети видео о бесплатных инструментах администратора с открытым исходным кодом для mysql в Unix, последняя треть или около того о репликации)

Видео Просмотр инструментария администратора базы данных MySQL

Из той же речи: Руководство по пониманию mysqlreport

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