Лидер может иметь много последователей.Таблица 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
для запроса:
Как мы можем оптимизировать запрос, чтобы он выполнялся за несколько мс?
ОБНОВЛЕНИЕ с 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
ОБНОВЛЕНИЕ С SQL DUMP
SQL DUMP для локального воспроизведения просто создать speed_test
база данных локально и файл импорта, чтобы увидеть проблему медленных запросов в режиме реального времени со всеми данными таблицы (~ 100K строк) .