MySQL在这种查询中自然会慢吗,还是我的配置错误?

lev*_*and 15 mysql sql performance sql-tuning

以下查询旨在按用户接收未读消息的列表.它涉及3个表:recipients包含用户与消息ID的关系,messages包含消息本身,并message_readers包含哪些用户已阅读哪些消息的列表.

查询可靠地需要4.9秒 - 这严重损害了我们的性能,尤其令人担忧,因为我们希望数据库最终会增加几个数量级.当然,这是一个固有的重要查询,但数据集很小,直观地说它似乎应该快得多.服务器有足够的内存(32gb),整个数据库应始终加载到RAM中,并且盒子上没有其他任何东西在运行.

这些表都很小:

recipients: 23581
messages: 9679
message_readers: 2685
Run Code Online (Sandbox Code Playgroud)

查询本身:

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)
Run Code Online (Sandbox Code Playgroud)

解释计划非常简单:

+----+-------------+-------+--------+-----------------------------------+-----------------------------------+---------+--------------------------------+-------+-------------+
| 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 |
+----+-------------+-------+--------+-----------------------------------+-----------------------------------+---------+--------------------------------+-------+-------------+
Run Code Online (Sandbox Code Playgroud)

有一个索引message_readers.read_by_id,但我想它不能真正使用它因为IS NULL条件.

我正在使用除以下内容之外的所有默认设置:

key_buffer=4G
query_cache_limit = 256M
query_cache_size = 1G
innodb_buffer_pool_size=12G
Run Code Online (Sandbox Code Playgroud)

谢谢!

Jef*_*dge 4

假设 是message_readers的子集recipients,我建议进行以下更改:

  1. 把桌子拿掉message_readers,用桌子上的旗帜代替recipients。这将消除空检查并删除连接。

  2. 它可能已经是,但请确保您的聚集索引是recipientsisid, 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'
Run Code Online (Sandbox Code Playgroud)

更新

这是使用现有方案的查询的正确版本:

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
Run Code Online (Sandbox Code Playgroud)

这假设您的聚集索引符合预期:

recipients: id, message_id
messages: id
message_readers: read_by_id, message_id
Run Code Online (Sandbox Code Playgroud)