【问题标题】:Why is my MySQL query with Multiple Joins so slow?为什么我的带有多个连接的 MySQL 查询这么慢?
【发布时间】:2014-12-19 08:38:19
【问题描述】:

我正在尝试对包含大约 30,000 行的表(history_details)运行查询,并且需要连接多个表(历史、房间、室友、属性、列表、学生和管理员)。

  1. 历史表的第一次连接很简单。
  2. 只有在满足条件时才应添加接下来的 3 个连接(房间、室友、属性)。在这种情况下,它会查看 history_details 表中 listing_type 列的值,如果它是适当的值,则应将连接添加到查询中。
  3. 接下来的 3 个连接需要满足几个条件才能包含它们,但在这些情况下,它可以是任何 OR 条件。

                    SELECT SQL_CALC_FOUND_ROWS 
                        history_details.history_id,
                        history_details.listing_type,
                        history_details.listing_id,
                        history_details.date_added,
                        history_details.field,
                        history_details.before_value,
                        history_details.after_value,
                        history_details.edit_group,
                        history_details.email,
                        history_details.name,
                        history_details.correction_message,
                        history_details.photo_filepath,
                        history.history_message,
                        listers.first_name,
                        listers.last_name,
                        students.first_name,
                        students.last_name,
                        admin.first_name,
                        admin.last_name
                    FROM history_details
                    LEFT JOIN history
                        ON history_details.history_id = history.history_id
                    LEFT JOIN rooms
                        ON history_details.listing_type="room" AND rooms.room_id = history_details.listing_id
                    LEFT JOIN roommates
                        ON history_details.listing_type="roommate"
                        AND roommates.roommate_id = history_details.listing_id
                    LEFT JOIN properties
                        ON history_details.listing_type="property"
                        AND properties.property_id = history_details.listing_id
                    LEFT JOIN listers
                        ON (history_details.listing_type="lister" AND listers.lister_id = history_details.listing_id)
                        OR (history_details.listing_type="property" AND listers.lister_id = properties.lister_id)
                        OR (history_details.listing_type="room" AND rooms.lister_type="lister" AND listers.lister_id = rooms.lister_id)
                    LEFT JOIN students
                        ON (history_details.listing_type="student" AND students.student_id = history_details.listing_id)
                        OR (history_details.listing_type="roommate" AND students.student_id = roommates.student_id)
                        OR (history_details.listing_type="room" AND rooms.lister_type="student" AND students.student_id = rooms.lister_id)
                    LEFT JOIN admin
                        ON history_details.listing_type="admin" AND admin.admin_id = history_details.listing_id
                    LIMIT 0,100
    

它给了我正确的结果,但运行需要 73 秒。我添加了索引,但我不确定它们是否被使用。这是我使用 EXPLAIN SELECT SQL_CALC_FOUND_ROWS .... 运行解释时显示的内容

这是我使用 EXPLAIN SELECT .... 运行解释时显示的内容

这些是我添加到各个表中的索引: history_details

属性

房间

室友

你能告诉我我的索引是否被使用了吗?另外,如何改进我的查询、表结构或索引以提高速度?

编辑: 多亏了 Amadan 的有用建议,它现在以 147 毫秒而不是之前的 73 秒运行。这是我最终使用的最后一个查询:

                SELECT SQL_CALC_FOUND_ROWS 
                    history_details.*,
                    listers.first_name AS lister_first_name,
                    listers.last_name AS lister_last_name,
                    NULL AS student_first_name,
                    NULL AS student_last_name,
                    NULL AS admin_first_name,
                    NULL AS admin_last_name
                FROM history_details
                LEFT JOIN history
                    ON history_details.history_id = history.history_id
                LEFT JOIN listers
                    ON listers.lister_id = history_details.listing_id
                WHERE history_details.listing_type="lister"

                UNION

                SELECT history_details.*,
                    NULL AS lister_first_name,
                    NULL AS lister_last_name,
                    students.first_name AS student_first_name,
                    students.last_name AS student_last_name,
                    NULL AS admin_first_name,
                    NULL AS admin_last_name
                FROM history_details
                LEFT JOIN history
                    ON history_details.history_id = history.history_id
                LEFT JOIN students
                    ON students.student_id = history_details.listing_id
                WHERE history_details.listing_type="student"

                UNION

                SELECT history_details.*,
                    listers.first_name AS lister_first_name,
                    listers.last_name AS lister_last_name,
                    NULL AS student_first_name,
                    NULL AS student_last_name,
                    NULL AS admin_first_name,
                    NULL AS admin_last_name
                FROM history_details
                LEFT JOIN history
                    ON history_details.history_id = history.history_id
                LEFT JOIN properties
                    ON properties.property_id = history_details.listing_id
                LEFT JOIN listers
                    ON listers.lister_id = properties.lister_id
                WHERE history_details.listing_type="property"

                UNION

                SELECT history_details.*,
                    listers.first_name AS lister_first_name,
                    listers.last_name AS lister_last_name,
                    students.first_name AS student_first_name,
                    students.last_name AS student_last_name,
                    NULL AS admin_first_name,
                    NULL AS admin_last_name
                FROM history_details
                LEFT JOIN history
                    ON history_details.history_id = history.history_id
                LEFT JOIN rooms
                    ON rooms.room_id = history_details.listing_id
                LEFT JOIN listers
                    ON (rooms.lister_type="lister" AND listers.lister_id = rooms.lister_id)
                LEFT JOIN students
                    ON (rooms.lister_type="student" AND students.student_id = rooms.lister_id)
                WHERE history_details.listing_type="room"

                UNION

                SELECT history_details.*,
                    NULL AS lister_first_name,
                    NULL AS lister_last_name,
                    students.first_name AS student_first_name,
                    students.last_name AS student_last_name,
                    NULL AS admin_first_name,
                    NULL AS admin_last_name
                FROM history_details
                LEFT JOIN history
                    ON history_details.history_id = history.history_id
                LEFT JOIN roommates
                    ON roommates.roommate_id = history_details.listing_id
                LEFT JOIN students
                    ON students.student_id = roommates.student_id
                WHERE history_details.listing_type="roommate"

                UNION

                SELECT history_details.*,
                    NULL AS lister_first_name,
                    NULL AS lister_last_name,
                    NULL AS student_first_name,
                    NULL AS student_last_name,
                    admin.first_name AS admin_first_name,
                    admin.last_name AS admin_last_name
                FROM history_details
                LEFT JOIN history
                    ON history_details.history_id = history.history_id
                LEFT JOIN admin
                    ON admin.admin_id = history_details.listing_id
                WHERE history_details.listing_type="admin"
                ORDER BY history_details_id DESC
                LIMIT 0,100

