【问题标题】:how can I make this query more efficient?我怎样才能使这个查询更有效率?
【发布时间】:2011-12-22 03:42:48
【问题描述】:

编辑:这是原始查询的简化版本(在 475K 行的产品表上运行 3.6 秒)

SELECT p.*, shop FROM products p JOIN
users u ON p.date >= u.prior_login and u.user_id = 22 JOIN
shops s ON p.shop_id = s.shop_id
ORDER BY shop, date, product_id;

这是解释计划

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  u   const   PRIMARY,prior_login,user_id PRIMARY 4   const   1   Using temporary; Using filesort
1   SIMPLE  s   ALL PRIMARY NULL    NULL    NULL    90   
1   SIMPLE  p   ref shop_id,date,shop_id_2,shop_id_3    shop_id 4   bitt3n_minxa.s.shop_id  5338    Using where

瓶颈似乎是ORDER BY date,product_id。除去这两个排序,查询在 0.06 秒内运行。 (删除两者之一(但不是两者)几乎没有效果,查询仍然需要 3 秒以上。)我在 products 表中的 product_id 和 date 都有索引。我还在(产品,日期)上添加了一个索引,但没有任何改进。

newtover 认为问题在于 INNER JOIN users u1 ON products.date >= u1.prior_login 要求阻止使用 products.date 上的索引

已向我建议(不是来自此线程)在约 0.006 秒内执行的查询的两种变体(而不是原始的 3.6 秒)。

这个使用子查询,它似乎强制连接的顺序

SELECT p.*, shop 
  FROM 
  (
    SELECT p.*
    FROM products p 
    WHERE p.date >= (select prior_login FROM users where user_id = 22)
  ) as p
  JOIN shops s 
    ON p.shop_id = s.shop_id
  ORDER BY shop, date, product_id;

这个使用 WHERE 子句做同样的事情(虽然 SQL_SMALL_RESULT 的存在不会改变执行时间,没有它也是 0.006 秒)

SELECT SQL_SMALL_RESULT p . * , shop
FROM products p
INNER JOIN shops s ON p.shop_id = s.shop_id
WHERE p.date >= ( 
SELECT prior_login
FROM users
WHERE user_id =22 ) 
ORDER BY shop, DATE, product_id;

我的理解是,由于在将产品表连接到商店表之前减少了产品表的相关行数,这些查询的工作速度要快得多。我想知道这是否正确。

【问题讨论】:

  • 顺便问一下,你需要在circle_favorited上排序的多个product_id行是从哪里来的?
  • @newtover 用户的多个朋友可以收藏同一个产品,因此每个喜欢具有给定 product_id 产品的不同朋友都会生成一个包含该 product_id(和朋友的用户名)的新行。收藏夹表中的每一行都包含一个 user_id 和一个二进制收藏夹值。如果此值为 0,则表示用户对产品投了反对票。如果此值为 1,则用户对产品投了赞成票。我按照 circle_favorited 进行排序,以便区分那些对产品投赞成票的用户和对它投反对票的用户。

标签: mysql query-optimization


【解决方案1】:

使用EXPLAIN 语句查看执行计划。您也可以尝试为products.dateu1.prior_login 添加索引。

另外,请确保您已经定义了外键并对其进行了索引。

祝你好运。

【讨论】:

  • 我在上面添加了一个解释计划。在 date 和 prior_login 上添加索引不会改变执行时间。
【解决方案2】:

我们确实需要一个解释计划……但是

非常小心 select * from table where id in (select id from another_table) 这是臭名昭著的。通常这些可以用连接代替。以下查询可能会运行,尽管我还没有测试过。

SELECT shop,
       shops.shop_id AS shop_id,
       products.product_id AS product_id,
       brand,
       title,
       price,
       image AS image,
       image_width,
       image_height,
       0 AS sex,
       products.date AS date,
       fav1.favorited AS circle_favorited,
       fav2.favorited AS session_user_favorited,
       u2.username AS circle_username
  FROM products
       LEFT JOIN favorites fav2
          ON     fav2.product_id = products.product_id
             AND fav2.user_id = 22
             AND fav2.current = 1
       INNER JOIN shops
          ON shops.shop_id = products.shop_id
       INNER JOIN users u1
          ON products.date >= u1.prior_login AND u1.user_id = 22
       LEFT JOIN favorites fav1
          ON products.product_id = fav1.product_id
       LEFT JOIN friends f1
          ON f1.star_id = fav1.user_id
       LEFT JOIN users u2
          ON fav1.user_id = u2.user_id
 WHERE f1.fan_id = 22 OR fav1.user_id = 22
ORDER BY shop,
         DATE,
         product_id,
         circle_favorited

