【问题标题】:SQL Query Optimization When using Multiple Joins and Large Record Set使用多个连接和大型记录集时的 SQL 查询优化
【发布时间】:2012-03-14 02:04:26
【问题描述】:

我正在制作一个留言板,并尝试检索常规主题(即未粘贴的主题)并按最后发布消息的日期对它们进行排序。我能够做到这一点,但是当我有大约 10,000 条消息和 1500 个主题时,查询时间 > 60 秒。

我的问题是,我可以对查询做些什么来提高性能还是我的设计存在根本缺陷?

这是我正在使用的查询。

SELECT Messages.topic_id,
       Messages.posted,
       Topics.title,
       Topics.user_id,
       Users.username
FROM Messages
LEFT JOIN
  Topics USING(topic_id)
LEFT JOIN
   Users on Users.user_id = Topics.user_id
WHERE Messages.message_id IN (
    SELECT MAX(message_id)
    FROM Messages
    GROUP BY topic_id)
AND Messages.topic_id
NOT IN (
    SELECT topic_id
    FROM StickiedTopics)
AND Messages.posted IN (                           
    SELECT MIN(posted)
    FROM Messages 
    GROUP BY message_id)
AND Topics.board_id=1
ORDER BY Messages.posted DESC LIMIT 50

编辑这是解释计划

+----+--------------------+----------------+----------------+------------------+----------+---------+-------------------------+------+----------------------------------------------+
| id | select_type        | table          | type           | possible_keys    | key      | key_len | ref                     | rows | Extra                                        |
+----+--------------------+----------------+----------------+------------------+----------+---------+-------------------------+------+----------------------------------------------+
|  1 | PRIMARY            | Topics         | ref            | PRIMARY,board_id | board_id | 4       | const                   |  641 | Using where; Using temporary; Using filesort |
|  1 | PRIMARY            | Users          | eq_ref         | PRIMARY          | PRIMARY  | 4       | spergs3.Topics.user_id  |    1 |                                               |
|  1 | PRIMARY            | Messages       | ref            | topic_id         | topic_id | 4       | spergs3.Topics.topic_id |    3 | Using where                                   |
|  4 | DEPENDENT SUBQUERY | Messages       | index          | NULL             | PRIMARY  | 8       | NULL                    |    1 |                                              |
|  3 | DEPENDENT SUBQUERY | StickiedTopics | index_subquery | topic_id         | topic_id | 4       | func                    |    1 | Using index                                  |
|  2 | DEPENDENT SUBQUERY | Messages       | index          | NULL             | topic_id | 4       | NULL                    |    3 | Using index                                  |
+----+--------------------+----------------+----------------+------------------+----------+---------+-------------------------+------+----------------------------------------------+

索引

+----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table    | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Messages |          0 | PRIMARY  |            1 | message_id  | A         |        9956 |     NULL | NULL   |      | BTREE      |         |
| Messages |          0 | PRIMARY  |            2 | revision_no | A         |        9956 |     NULL | NULL   |      | BTREE      |         |
| Messages |          1 | user_id  |            1 | user_id     | A         |         432 |     NULL | NULL   |      | BTREE      |         |
| Messages |          1 | topic_id |            1 | topic_id    | A         |        3318 |     NULL | NULL   |      | BTREE      |         |
+----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+

+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table  | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Topics |          0 | PRIMARY  |            1 | topic_id    | A         |        1205 |     NULL | NULL   |      | BTREE      |         |
| Topics |          1 | user_id  |            1 | user_id     | A         |         133 |     NULL | NULL   |      | BTREE      |         |
| Topics |          1 | board_id |            1 | board_id    | A         |           1 |     NULL | NULL   |      | BTREE      |         |
+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+

+-------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table | Non_unique | Key_name        | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+-------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Users |          0 | PRIMARY         |            1 | user_id     | A         |        2051 |     NULL | NULL   |      | BTREE      |         |
| Users |          0 | username_UNIQUE |            1 | username    | A         |        2051 |     NULL | NULL   |      | BTREE      |         |
+-------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+

