【问题标题】:Mysql slow query: JOIN + multiple WHERES + ORDER BYMysql 慢查询:JOIN + 多个 WHERES + ORDER BY
【发布时间】:2011-04-19 14:54:57
【问题描述】:

潜伏已久,第一个问题!

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

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 中可以有多个实例,这就是它们需要分组的原因)

【问题讨论】:

  • 其中一个 PK 是复合的,但是每个组都是一行:产品 ID、该产品的最低价格和相关数据。编辑:这是对似乎已经消失的评论的回应。 edit2:是的,我想我点击了编辑而不是添加评论......顺利。

标签: database mysql indexing query-optimization


【解决方案1】:

索引在 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 中的性能的文章,如果您想了解更多信息,值得一读。

祝你好运!

编辑:我第一次错过的最后一个问题,答案是,如果您对主要连接字段进行索引,然后更改 where 只会稍微影响整体性能,但我放在表上的唯一索引应该占您要作为查询基础的大部分内容。要记住的主要事情是,如果您经常查询或加入某个字段,那么它确实应该被索引,但是您不应该担心在重新调整索引策略方面的小查询和对顺序的更改。

【讨论】:

  • 乔恩,谢谢!那些多列索引起到了作用,而且您的编辑也很到位,顺序并没有真正拖累查询,因为它只在 13 行上运行。干杯!
  • 乔恩,你帮了我们大忙。 JOIN 索引上的那一点是我以前从未听说过的,它是类似问题的救命稻草。
  • 总是很高兴听到!这是数据库设计中经常被忽视的一部分,有时可能会让您付出高昂的代价,很乐意提供帮助。
【解决方案2】:

在性能方面,使用它从来都不是一件好事

select *

您应该改用单独的列名。

select column1,column2 etc...

【讨论】:

  • 这个词...我确实知道的为数不多的事情之一,但认为它可以忽略不计并提高了我的问题的可读性。
【解决方案3】:

我个人是一个 sql 极简主义者,避免任何类型的子查询或不能索引到索引列的连接。

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

JohnVD 提出了很多好的观点,但如果您需要创建一个包括 product_name 在内的唯一键,您应该看看是否可以将其规范化为 it。

如果可能,不惜一切代价避免为 varchar 列编制索引。每个索引条目都与列的最大大小一样大,即使它们通常只是其中的一小部分。如果您使用的是 utf-8 之类的字符集,则大小为 ~ maxlen+3。

根据您的限制,似乎需要订购。但是,正如您在进行分组时的仅供参考一样,如果您要使用整个结果集,则添加 ORDER BY NULL。通过 explain 运行这两个变体,看看为什么;按 null 的顺序消除了隐含的文件排序,您可以对客户端进行排序。 (如果您使用汇总进行分组,则这是不可能的)

【讨论】:

    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      正如 Mitch 所说,试图找到自然记录数较少的标准肯定会赢得性能。如果 Category + Gender 很常见,请在 BOTH 列上创建一个索引。此外,一旦您找到最佳标准,您可能会更改以下查询以更好地匹配它。 “STRAIGHT_JOIN”告诉 MySQL 按照您声明的顺序执行此操作,而不是尝试更改用于查询基础的主表并连接到另一个...所以,我不知道哪个更准确的索引类别,性别或日期...如果日期的记录基础较少,那么我会将其交换为 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 
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-03-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-15
        相关资源
        最近更新 更多