Я создаю клон Tinder для учебного проекта, и я пытаюсь сделать что-то очень простое концептуально, но похоже, что мой запрос действительно слишком тяжелый.
Я создал эту простую скрипту, чтобы визуализировать структуру базы данных.
Я попытался поместить индексы в user.id
user.gender * user.orientation
match.user1
match.user2
match.createdAt
без везения.
Я хочу найти людей с меньшим количеством совпадений в зависимости от пола, ориентации, lastLogin и даты календаря.
Пользователи не будут участвовать в более чем 4 матчах в течение 24 часов, поэтому я ищу пользователей с <= 3 матчами в течение последних 24 часов.
Значения в следующем жестко закодированы для легкого редактирования запроса и потому, что я не занимал время, чтобы сделать эту часть на данный момент.
Совпадение состоит из 2 пользователей (user1 и user2).
Предел в 4 совпадения в тот же день представляет собой сумму, когда они отображаются как user1 и user2.
SELECT total_sum, userId
FROM (
SELECT u.id as userId, u.orientation as userOrientation, u.gender as userGender, m1.sum1, m2.sum2, (m1.sum1 + m2.sum2) AS total_sum
FROM user u
INNER JOIN (
SELECT user1, COUNT(user1) as sum1
FROM 'match'
WHERE createdAt > DATE('2017-12-11 00:00:00')
GROUP BY user1
) m1
ON m1.user1 = u.id
INNER JOIN (
SELECT user2, COUNT(user1) as sum2
FROM 'match'
WHERE createdAt > DATE('2017-12-11 00:00:00')
GROUP BY user2
) m2
ON m2.user2 = u.id
WHERE u.gender IN ('female')
AND u.orientation IN ('hetero', 'bi')
AND u.lastLogin > 1512873464582
) as total
WHERE total_sum < 4
ORDER BY total_sum ASC
LIMIT 8
С крошечными таблицами запрос занимает несколько мс, но со средними таблицами (50 тыс. Пользователей, совпадением 200 тыс.), Запрос занимает много времени (170 секунд).
Согласно ответу @Thorsten Kettner, это объясняет план его запроса, когда я запускаю его в свой тестовый db после установки индексов, которые он посоветовал:
Я закончил делать что-то проще.
Сначала я упростил таблицу соответствия, удалив столбец user2
. Он удваивает размер, потому что теперь 1 совпадение становится 2 строками, но позволяет мне делать что-то очень простое и очень эффективное с соответствующими индексами.
Первый запрос состоит в том, чтобы управлять пользователями без совпадений, а второй - обработать пользователя со спичками. У меня больше нет matchLimit в запросе, поскольку он добавляет дополнительную работу для mysql, и мне просто нужно проверить первый результат, чтобы увидеть, соответствует ли matchNumber <= 3.
(SELECT u.id, mc.id as nb_match, u.gender, u.orientation
FROM user u
LEFT JOIN match_composition mc
ON (mc.matchedUser = u.id AND mc.createdAt > DATE('2017-12-11 00:00:00'))
WHERE u.lastLogin > 1512931740721
AND u.orientation IN ('bi', 'hetero')
AND u.gender IN ('female')
AND mc.id IS NULL
ORDER BY u.lastLogin DESC)
UNION ALL
(SELECT u.id, count(mc.id) as nb_match, u.gender, u.orientation
FROM match_composition mc
JOIN user u
ON u.id = matchedUser
WHERE mc.createdAt > DATE('2017-12-11 00:00:00')
AND u.lastLogin > 1512931740721
AND u.orientation IN ('bi', 'hetero')
AND u.gender IN ('female')
GROUP BY matchedUser
ORDER BY nb_match ASC
LIMIT 8)
Спасибо за вашу помощь
Пользователь может быть сопоставлен как user1 или user2. Мы можем использовать UNION ALL
для получения одной записи для каждого пользователя:
select user1 as userid from match union all select user2 as userid from match;
Полный запрос:
select
u.id as userid,
coalesce(um.total, 0) as total
from user u
left join
(
select userid, count(*) as total
from
(
select user1 as userid from match where createdat > date '2017-12-11'
union all
select user2 as userid from match where createdat > date '2017-12-11'
) m
group by userid
) um on um.userid = u.id
where u.gender IN ('female')
and u.orientation in ('hetero', 'bi')
and u.lastlogin > 1512873464582
and coalesce(um.total, 0) < 4
order by coalesce(um.total, 0);
Для этого у вас будут следующие индексы:
create index idx_m1 on match (createdat, user1);
create index idx_m2 on match (createdat, user2);
create index idx_u on user (lastlogin, gender, orientation, id);
Я думаю, вы были правы в своих навыках SQL. Вот что я придумал:
SELECT u.id as userId,
u.orientation as userOrientation,
u.gender as userGender,
count(m.user1) total_sum
FROM user u
LEFT JOIN 'match' m on (u.id in (m.user1, m.user2)
and m.createdAt > DATE('2017-12-11 00:00:00'))
WHERE u.gender IN ('female')
AND u.orientation IN ('hetero', 'bi')
AND u.lastLogin > 1512873464582
having count(m.user1) <=4
ORDER BY total_sum ASC
LIMIT 8;
Изменение: покрыты также случаи без совпадений
Попробуйте поиграть со столом матча индексации столбцов user1, user1, а также с пользовательской таблицей столбцами (или комбинацией столбцов), которые вы используете в фильтрах (пол, например), посмотрят, что приносит более высокую производительность.
Из того, что вы предоставляете, я бы создал индексы на:
- match.user1
- match.user2
- match.createdAt
- user.id (уникальный и, возможно, PK) - user.lastLogin
Я также попытался бы заменить COUNT (user1) на COUNT (*), но, вероятно, это не повлияет.
Индексы на user.gender и user.orientation, вероятно, бесполезны: эффективность индекса как-то пропорциональна дисперсии его базовых значений. Поэтому индекс в поле с 2-3 различными значениями является более дорогостоящим, чем полезный.
Что касается DLL, попробуйте следующее. Я попытался принудительно выполнить фильтрацию на user
перед соединением с match
, если оптимизатор запросов не работает должным образом (у меня мало опыта работы с базами данных без MS)
SELECT total_sum, userId
FROM (SELECT u.id as userId, u.orientation as userOrientation, u.gender as userGender, m1.sum1, m2.sum2, (m1.sum1 + m2.sum2) AS total_sum
FROM (SELECT * FROM user
WHERE gender = 'female'
AND orientation IN ('hetero', 'bi')
AND lastLogin > 1512873464582
) u
INNER JOIN (SELECT user1, COUNT(*) as sum1
FROM 'match'
WHERE createdAt > DATE('2017-12-11 00:00:00')
GROUP BY user1
) m1 ON m1.user1 = u.id
INNER JOIN (SELECT user2, COUNT(*) as sum2
FROM 'match'
WHERE createdAt > DATE('2017-12-11 00:00:00')
GROUP BY user2
) m2 ON m2.user2 = u.id
) as total
WHERE total_sum < 4
ORDER BY total_sum ASC
LIMIT 8
explain plan
и посмотреть, что делает запрос медленным, я думаю.