MySQL, естественно, медленен при таком запросе или неправильно настроен? - PullRequest
15 голосов
/ 27 июня 2011

Следующий запрос предназначен для получения списка непрочитанных сообщений пользователем. Он включает в себя 3 таблицы: recipients содержит отношение пользователей к идентификаторам сообщений, messages содержит сами сообщения, а message_readers содержит список сообщений, которые пользователи прочитали, какие сообщения.

Надежный запрос занимает 4,9 секунды - это серьезно сказывается на нашей производительности и вызывает особую тревогу, поскольку мы надеемся, что со временем база данных будет на несколько порядков больше. Конечно, это по своей сути тяжелый запрос, но набор данных крошечный, и интуитивно кажется, что он должен быть намного быстрее. На сервере достаточно памяти (32 ГБ), чтобы вся база данных всегда загружалась в ОЗУ, и на коробке больше ничего не работает.

Все таблицы крошечные:

recipients: 23581
messages: 9679
message_readers: 2685

Сам запрос:

SELECT 
    m.*
FROM 
    messages m
INNER JOIN recipients r ON r.message_id = m.id
LEFT JOIN message_readers mr ON mr.message_id = m.id
WHERE
    r.id = $user_id
    AND (mr.read_by_id IS NULL OR mr.read_by_id <> $user_id)

План объяснения довольно прост:

+----+-------------+-------+--------+-----------------------------------+-----------------------------------+---------+--------------------------------+-------+-------------+
| id | select_type | table | type   | possible_keys                     | key                               | key_len | ref                            | rows  | Extra       |
+----+-------------+-------+--------+-----------------------------------+-----------------------------------+---------+--------------------------------+-------+-------------+
|  1 | SIMPLE      | r     | ref    | index_recipients_on_id            | index_recipients_on_id            | 768     | const                          | 11908 | Using where |
|  1 | SIMPLE      | m     | eq_ref | PRIMARY                           | PRIMARY                           | 4       | db.r.message_id                |     1 | Using index |
|  1 | SIMPLE      | mr    | ALL    | NULL                              | NULL                              | NULL    | NULL                           |  2498 | Using where |
+----+-------------+-------+--------+-----------------------------------+-----------------------------------+---------+--------------------------------+-------+-------------+

На message_readers.read_by_id есть индекс IS, но я полагаю, он не может использовать его из-за условия IS NULL.

Я использую все настройки по умолчанию, кроме следующих:

key_buffer=4G
query_cache_limit = 256M
query_cache_size = 1G
innodb_buffer_pool_size=12G

Спасибо!

Ответы [ 6 ]

4 голосов
/ 27 июня 2011

Предполагая, что message_readers является подмножеством recipients, я рекомендую внести следующие изменения:

  1. Избавьтесь от таблицы message_readers и замените ее флажком на таблице recipients. Это исключит нулевую проверку и удалит соединение.

  2. Вероятно, это уже так, но убедитесь, что ваш кластеризованный индекс для recipients равен id, message_id, а не message_id, id, поскольку почти все поиски сообщений будут основаны на получателях.

Вот SELECT, который приводит:

SELECT
    r.whatever,
    m.whatever,
    -- ...
FROM
    recipients r
    INNER JOIN messages m ON m.id = r.message_id
WHERE
    r.id = $user_id
    AND r.read_flag = 'N'

UPDATE

Вот правильная версия вашего запроса по существующей схеме:

SELECT
    r.whatever,
    m.whatever,
    -- ...
FROM
    recipients r
    INNER JOIN messages m ON r.message_id = m.id
    LEFT JOIN message_readers mr ON mr.read_by_id = r.id 
                                 AND mr.message_id = m.id
WHERE
    r.id = $user_id
    AND mr.read_by_id IS NULL

Предполагается, что ваши кластеризованные индексы соответствуют ожиданиям:

recipients: id, message_id
messages: id
message_readers: read_by_id, message_id
1 голос
/ 28 июня 2011

счетчик комментариев (m.id) означает, что число не равно нулю, но m.id никогда не равно нулю, поэтому его дополнительно.попробуйте с этим

SELECT count(*)
FROM 
messages m
INNER JOIN recipients r ON r.message_id = m.id  
left join 
(
    select m.id
    messages m
    INNER JOIN message_readers mr 
    ON mr.message_id = m.id     
    and (mr.read_by_id <> $user_id or mr.read_by_id IS NULL)        
)as sub 
on sub.id = m.id        
WHERE r.id = $user_id

