【问题标题】:Fastest way to order by having true result on a left join in MYSQL通过在 MYSQL 中的左连接上获得真实结果来排序的最快方法
【发布时间】:2015-05-23 04:15:17
【问题描述】:

我正在尝试设置在两个不同表上匹配数据的东西。结果将按第二个表上的某些数据为真排序。但是,并非第一个表中的每个人都在第二个表中。我的问题是双重的。 1) 速度。我当前的 MYSQL 查询需要 4 秒来处理每个表上的数千个结果。 2) 没有正确订购。我需要它来按谁在线排序结果,但仍然按字母顺序排列。就目前而言,它根据 chathelp 表根据每个人是否在线对每个人进行排序,然后用 users 表填充其余部分。

我有什么:

SELECT  u.name, u.id, u.url, c.online
    FROM  users AS u
    LEFT JOIN  livechat AS c ON u.url = CONCAT('http://www.software.com/', c.chat_handle)
    WHERE  u.live_account = 'y'
    ORDER BY  c.online DESC, u.name ASC
    LIMIT  0, 24 

users
+-----------------------------------------------------------+--------------+
| id | name        | url                                    | live_account |
+-----------------------------------------------------------+--------------|
|  1 | Lisa Fuller | http://www.software.com/LisaHelpLady | y            |
|  2 | Eric Reiner |                                      | y            |
|  3 | Tom Lansen  | http://www.software.com/SaveUTom     | y            |
|  4 | Billy Bob   | http://www.software.com/BillyBob     | n            |
+-----------------------------------------------------------+--------------+

chathelp
+------------------------------------+
| chat_id | chat_handle    | online  |
+------------------------------------+
| 12      | LisaHelpLady   | 1       |
| 34      | BillyBob       | 0       |
| 87      | SaveUTom       | 0       |
+------------------------------------+

我希望收到的数据是什么样的:

+----------------------------------------------------------------------+
| name        | id | url                                     | online  |
+----------------------------------------------------------------------+
| Lisa Fuller |  1 | http://www.software.com/LisaHelpLady    | 1       |
| Eric Reiner |  4 |                                         | 0       |
| Tom Lansen  |  3 | http://www.software.com/SaveUTom        | 0       |
+----------------------------------------------------------------------+

说明:Billy 因没有真实账户而立即被排除在外。 Lisa 比 Eric 更早,因为她在线。 Tom 在 Eric 之后是因为他离线并且在数据中按字母顺序排列。两个表之间唯一匹配的数据是 url 列与 chat_handle 列的一部分。

我得到的是什么:

(基本上,我得到的是 Lisa、Tom,然后是 Eric)

无论他们是否在线,我都会首先列出聊天帮助表中的每个人。所以先来 600 人,然后我从 users 表中得到不在两个表中的其余人。我需要按字母顺序将聊天帮助表中离线的人分类到用户表中。因此,如果 Lisa 和 Tom 是唯一的在线用户,他们将排在第一位,但用户表中的其他所有人,无论他们是否设置了聊天帮助句柄,都将按字母顺序排在这两个用户之后。

同样,我需要在 4 秒内对它们进行排序并弄清楚如何做到这一点。我在两个表上都尝试过索引,但它们没有帮助。解释说它正在使用表用户上的键(名称)点击行 4771 -> 使用 where;使用临时;使用 filesort 并在 table2 NULL 上使用 1054 行的键,而额外的列中没有任何内容。

任何帮助将不胜感激。

编辑以添加表格并解释语句

CREATE TABLE `chathelp` (
  `chat_id` int(13) NOT NULL,
  `chat_handle` varchar(100) NOT NULL,
  `online` tinyint(1) NOT NULL DEFAULT '0',
  UNIQUE KEY `chat_id` (`chat_id`),
  KEY `chat_handle` (`chat_handle`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 


 CREATE TABLE `users` (
  `id` int(8) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL,
  `url` varchar(250) NOT NULL,
  `live_account` varchar(1) NOT NULL DEFAULT 'n',
  PRIMARY KEY (`id`),
  KEY `livenames` (`live_account`,`name`)
) ENGINE=MyISAM AUTO_INCREMENT=9556 DEFAULT CHARSET=utf8

+----+-------------+------------+------+---------------+--------------+---------+-------+------+----------------------------------------------+
| id | select_type | table      | type | possible_keys | key          | key_len | ref   | rows | Extra                                        |
+----+-------------+------------+------+---------------+--------------+---------+-------+------+----------------------------------------------+
|  1 | SIMPLE      | users      | ref  | livenames     | livenames    | 11      | const | 4771 | Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | chathelp   | ALL  | NULL          | NULL         | NULL    | NULL  | 1144 |                                              |
+----+-------------+------------+------+---------------+--------------+---------+-------+------+----------------------------------------------+

【问题讨论】:

  • 您的查询速度很慢,因为 1) 处理程序上没有可以使用的索引,2) 由于 concat 或您有 blob 类型的列,您有一个磁盘排序。
  • 因此,如果我要将 concat 字符串添加到 chathelp.chat_handle 中的信息中,我可以做一个相等的匹配来加速它吗?但是当显示句柄时,我必须从 MYSQL 中的数据中删除它。或者,使用完整的 URL 在 chathelp 上创建另一个列,以便我可以匹配它?希望避免改变任何东西。
  • 好的。看起来我的星期五晚上被枪杀了。感谢您的帮助,我接受了我的问题 #2 的答案。