【问题讨论】:

  • 您至少应该发布解释计划。
  • 无查询计划 = 无优化(尽管可能会猜测应该存在的索引,需要在问题中显示)。您是否考虑过用适当的JOINs 替换IN? (我不使用 MySQL,所以我不知道它是做什么的:-/)
  • @EvilTeach,谢谢我更新了我的问题
  • @pst 我用索引更新了这个问题,我不确定如何使用连接来解决这个问题。我相信IN 子句是从子查询中选择结果的子集。我不确定如何使用JOIN 来获得不在StickiedTopics 表中的结果。

标签: mysql sql performance optimization database-optimization


【解决方案1】:

我将从合格主题的第一个基础开始,获取这些 ID,然后再加入。 我的内部第一个查询执行按 topic_id 和 max 消息分组的预限定,以获得预限定的不同 ID。我也对stickiesTopics 应用了左连接。为什么?通过进行左连接,我可以查找那些已经找到的(那些你想排除的)。所以我为 Stickies 主题 ID 应用了 WHERE 子句为 NULL(即:未找到)。因此,通过这样做,我们已经在列表中显着配对,而无需执行多个嵌套子查询。根据该结果,我们可以加入消息、主题(包括 board_id = 1 的限定符)、用户并根据需要获取部件。最后,为您的 MIN(posted) 限定符应用一个 WHERE IN 子选择。不了解其基础,但将其作为原始查询的一部分保留。然后是order by和limit。

SELECT STRAIGHT_JOIN
      M.topic_id,
      M.posted,
      T.title,
      T.user_id,
      U.username
   FROM 
      ( select 
              M1.Topic_ID, 
              MAX( M1.Message_id ) MaxMsgPerTopic
           from 
              Messages M1
                 LEFT Join StickiedTopics ST
                    ON M1.Topic_ID = ST.Topic_ID
           where
              ST.Topic_ID IS NULL
           group by 
              M1.Topic_ID ) PreQuery
        JOIN Messages M
           ON PreQuery.MaxMsgPerTopic = M.Message_ID
           JOIN Topics T
               ON M.Topic_ID = T.Topic_ID
              AND T.Board_ID = 1
              LEFT JOIN Users U
                 on T.User_ID = U.user_id 
   WHERE
      M.posted IN ( SELECT MIN(posted)
                       FROM Messages 
                       GROUP BY message_id)
   ORDER BY 
      M.posted DESC 
   LIMIT 50

【讨论】:

  • 这太好了,非常感谢!它将查询时间缩短到 20 秒,并将硬件稍微增加到 4 秒。好多了!
【解决方案2】:

我猜你的问题很大一部分在于你的子查询。试试这样的:

SELECT Messages.topic_id,
       Messages.posted,
       Topics.title,
       Topics.user_id,
       Users.username
FROM Messages
LEFT JOIN
    Topics USING(topic_id)
LEFT JOIN
    StickiedTopics ON StickiedTopics.topic_id = Topics.topic_id 
                   AND StickedTopics.topic_id IS NULL
LEFT JOIN
    Users on Users.user_id = Topics.user_id
WHERE Messages.message_id IN (
    SELECT MAX(message_id)
    FROM Messages m1
    WHERE m1.topic_id = Messages.topic_id)
AND Messages.posted IN (                           
    SELECT MIN(posted)                                                                                           
    FROM Messages m2
    GROUP BY message_id)
AND Topics.board_id=1
ORDER BY Messages.posted DESC LIMIT 50

我通过删除分组优化了第一个子查询。第二个子查询是不必要的,因为它可以替换为JOIN

我不太确定这第三个子查询应该做什么:

AND Messages.posted IN (                           
    SELECT MIN(posted)                                                                                           
    FROM Messages m2
    GROUP BY message_id)

如果我知道它应该做什么,我也许可以帮助优化它。 posted 到底是什么 - 日期、整数等?它代表什么?

【讨论】:

  • Messages.posted 是一个 unix 时间戳。留言板还支持编辑并保留修订历史记录,因此此查询将获取最旧修订的日期。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-24
  • 2016-04-30
  • 2023-03-12
  • 1970-01-01
  • 2012-06-05
  • 1970-01-01
相关资源
最近更新 更多