SELECT
*
FROM
`users`
WHERE
`id` != 1
AND `users`.`activated` = 1
AND NOT EXISTS (
SELECT
1
FROM
`blockings`
WHERE (blockings.user_id = users.id
AND blockings.blocked_id = 1)
OR(blockings.blocked_id = users.id
AND blockings.user_id = 1))
ORDER BY
users.id DESC
LIMIT 10 OFFSET 0
Выполнение этого запроса в таблице из 30 тыс. строк занимает 5 секунд.
Это занимает некоторое время, когда я удаляю часть НЕ СУЩЕСТВУЕТ
Существует индекс для пользователей.id и blockings.user_id и blockings.blocked_id
Как я могу ускорить этот запрос?
@easleyfixed ничего не меняет. по-прежнему занимает 4,5 секунды
Хм, хорошо, так что сам поиск по сравнению с вытягиванием - это задержка ... просто удостоверяюсь. Ну, я собирался предложить индексацию, но она у вас уже есть. Какую версию MySQL вы используете и, возможно, данные на SSD-накопителях?
mysql 8.0.28 aws rds
Задавая вопросы по оптимизации запросов в Stack Overflow, включите в свой запрос выходные данные SHOW CREATE TABLE
для каждой таблицы, чтобы нам не приходилось гадать о столбцах, типах данных или индексах. Также включите EXPLAIN для текущего запроса и таблиц.
Это что-то вроде «удара в темноте», поскольку вы не включили определения таблиц или вывод EXPLAIN для текущего запроса в свой вопрос.
С индексами одного столбца на blockings.user_id
и blockings.blocked_id
вы должны увидеть index_merge для blockings
в выводе EXPLAIN для вашего текущего запроса.
С PK (user_id, blocked_id)
и индексом (blocked_id, user_id)
использование UNION в вашем NOT EXISTS, вероятно, будет намного быстрее:
SELECT *
FROM users u
WHERE u.id != 1
AND u.activated = 1
AND NOT EXISTS (
SELECT 1
FROM blockings b
WHERE b.user_id = u.id AND b.blocked_id = 1
UNION ALL
SELECT 1
FROM blockings b
WHERE b.blocked_id = u.id AND b.user_id = 1
)
ORDER BY u.id DESC
LIMIT 10 OFFSET 0;
Как предложил MatBailie, стоит попробовать разделить UNION ALL на два отдельных NOT EXISTS:
SELECT *
FROM users u
WHERE u.id != 1
AND u.activated = 1
AND NOT EXISTS (
SELECT 1
FROM blockings b
WHERE b.user_id = u.id AND b.blocked_id = 1
)
AND NOT EXISTS (
SELECT 1
FROM blockings b
WHERE b.blocked_id = u.id AND b.user_id = 1
)
ORDER BY u.id DESC
LIMIT 10 OFFSET 0;
Я бы использовал два оператора NOT EXISTS (в сочетании с AND), а не UNION ALL
Я попробовал оба и получил немного лучшую производительность (незначительную) от UNION ALL, но я думаю, что это будет зависеть от распределения данных OP. Определенно стоит попробовать оба.
Вот возможный альтернативный запрос, и вам может потребоваться добавить индекс для столбцов, используемых в предложении where.
SELECT
users.id, /* all the necessary columns goes here */
users.name,
users. Email
FROM
users
LEFT JOIN blockings ON (
(blockings.user_id = users.id AND blockings.blocked_id = 1)
OR (blockings.blocked_id = users.id AND blockings.user_id = 1)
)
WHERE
users.id != 1
AND users.activated = 1
AND blockings.id IS NULL
ORDER BY
users.id DESC
LIMIT 10 OFFSET 0;
С операционкой все равно будет проблема.
Вам нужно ВСЕ в ряду? Может быть, вы можете ограничить * выбора ТОЛЬКО нужными столбцами?