【讨论】:

  • 谢谢,我在上面添加了一个解释计划,现在将测试您的查询
  • 您的查询只产生 7 个结果,而不是原来的 497 个。我相信这是因为 WHERE 子句将结果限制在那些情况下(f1.fan_id = 22 或 fav1.user_id = 22),而对于大多数产品而言,情况并非如此。如果我移动这些标准以使它们成为LEFT JOIN friends f1 声明的一部分,那么当用户收藏该产品时,我似乎得到了一个产品的重复行(即两行,圆圈用户名作为该产品的会话用户),这在原始查询中没有发生。查询耗时 3.7 秒。
  • 我在解释计划中没有看到任何让我觉得非常离谱的内容。我的建议是逐表构建查询并找出导致最大性能损失的原因。应该不会花太长时间,因为您已经有了一个功能查询。
  • 我听从了您的建议,发现当我删除 ORDER BY product_id, date 时执行时间变为 0.06 秒。 (如果我按 product_id、date 或两者中的任何一个进行订购,查询所需的时间大约相同,超过 3 秒。)我有 product_id 和 date 的索引,所以我不知道如何解决这个问题。
  • 您是否有 product_id 和 date 的组合索引(列顺序很重要,所以尝试两者)这与 product_id 和 date 上的单独索引不同
【解决方案3】:

查询由于排序而变慢的事实是相当明显的,因为在这种情况下很难找到将应用 ORDER BY 的索引。主要问题是products.date >= 比较,它破坏了使用 ORDER BY 的任何索引。由于要输出大量数据,MySQL 开始使用临时表进行排序。

我要做的是尝试强制 MySQL 输出数据按已经具有所需顺序的索引的顺序并删除 ORDER BY 子句。

我不是在电脑前测试,但我该怎么做:

  • 我会做所有的内部连接
  • 然后我将 LEFT JOIN 加入一个子查询,该子查询对按 product_id、circle_favourited 排序的收藏夹进行所有计算(这将提供最后的排序标准)。

那么,问题是如何让数据按shop、date、product_id排序

我稍后再写=)

UPD1:

您可能应该阅读一些关于 btree 索引如何在 MySQL 中工作的内容。 mysqlperformanceblog.com 上有一篇关于它的好文章(我目前是用手机写的,手头没有链接)。简而言之,您似乎在谈论单列索引,它根据在单列中排序的值排列指向行的指针。复合索引存储基于多个列的顺序。索引主要用于在明确定义的范围内进行操作,以在从它们指向的行中检索数据之前获取大部分信息。索引通常不知道同一张表上的其他索引,因此它们很少被合并。当没有更多信息可以从索引中获取时,MySQL 开始直接对数据进行操作。

这是一个日期索引不能使用product_id上的索引,但是(date,product_id)上的索引可以在日期条件之后获得关于product_id的更多信息(对特定日期匹配的产品id排序)。

不过,日期 (>=) 上的范围条件打破了这一点。这就是我所说的。

UPD2:

据我了解,问题可以归结为(大部分时间都花在这上面):

SELECT p.*, shop
FROM products p
JOIN users u ON p.`date` >= u.prior_login and u.user_id = 22
JOIN shops s ON p.shop_id = s.shop_id
ORDER BY shop, `date`, product_id;

现在在用户和产品上添加索引(user_id,prior_login)和(日期),并尝试以下查询:

SELECT STRAIGHT_JOIN p.*, shop
FROM (
  SELECT product_id, shop
  FROM users u
  JOIN products p
    user_id = 22 AND p.`date` >= prior_login
  JOIN shops s
    ON p.shop_id = s.shop_id
  ORDER BY shop, p.`date`, product_id
) as s
JOIN products p USING (product_id);

如果我是正确的,查询应该返回相同的结果但更快。如果您能发布查询的 EXPLAIN 结果,那就太好了。

【讨论】:

  • 如果我理解正确,问题是如果我想使用 products.date 上的索引,我不能同时使用 products.date 来限制结果。我对学习如何在没有 ORDER BY 的情况下进行排序非常感兴趣。到现在还想不通。
  • 我也很好奇为什么ORDER BY product_id 也会导致瓶颈,即使这不涉及products.date >= 比较
  • 不幸的是,该查询以与原始查询相同的速度运行,大约 3.5 秒。我确实在 products 表上创建了一个索引('shop_id,date')。也许需要一些其他索引?
  • 另外,我注意到如果我从简化查询(您的答案中的第一个)中删除 JOIN users u ON p.date` >= u.prior_login 和 u.user_id = 22`,查询几乎需要恰好是执行时间的两倍(超过七秒)。没有这个 JOIN 的查询检索 475K 行(而有 JOIN 的查询大约 1K)。这是否表明 JOIN 不会干扰索引?
  • @jela,我更新了最后一个查询。试试这个并显示解释。您可以在 pastebin 上提供一个链接,我想看看。
猜你喜欢
  • 2010-11-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-29
  • 1970-01-01
  • 2015-12-23
相关资源
最近更新 更多