如何使此查询更有效?

时间:2011-12-22 03:42:49

标签: mysql query-optimization

编辑:这是原始查询的简化版本(在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;

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

3 个答案:

答案 0 :(得分:1)

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

另外请确保您已定义外键并将其编入索引。

祝你好运。

答案 1 :(得分:0)

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

要非常小心select * from table where where in(select 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

答案 2 :(得分:0)

由于在这种情况下难以找到将应用ORDER BY的索引,因此由于排序而导致查询缓慢这一事实很明显。主要问题是products.date >=比较,它使用ORDER BY的任何索引中断。由于您需要输出大量数据,因此MySQL开始使用临时表进行排序。

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

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

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

所以,问题是如何在商店,日期,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的结果会很好。