标签: mysql performance join left-join


【解决方案1】:

我们猜测online 是整数数据类型。

您可以像这样修改 order by 子句中的表达式:

ORDER BY IFNULL(online,0) DESC, users.name ASC
         ^^^^^^^      ^^^ 

问题在于,对于user 中没有chathelp 中匹配行的行,结果集中online 列的值是NULL。而NULL 总是在所有非 NULL 值之后排序。

如果我们假设helpchat 中的缺失行与helpchat 中具有0 在线的行同等对待,我们可以将NULL 值替换为0。 (如果 online 列中有 NULL 值,我们将无法区分它和帮助聊天中的缺失行(在 ORDER BY 中使用此表达式。))

编辑

优化性能

为了解决性能问题,我们需要查看EXPLAIN 的输出。

使用上面编写的查询,无法绕过“使用文件排序”来获取在该表达式上按指定顺序返回的行。

我们也许可以重新编写查询以更快地获得等效结果。

但我怀疑“使用文件排序”操作并不是真正的问题,除非有大量(成千上万)行需要排序。

我怀疑没有合适的索引可用于连接操作。

但在我们下意识地“添加索引!”之前,我们确实需要查看EXPLAIN,并查看表定义包括索引。 (SHOW CREATE TABLE 的输出是合适的。

我们只是没有足够的信息来提出建议。

参考:8.8.1 Optimizing Queries with EXPLAIN

作为猜测,我们可能想尝试这样的查询:

 SELECT u.name
      , u.id
      , l.url
      , l.online
   FROM users
   LEFT
   JOIN livechat 
     ON l.url = CONCAT('http://www.software.com/', u.chat_handle)
    AND l.online = 1
  WHERE u.live_account = 'y'
  ORDER 
     BY IF(l.online=1,0,1) ASC 
      , u.name ASC
  LIMIT 0,24

在我们添加了覆盖索引之后,例如

.. ON user (live_account,chat_handle,name, id)
...ON livechat (url, online)

(如果查询使用了覆盖索引,则 EXPLAIN 应在 Extra 列中显示“Using index”。)

一种方法可能是将查询分成两部分:内连接和半反连接。这只是我们可能会尝试的猜测,但同样,我们想比较EXPLAIN 的输出。

有时,我们可以使用这样的模式获得更好的性能。但为了获得更好的性能,以下两个查询都需要比原始查询更高效:

 ( SELECT u.name
        , u.id
        , l.url
        , l.online
     FROM users u
     JOIN livechat 
       ON l.url = CONCAT('http://www.software.com/', u.chat_handle)
      AND l.online = 1
    WHERE u.live_account = 'y'
    ORDER 
       BY u.name ASC
    LIMIT 0,24
 )
 UNION ALL
 ( SELECT u.name
        , u.id
        , NULL AS url
        , 0    AS online
     FROM users u
     LEFT
     JOIN livechat 
       ON l.url = CONCAT('http://www.software.com/', u.chat_handle)
      AND l.online = 1
    WHERE l.url IS NULL
      AND u.live_account = 'y'
    ORDER 
       BY u.name ASC
    LIMIT 0,24
 )
 ORDER BY 4 DESC, 1 ASC
 LIMIT 0,24 

【讨论】:

  • 是的,online 是 int 值,您已经解决了问题 #2,因为它现在可以正确地对它们进行排序。但我还有问题一。运行需要 4 秒。
  • 为了解决性能问题,我们需要查看来自EXPLAIN 的输出。编写查询后,就无法绕过表达式上的“使用文件排序”。但我怀疑这不是真正的问题,我怀疑合适的索引不适用于连接操作。但是在我们下意识地“添加索引”之前,我们确实需要查看EXPLAIN,以及包含索引的表定义(即SHOW CREATE TABLE),我们还没有足够的信息。参考:8.8.1 Optimizing Queries with EXPLAIN
  • 谢谢。重读您写的内容并使用您的示例,我能够找出一些可以缩短时间的方法。
  • 我注意到 chatthelp 表是 MyISAM 引擎而不是 InnoDB。它不是 InnoDB 是否有特定原因?我对 UNION ALL 的两个查询的建议是基于您只返回“前 24 行”的想法,因此我们只需要(最多)具有活动聊天的“前 24 行”,并且在大多数,没有的“前 24 行”。我的想法是,我们可以让 MySQL 使用索引“按顺序”返回行,而无需对数千行执行排序操作。如果我们可以快速获得 24 行,并将其与另外 24 行连接,我们就可以对其进行排序。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-12-28
  • 1970-01-01
  • 1970-01-01
  • 2014-08-04
  • 2016-07-25
  • 2020-04-03
  • 1970-01-01
相关资源
最近更新 更多