【问题标题】:why is select much slower with left join+where为什么选择左连接+位置要慢得多
【发布时间】:2013-09-14 00:26:00
【问题描述】:

我正在尝试优化一个需要很长时间才能处理的 MySQL 查询。假设我们有两个表,一个用户表和一个购买表。两个表都有大约 20,000 行。

mysql> 
SELECT NOW(),u.id
    FROM users u
    LEFT JOIN purchases p
        ON p.user_id = u.id
    WHERE
        p.website_id = 1234
    ORDER BY u.total_paid DESC
    LIMIT 10;
+---------------------+-------+
| NOW()               | id    |
+---------------------+-------+
*snip*
+---------------------+-------+
10 rows in set (0.06 sec)

不是超级快,但非常活泼。如果我除了将 u.id 更改为 u.* 之外什么都不做,它会显着减速:

mysql>
SELECT NOW(),u.*
    FROM users u
    LEFT JOIN purchases p
        ON p.user_id = u.id
    WHERE
        p.website_id = 1234
    ORDER BY u.total_paid DESC
    LIMIT 10;
+---------------------+-------+
*snip*
+---------------------+-------+
10 rows in set (0.37 sec)

在你说“好吧,你永远不应该使用select *”之前,请考虑到你添加的字段越多,它就会慢慢爬到那个时间长度,即命名一半要选择的字段将导致查询在 ~0.20 内执行秒,并且 users 表中没有字段大于 varchar(255)

但是,如果我从相对快速的查询中获取 id,我只是:

mysql>
SELECT *
    FROM users
    WHERE id IN (*snip*);
+---------------------+-------+
*snip*
+---------------------+-------+
10 rows in set (0.01 sec)

所以我的两个查询:select u.id 加上 select u.* where id in 比我假设的类似查询要快。什么鬼?

更多信息: users 表上有大约 30 个字段。同样,没有字段大于 varchar(255)

更多详情:这两个查询的解释是这样的:

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: p
         type: ref
possible_keys: PRIMARY,user_id_index,website_id_index,website_user_id_index,website_created_index,website_type_created_index,website_type_index,purchase_user_id_type_index,user_id_website_id_index,website_id_user_id_index
          key: website_id_user_id_index
      key_len: 9
          ref: const
         rows: 9976
        Extra: Using where; Using index; Using temporary; Using filesort
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: u
         type: eq_ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 8
          ref: database.p.user_id
         rows: 1
        Extra:

编辑可能是因为它使用临时/文件排序,它必须从用户中选择 *,不知道哪些行最终会出现在最终结果集中?因此,这似乎是微不足道的额外数据量,但实际上这是选择表的一大块之间的区别?如果这是正确的,有什么建议吗?

【问题讨论】:

  • 您查看过每个查询的EXPLAIN 吗?我猜你的SELECT * 需要扫描更多行。
  • 我有,是的。两者都在扫描 9976 行,并且都使用相同的 website_id_user_id_index 索引。
  • 在这里发布它们会有所帮助。
  • 刚刚发布它们(带着想法)。
  • 不知道MySQL是否会在EXPLAIN中显示这个:如果只查询u.id,MySQL根本不需要查看表,它可以从索引叶中检索值.选择u.* 会强制它检索表数据,从而使查询变慢。除此之外,您的查询没有多大意义,因为您首先是 outer join,然后是 filter,然后是 p。这不应该对服务器产生影响,但查询应该读取 left join ... on p.user_id = u.id and p.website_id = 1234 而不使用 where-clause 或使用 inner join

标签: mysql sql


【解决方案1】:

首先,我想问/部分回答。你到底要什么。您对购买表有一个 LEFT-JOIN,但随后有一个用于特定“购买”网站 ID 的 WHERE 子句。这实质上是将查询带到 INNER JOIN 并仅返回那些从相关站点购买的用户。也就是说,我会将查询重写为

select 
      NOW(),
      u.id 
   from 
      purchases p
         JOIN users u 
            ON p.user_id = u.id
   where 
      p.website_id = 1234 
   order by 
      u.total_paid desc 
   limit 
      10;

假设您在 (Website_ID) 上有一个索引,这将首先从购买开始并加入用户,但仅适用于网站 1234 上的购买。这也可能给出错误答案,因为如果一个用户从同一个网站,他们是顶级买家之一……他们的 ID 可能会出现多次。为了防止这种情况发生,我会从站点预先查询 DISTINCT 用户,然后加入用户。我会在 (Website_ID, user_ID) 的购买表上有一个索引,然后执行以下操作。

select 
      NOW(),
      u.id 
   from 
      ( select distinct p.user_id
           from purchases p
           where p.website_id = 1234 ) PQ
         JOIN users u 
            ON PQ.user_id = u.id
   order by 
      u.total_paid desc 
   limit 
      10;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-10-16
    • 2018-03-15
    • 1970-01-01
    • 1970-01-01
    • 2022-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多