MySQL - Left Join занимает слишком много времени, как оптимизировать запрос? - PullRequest
0 голосов
/ 26 апреля 2018

Лидер может иметь много последователей.Таблица notification_followers получает одно уведомление, когда лидер добавляет сообщение с записями leader_id 1 и notifiable_id 0 (id 1,2 в таблице).Эта же таблица получает одно уведомление, когда за кем-то следует текущий пользователь 14, с записью leader_id 0 и notifiable_id 14 (идентификатор 3 в таблице).

notification_followers ( idявляется ПЕРВИЧНЫМ, каждое поле, за исключением данных, является самостоятельным индексом )

| id | uuid               | leader_id | notifable_id | data   | created_at
-----------------------------------------------------------------------------------
| 1  | 001w2cwfoqzp8F3... | 1         | 0            | Post A | 2018-04-19 00:00:00
| 2  | lvbuX4d5qCHJUIN... | 1         | 0            | Post B | 2018-04-20 00:00:00
| 3  | eEq5r5g5jApkKgd... | 0         | 14           | Follow | 2018-04-21 00:00:00

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

Нам нужно сейчаспроверьте, является ли пользователь 14 подписчиком leader_id 1, чтобы узнать, показывать ли ему уведомления 1 и 2.Для этого мы сканируем таблицу user_follows, чтобы определить, существует ли зарегистрированный пользователь как followed_id для leader_id, чтобы они знали об уведомлении, но только если они следовали за лидером до уведомление было опубликовано (новые подписчики не должны получать старые сообщения, когда подписчик подписан, только новые).

user_follows (id является ПЕРВИЧНЫМ, каждое поле является индексом самостоятельно)

| id | leader_id | follower_id | created_at
----------------------------------------------------
| 1  | 1         | 14         |  2018-04-18 00:00:00 // followed before, has notifs
| 2  | 1         | 15         |  2018-04-22 00:00:00 // followed after, no notifs

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

notification_followers_read (составной указатель для messages_uuid, follower_id)

| notification_uuid | follower_id | read_at
--------------------------------------------------------
  qIXE97AP49muZf... | 17          | 2018-04-21 00:00:00 // not for 14, we ignore it

Теперь мы хотим вернуть последние 10 уведомлений, упорядоченных с автоматическим увеличением nf.id descдля пользователя 14.Они должны видеть все 3 уведомления от notification_followers, поскольку никто из них не был прочитан этим пользователем еще .Первые 2, так как они следовали за лидером до , лидер сделал посты, и 3-е уведомление, так как они следовали, и их notifiable_id составляет 14.

Вотзапрос, который работает, но занимает слишком много времени ~ 9 секунд :

SELECT nf.id, nf.uuid, nf.leader_id, nf.data, nf.created_at, nfr.read_at
FROM notification_followers nf
LEFT JOIN user_follows uf ON uf.leader_id = nf.leader_id AND uf.follower_id = 14
LEFT JOIN notification_followers_read nfr ON nf.uuid = nfr.notification_uuid AND nfr.follower_id = 14
WHERE (nf.created_at > uf.created_at OR notifiable_id = 14)
ORDER BY nf.id DESC LIMIT 10

notification_followers имеет ~ 100K записей, и мы используем InnoDB.Вот EXPLAIN для запроса:

Explain

Как мы можем оптимизировать запрос, чтобы он выполнялся за несколько мс?

ОБНОВЛЕНИЕ с UNION

Ниже приведен EXPLAIN для следующего запроса UNION, и я также включил EXPLAIN для каждого подзапроса отдельно.

(SELECT nf.id, nf.uuid, nf.leader_id, nf.data, nf.created_at, nfr.read_at
FROM notification_followers nf
LEFT JOIN user_follows uf ON uf.leader_id = nf.leader_id AND uf.follower_id = 14 AND nf.created_at > uf.created_at
LEFT JOIN notification_followers_read nfr ON nf.uuid = nfr.notification_uuid AND nfr.follower_id = 14
ORDER BY nf.id DESC
LIMIT 10)