одно сомнение может быть правильным в вашей бизнес-логике, почему все пользователи могут читать входящие сообщения (mr.read_by_is null) и почему сообщение может быть прочитано для других или не определено получателем(mr.read_by_id <> $ user_id)

это пул, я думаю

один из лучших подходов - изменить внутреннее в подзапросе с помощью существует. см., Что "mr.read_by_id IS NULL" не является обязательным, то есть если mr_read_by_id равен нулю ", значит, что" mr.read_by_id = $ user_id "является ложным"

SELECT count(*)
FROM 
messages m
INNER JOIN recipients r ON r.message_id = m.id  
left join 
(
    select m.id
    messages m
            where not exists(select * from message_readers mr 
    where mr.message_id = m.id      
    and mr.read_by_id = $user_id)
)as sub 
on sub.id = m.id        
WHERE r.id = $user_id
1 голос
/ 28 июня 2011

Если я что-то упустил, вам, кажется, не нужна таблица сообщений вообще. Что вам действительно нужно, так это количество идентификаторов сообщений, которые появляются для этого пользователя в получателях и не отображаются для этого пользователя в message_readers.

Если я прямо выше, вы можете выполнить то, что вы хотите с МИНУСОМ:

SELECT count(message_id)
  FROM (
        SELECT r.message_id  
          FROM recipients r 
         WHERE r.id = $user_id
        MINUS
        SELECT mr.message_id
          FROM message_readers mr
         WHERE mr.read_by_id = $user_id
       )

Это полностью исключает соединения. Теперь, если вам действительно нужны данные из таблицы сообщений для производственного запроса, вы можете присоединить таблицу сообщений к этому подзапросу (или вставить ее в предложение IN).

Возможно, я здесь не на базе, так как мой опыт работы с Oracle-страной, но MySQL поддерживает МИНУС, так что, вероятно, стоит попробовать.

1 голос
/ 27 июня 2011

Какое время запроса для

select distinct message_id
  from message_readers
 where read_by_id <> $user_id

Примечание: логика "is null" должна быть поймана этим, так как null не равен ничему

Если это быстро, попробуйте следующее:

SELECT count(m.id)
FROM messages m
INNER JOIN recipients r ON r.message_id = m.id
where r.id = $user_id
and m.id in (
    select distinct message_id
      from message_readers
     where read_by_id <> $user_id)

Оригинальный ответ не сработал: Попробуйте включить message_id и id в закрывающий индекс получателей и посмотрите, что произойдет.

1 голос
/ 27 июня 2011

Вы можете избавиться от условия IS NULL, переписав свой запрос следующим образом:

SELECT 
    count(m.id)
FROM 
    messages m
INNER JOIN recipients r ON re.message_id = m.id
WHERE r.id = $user_id
  AND NOT EXISTS
         (SELECT mr.id 
            FROM message_readers mr 
           WHERE mr.message_id = m.id
             AND mr.read_by_id = $user_id)

В основном это выглядит как: получить все messages для recipient, где нет в message_readers и описывает проблему simpeler.

1 голос
/ 27 июня 2011

Предполагая, что вы просто хотите считать, как показано в вашем запросе), что произойдет, если вы измените соединения следующим образом?

Я использую MSSQL, и это может ускорить его.Я никогда не использовал MySQL, но он должен работать, не так ли?

SELECT     count(m.id)
FROM       messages m
INNER JOIN recipients r ON r.message_id = m.id AND r.id = $user_id
LEFT JOIN  message_readers mr ON mr.message_id = m.id AND (mr.read_by_id IS NULL OR mr.read_by_id <> $user_id)

РЕДАКТИРОВАТЬ: Как насчет этого для безумной идеи?Я думал, что вы могли бы разделить OR на два отдельных левых соединения, а затем взять запись, если что-то из этого возвращает что-то.

SELECT     count(m.id)
FROM       messages m
LEFT JOIN  recipients r ON r.message_id = m.id AND r.id = $user_id
LEFT JOIN  message_readers mr ON mr.message_id = m.id AND mr.read_by_id IS NULL
LEFT JOIN  message_readers mr2 ON mr2.message_id = m.id AND mr2.read_by_id <> $user_id
WHERE      COALESCE(mr.message_id, mr2.message_id) IS NOT NULL
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...