【问题讨论】:

  • explain select .... 显示了什么?
  • 在每种情况下,您首先应该查看的是执行计划。
  • 在连接中使用 OR 条件也会减慢查询速度。
  • @AbhikChakraborty - 我添加了没有 SQL_CALC_FOUND_ROWS 的 EXPLAIN 输出的屏幕截图。只是略有不同。
  • @PanagiotisKanavos - 我不熟悉执行计划。您能否详细说明或提供一个链接来说明这一点?

标签: mysql join indexing


【解决方案1】:

keyNULL 的行,它没有为这些表使用任何索引。 IE。 listersstudents 未优化。那是因为它的索引是不可能的,因为您使用OR 来生成它。进行多个查询,每个history_details.listing_typeUNION 一个。是的,看起来很丑,重复很多,但速度会在那里。

联合语法如下:

SELECT ...
WHERE history_details.listing_type="lister"
UNION
SELECT ...
WHERE history_details.listing_type="property"
UNION
SELECT ...
WHERE history_details.listing_type="room"
UNION
SELECT ...
WHERE history_details.listing_type="admin"
....
...
ORDER BY whatever
LIMIT whatever, whatever

确保每个SELECT 具有相同数量的字段(因此即使是无用的连接也必须进行,但它们会很快,因为它们会被索引)。 ... 基本上是您的整个查询 (SELECT field1, field2... FROM table1 LEFT JOIN table2 ON condition ...),但将 ORlisterstudent 子句中取出。 UNION 将合并子选择的所有行。 ORDERLIMIT 将应用于联合的结果,而不是最后一个 SELECT

【讨论】:

  • 感谢您的建议。我对UNION不熟悉。你有任何示例代码吗?另外,如果我使用这种方法,我还会在一个结果集中收到所有返回的行吗?
  • 编辑了一个粗略的例子,真实的代码对于答案来说有点太长了。如果SELECT A 给你两行,1, 2SELECT B 给你4, 5, 6,那么SELECT A UNION SELECT B 会给你5 行。
  • 好的,我试一试。谢谢!您是说我需要每个条件 Select 语句中的所有连接,还是只需要与该特定条件相关的连接?
  • 我是,但重新考虑它是错误的。您可以取消不执行任何操作的联接(例如,当您查找 student 值时使用 listers 表),但请确保具有相同数量(和顺序)的字段(即在上面的示例中替换listers.first_nameNULL)。
  • SELECT a, b, c FROM (SELECT a, b, c UNION SELECT a, b, c) WHERE a ORDER BY b LIMIT 0, 100,您之前的整个查询都在括号中;但这将消除您的列表、属性和管理员,因为这些字段是 NULL 用于这些查询......而且外部的“WHERE”也不会被索引,因为它将位于没有索引的计算临时表上.因此,与其这样做,我会在每个有学生的UNIONed SELECT 中添加三个相同的WHERE 子句。
猜你喜欢
  • 2011-03-07
  • 1970-01-01
  • 2023-03-05
  • 2012-06-13
  • 1970-01-01
  • 1970-01-01
  • 2016-12-16
  • 2012-08-13
  • 2010-10-26
相关资源
最近更新 更多