UNION DISTINCT

(SELECT nf.id, nf.uuid, nf.leader_id, nf.data, nf.created_at, nfr.read_at
FROM notification_followers nf
LEFT JOIN notification_followers_read nfr ON nf.uuid = nfr.notification_uuid AND nfr.follower_id = 14
WHERE nf.notifiable_id = 14
ORDER BY nf.id DESC
LIMIT 10)

ORDER BY id desc
LIMIT 10

enter image description here

ОБНОВЛЕНИЕ С SQL DUMP

SQL DUMP для локального воспроизведения просто создать speed_test база данных локально и файл импорта, чтобы увидеть проблему медленных запросов в режиме реального времени со всеми данными таблицы (~ 100K строк) .

Ответы [ 3 ]

0 голосов
/ 26 апреля 2018

OR часто вызывает проблемы с производительностью, поскольку затрудняет использование индекса.Разделите запрос на два разных случая и объедините их с UNION.

(SELECT nf.id, nf.uuid, nf.leader_id, nf.data, nf.created_at, nfr.read_at
FROM notification_followers nf
LEFT JOIN user_follows uf ON uf.leader_id = nf.leader_id AND uf.follower_id = 14 AND nf.created_at > uf.created_at
LEFT JOIN notification_followers_read nfr ON nf.uuid = nfr.notification_uuid AND nfr.follower_id = 14
ORDER BY nf.id DESC
LIMIT 10)

UNION ALL

(SELECT nf.id, nf.uuid, nf.leader_id, nf.data, nf.created_at, nfr.read_at
FROM notification_followers nf
LEFT JOIN notification_followers_read nfr ON nf.uuid = nfr.notification_uuid AND nfr.follower_id = 14
WHERE nf.notifiable_id = 14
ORDER BY nf.id DESC
LIMIT 10)

ORDER BY id desc
LIMIT 10
0 голосов
/ 05 мая 2018

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

В любом случае, при добавлении следующих индексов продолжительность выполнения сокращается до 50 мс.

ALTER TABLE `notification_followers` ADD INDEX `notification_followe_idx_id_uuid_at_id_data` (`leader_id`,`uuid`,`created_at`,`id`,`data`(255));
ALTER TABLE `notification_followers_read` ADD INDEX `notification_followe_idx_id_uuid_at` (`follower_id`,`notification_uuid`,`read_at`);
ALTER TABLE `user_follows` ADD INDEX `user_follows_idx_id_id_at` (`follower_id`,`leader_id`,`created_at`);
0 голосов
/ 26 апреля 2018

Ваш запрос:

SELECT nf.id, nf.uuid, nf.leader_id, nf.data, nf.created_at, nfr.read_at
FROM notification_followers nf LEFT JOIN
     user_follows uf
     ON uf.leader_id = nf.leader_id AND uf.follower_id = 14 LEFT JOIN
     notification_followers_read nfr
     ON nf.uuid = nfr.notification_uuid AND nfr.follower_id = 14
WHERE nf.created_at > uf.created_at OR nf.notifiable_id = 14
ORDER BY nf.id DESC
LIMIT 10;

Это немного сложно.Предложение or - настоящий убийца.Но, исходя из вашей логики, я думаю, что вам нужно больше and, чем or:

SELECT nf.id, nf.uuid, nf.leader_id, nf.data, nf.created_at, nfr.read_at
FROM notification_followers nf LEFT JOIN
     user_follows uf
     ON uf.leader_id = nf.leader_id AND nf.created_at > uf.created_at AND 
        uf.follower_id = 14 LEFT JOIN
     notification_followers_read nfr
     ON nf.uuid = nfr.notification_uuid AND nfr.follower_id = 14
WHERE nf.notifiable_id = 14
ORDER BY nf.id DESC
LIMIT 10;

(обратите внимание, что оно переходит к предложению ON.)

Очевидные индексы: notification_followers(notifiable_id, leader_id, created_at), user_follows(leader_id, follower_id, created_at) и notification_followers_read(notification_uuid, notifiable_id).

...