Оптимизировать объединение, сумму, подзапросы

0

Я создаю клон 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)

Спасибо за вашу помощь

Теги:

3 ответа

2
Лучший ответ

Пользователь может быть сопоставлен как 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);
  • 0
    Это серьезно работает лучше, чем все. Но мне все еще около 25 с, что является огромным временем для ожидания HTTP-запроса. Я бы не ожидал, что это будет так сложно ... Спасибо
  • 0
    Мне жаль слышать это. Так что пришло время использовать explain plan и посмотреть, что делает запрос медленным, я думаю.
Показать ещё 1 комментарий
2

Я думаю, вы были правы в своих навыках 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, а также с пользовательской таблицей столбцами (или комбинацией столбцов), которые вы используете в фильтрах (пол, например), посмотрят, что приносит более высокую производительность.

  • 0
    Спасибо Эдгар, но твоя просьба даже длиннее моей! (395s). Я создал индекс для: user.id, match.user1, match.user2, user.gender * user.orientation, но это все еще очень медленно
  • 0
    @philipxy: Что заставляет тебя так говорить? Вам нужно внешнее объединение, чтобы получить пользователей, у которых еще нет записей матчей.
Показать ещё 1 комментарий
0

Из того, что вы предоставляете, я бы создал индексы на:
- 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
  • 0
    Обычно у меня есть 2 пола и 3 ориентации. Спасибо за Ваш ответ. Я попробовал индексы, как вы советовали, но безрезультатно. Я, вероятно, делаю что-то не так. Я сейчас работаю над DDL.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню