Mysql慢查询:JOIN +多个WHERES + ORDER BY

时间:2010-09-30 22:54:51

标签: database mysql indexing query-optimization

长期潜伏,第一个问题!

我正在努力优化此查询,该查询选择与所选过滤器匹配的价格最低的商品:

SELECT product_info.*, MIN(product_all.sale_price) as sale_price, product_all.buy_link
FROM product_info
NATURAL JOIN (SELECT * FROM product_all WHERE product_all.date = '2010-09-30') as product_all
WHERE (product_info.category = 2  
AND product_info.gender = 'W' )
GROUP BY product_all.prod_id
ORDER BY MIN(product_all.sale_price) ASC LIMIT 13

它的解释:

| id | select_type | table        | type   | possible_keys                                             | key     | key_len | ref                 | rows   | Extra                           |  
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+  
|  1 | PRIMARY     | <derived2>   | ALL    | NULL                                                     | NULL    | NULL    | NULL                | 89801  | Using temporary; Using filesort | 
|  1 | PRIMARY     | product_info | eq_ref | PRIMARY,category_prod_id_retail_price,category_ret...     | PRIMARY | 4       | product_all.prod_id | 1      | Using where                     | 
|  2 | DERIVED     | product_all  | ref    | date_2                                                    | date_2  | 3       |                     | 144107 |                                 | 

我已经尝试消除子查询,直觉看起来更好但实际上需要更长时间:

SELECT product_info.*, MIN(product_all.sale_price) as sale_price, product_all.buy_link
FROM product_info
NATURAL JOIN product_all
WHERE (product_all.date = '2010-09-30'
AND product_info.category = 2 
AND product_info.gender = 'W' )
GROUP BY product_all.prod_id
ORDER BY MIN(product_all.sale_price) ASC LIMIT 13

它的解释:

| id | select_type | table        | type | possible_keys                                             | key                      | key_len | ref                               | rows | Extra                                        |  
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+  
|  1 | SIMPLE      | product_info | ref  | PRIMARY,category_prod_id_retail_price,category_ret...     | category_retail_price    | 5       | const                             | 269  | Using where; Using temporary; Using filesort | 
|  1 | SIMPLE      | product_all  | ref  | PRIMARY,prod_id,date_2                                    | prod_id                  | 4       | equipster_db.product_info.prod_id | 141  | Using where                                  | 

以下是表格:

CREATE TABLE `product_all` (
`prod_id` INT( 10 ) NOT NULL PRIMARY KEY ,
`ref_id` INT( 10) NOT NULL PRIMARY KEY ,
`date` DATE NOT NULL ,
`buy_link` BLOB NOT NULL ,
`sale_price` FLOAT NOT NULL
) ENGINE = MYISAM ;


CREATE TABLE `product_info` (
`prod_id` INT( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`prod_name` VARCHAR( 200 ) NOT NULL,
`brand` VARCHAR( 50 ) NOT NULL,
`retail_price` FLOAT NOT NULL
`category` INT( 3 ) NOT NULL,
`gender` VARCHAR( 1 ) NOT NULL,
`type` VARCHAR( 10 ) NOT NULL
) ENGINE = MYISAM ;

我的问题:
- 查询结构看起来最优? - 什么索引会优化这个查询? - 重要的是:在添加或删除WHERE子句或使用不同的ORDER BY时,索引方法如何变化,例如按%off排序:

ORDER BY (1-(MIN(product_all.sale_price)/product_info.retail_price)) DESC  

编辑两个查询的自然联接对prod_id起作用(product_info中的一条记录可以在product_all中有多个实例,这就是为什么需要对它们进行分组)

5 个答案:

答案 0 :(得分:4)

指数在mysql中产生了巨大的差异,一个错误的索引花了15分钟的查询用正确的指数花费了0.2秒,但它找到了正确的平衡,这通常是问题所在。自然没有一些样本数据,如果下面的解决方案能够在任何时候拯救你,那么很难说,但理论上应该这样做。

要回答你的问题,我会像这样重新设计表格:

CREATE TABLE `product_all` ( 
`prod_id` INT( 10 ) NOT NULL, 
`ref_id` INT( 10) NOT NULL, 
`date` DATE NOT NULL , 
`buy_link` BLOB NOT NULL , 
`sale_price` FLOAT NOT NULL,
PRIMARY KEY (prod_id, ref_id) ,
INDEX date_Index (`date` ASC),
UNIQUE INDEX prod_price_Index (prod_id ASC, sale_price ASC)
) ENGINE = MYISAM ; 


CREATE TABLE `product_info` ( 
`prod_id` INT( 10 ) NOT NULL AUTO_INCREMENT, 
`prod_name` VARCHAR( 200 ) NOT NULL, 
`brand` VARCHAR( 50 ) NOT NULL, 
`retail_price` FLOAT NOT NULL, 
`category` INT( 3 ) NOT NULL, 
`gender` VARCHAR( 1 ) NOT NULL, 
`type` VARCHAR( 10 ) NOT NULL,
PRIMARY KEY (prod_id) ,
UNIQUE INDEX prod_id_name_Index (prod_id ASC, prod_name ASC),
INDEX category_Index (category ASC),
INDEX gender_Index (gender ASC)
) ENGINE = MYISAM ;

SELECT product_info.*, MIN(product_all.sale_price) as sale_price, product_all.buy_link         
FROM product_info         
NATURAL JOIN (SELECT * FROM product_all WHERE product_all.date = '2010-09-30') as product_all         
WHERE (product_info.category = 2           
AND product_info.gender = 'W' )         
GROUP BY product_all.prod_id         
ORDER BY MIN(product_all.sale_price) ASC LIMIT 13        

此处的性能提升是通过索引正在连接的主要字段并在where子句中显示的。就个人而言,我会选择你的第一个查询,因为当你想到它应该表现得更好。

据我所知,第一和第二个问题中发生了什么:

  • 第一个查询正在过滤 在做之前的子查询 自然加入,这意味着它唯一 加入结果数据而不是 整个表格。
  • 第二个查询正在加入 完整的第二个表然后 过滤生成的行 整个回到你想要的东西。

根据经验,您通常希望在主要加入字段以及where子句中使用最多的字段添加索引。我还在一些你想要定期查询的字段上放了一些独特的索引,比如prod_id_name_Index。

如果您可以发布一些虚拟数据来提高您的性能,我可能会得到一个更快的解决方案,我可以进行基准测试。

Here是一篇关于mysql性能索引的文章,值得一读,如果你想了解更多。

祝你好运!

编辑:我第一次错过了你的最后一个问题,答案是,如果你对主要加入字段进行索引然后改变到哪里只会略微影响整体性能,但是我在表格上的唯一索引应该是占据了您希望基于查询的大部分内容。要记住的主要事情是,如果您经常查询或加入某个字段,那么它应该真正被编入索引,但是您在调整索引策略方面不应该担心订单的次要查询和更改。

答案 1 :(得分:0)

性能方面,使用它永远不是一件好事

select *

您应该使用单个列名称。

select column1,column2 etc...

答案 2 :(得分:0)

就个人而言,我是一个SQL极简主义者,并避免任何无法索引列索引的子查询或连接。

如果那不太可能,我可能会单独运行子查询来收集我的密钥,对它们进行客户端站点排序,然后在(...)子句中构建where。

JohnVD提出了很多好处,但如果你需要制作一个包含product_name的唯一密钥,你应该真的看看它是否可以归一化为它。

如果可能的话,索引varchar列是不惜一切代价的。每个索引条目都与列的最大大小一样大,即使它们通常只是其中的一小部分。如果你使用像utf-8这样的字符集,那么大小就是~maxlen + 3。

根据您的限制,似乎需要订单。但就像你在做一个小组时的FYI一样,如果你打算使用整个结果集,那么就使用ORDER BY NULL。通过解释运行这两个变体,看看为什么; order by null消除了隐含的filesort,你可以对客户端进行排序。 (如果你正在使用汇总进行分组,这是不可能的)

答案 3 :(得分:0)

你应该坚持第二个查询。在列上使用索引可以最大程度地减少受影响的行。在这种情况下,它可能是日期。如果过滤条件总是包含多个列,则应尝试多列索引。 MySQL只会使用一个索引。

答案 4 :(得分:0)

正如米奇所说,试图找到自然会有较低记录数的标准肯定会赢得表现。如果Category + Gender非常常见,请将其作为BOTH列的索引。此外,一旦找到最佳条件,您可以更改以下查询以更好地匹配它。 “STRAIGHT_JOIN”告诉MySQL按你所说的顺序执行,而不是尝试更改用于查询基础和加入其他的主表...所以,我不知道哪个更准确的类别索引,性别或日期...如果日期将有更少的记录基础,那么我会将THAT作为FROM子句中的第一个表交换,并在心理上将日期的IT标准移动到WHERE子句的第一个位置(仅我个人)与视觉保持同步()。我已经看到STRAIGHT_JOIN在许多情况下显着提高了性能,否则这些情况似乎是简单的查询。

SELECT STRAIGHT_JOIN
      product_info.*, 
      MIN(product_all.sale_price) as sale_price, 
      product_all.buy_link 
   FROM 
      product_info,
      product_all 
   where 
          product_info.category = 2   
      AND product_info.gender = 'W'
      and product_info.prod_id = product_all.prod_id
      AND product_all.date = '2010-09-30'
   GROUP BY 
      product_info.prod_id 
   ORDER BY 
      MIN(product_all.sale_price) ASC 
   LIMIT 13