MySQL选择查询变得非常慢,BOTH在哪里和降序

时间:2017-09-15 13:04:49

标签: mysql performance innodb query-performance

我有这个选择查询,ItemType是varchar类型,ItemComments是int类型:

select * from ItemInfo where ItemType="item_type" order by ItemComments desc limit 1 

您可以看到此查询有3个条件

  1. 其中'ItemType'等于特定值;
  2. 按'ItemComments'排序
  3. 降序排列
  4. 有趣的是,当我选择具有所有三个条件的行时,它变得非常慢。但是如果我删除三者中的任何一个(条件2除外),查询运行得非常快。参见:

    select * from ItemInfo where ItemType="item_type" order by ItemComments desc limit 1;
    /* Affected rows: 0  Found rows: 1  Warnings: 0  Duration for 1 query: 16.318 sec. */
    
    select * from ItemInfo where ItemType="item_type" order by ItemComments limit 1;
    /* Affected rows: 0  Found rows: 1  Warnings: 0  Duration for 1 query: 0.140 sec. */
    
    select * from ItemInfo order by ItemComments desc limit 1;
    /* Affected rows: 0  Found rows: 1  Warnings: 0  Duration for 1 query: 0.015 sec. */
    

    另外,

    1. 我正在使用带有InnoDB引擎的MySQL 5.7。
    2. 我在ItemType和ItemComments上创建了索引,表ItemInfo包含200万行。
    3. 我搜索过许多可能的解释,例如MySQL支持降序索引,复合索引等。但是这些仍然无法解释为什么查询#1运行缓慢,而查询#2和#3运行良好。

      如果有人能帮助我,我们将非常感激。

      更新:创建表格并解释信息

      创建代码:

      CREATE TABLE `ItemInfo` (
      `ItemID` VARCHAR(255) NOT NULL,
      `ItemType` VARCHAR(255) NOT NULL,
      `ItemPics` VARCHAR(255) NULL DEFAULT '0',
      `ItemName` VARCHAR(255) NULL DEFAULT '0',
      `ItemComments` INT(50) NULL DEFAULT '0',
      `ItemScore` DECIMAL(10,1) NULL DEFAULT '0.0',
      `ItemPrice` DECIMAL(20,2) NULL DEFAULT '0.00',
      `ItemDate` DATETIME NULL DEFAULT '1971-01-01 00:00:00',
      PRIMARY KEY (`ItemID`, `ItemType`),
      INDEX `ItemDate` (`ItemDate`),
      INDEX `ItemComments` (`ItemComments`),
      INDEX `ItemType` (`ItemType`)
      )
      COLLATE='utf8_general_ci'
      ENGINE=InnoDB;
      

      解释结果:

      mysql> explain select * from ItemInfo where ItemType="item_type" order by ItemComments desc limit 1;
      +----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
      | id | select_type | table | partitions | type  | possible_keys | key          | key_len | ref  | rows | filtered | Extra       |
      +----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
      |  1 | SIMPLE      | i     | NULL       | index | ItemType      | ItemComments | 5       | NULL |   83 |     1.20 | Using where |
      +----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
      
      mysql> explain select * from ItemInfo where ItemType="item_type" order by ItemComments limit 1;
      +----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
      | id | select_type | table | partitions | type  | possible_keys | key          | key_len | ref  | rows | filtered | Extra       |
      +----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
      |  1 | SIMPLE      | i     | NULL       | index | ItemType      | ItemComments | 5       | NULL |   83 |     1.20 | Using where |
      +----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
      
      mysql> explain select * from ItemInfo order by ItemComments desc limit 1;
      +----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------+
      | id | select_type | table | partitions | type  | possible_keys | key          | key_len | ref  | rows | filtered | Extra |
      +----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------+
      |  1 | SIMPLE      | i     | NULL       | index | NULL          | ItemComments | 5       | NULL |    1 |   100.00 | NULL  |
      +----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------+
      

      来自O. Jones的查询

      mysql> explain
          ->  SELECT a.*
          ->      FROM ItemInfo a
          ->      JOIN (
          ->             SELECT MAX(ItemComments) ItemComments, ItemType
          ->               FROM ItemInfo
          ->              GROUP BY ItemType
          ->           ) maxcomm ON a.ItemType = maxcomm.ItemType
          ->                    AND a.ItemComments = maxcomm.ItemComments
          ->     WHERE a.ItemType = 'item_type';
      +----+-------------+------------+------------+-------+----------------------------------------+-------------+---------+---------------------------+---------+----------+--------------------------+
      | id | select_type | table      | partitions | type  | possible_keys                          | key         | key_len | ref                       | rows    | filtered | Extra                    |
      +----+-------------+------------+------------+-------+----------------------------------------+-------------+---------+---------------------------+---------+----------+--------------------------+
      |  1 | PRIMARY     | a          | NULL       | ref   | ItemComments,ItemType                  | ItemType    | 767     | const                     |   27378 |   100.00 | Using where              |
      |  1 | PRIMARY     | <derived2> | NULL       | ref   | <auto_key0>                            | <auto_key0> | 772     | mydb.a.ItemComments,const |      10 |   100.00 | Using where; Using index |
      |  2 | DERIVED     | ItemInfo   | NULL       | index | PRIMARY,ItemDate,ItemComments,ItemType | ItemType    | 767     | NULL                      | 2289466 |   100.00 | NULL                     |
      +----+-------------+------------+------------+-------+----------------------------------------+-------------+---------+---------------------------+---------+----------+--------------------------+
      

      我不确定我是否正确执行此查询,但我无法在相当长的时间内获取记录。

      来自Vijay的查询。但是我添加了ItemType连接条件原因,只有max_comnt从其他ItemType返回项目:

      SELECT ifo.* FROM ItemInfo ifo 
      JOIN (SELECT ItemType, MAX(ItemComments) AS max_comnt FROM ItemInfo WHERE ItemType="item_type") inn_ifo 
      ON ifo.ItemComments = inn_ifo.max_comnt and ifo.ItemType = inn_ifo.ItemType
      /* Affected rows: 0  Found rows: 1  Warnings: 0  Duration for 1 query: 7.441 sec. */
      
      explain result:
      +----+-------------+------------+------------+-------------+-----------------------+-----------------------+---------+-------+-------+----------+-----------------------------------------------------+
      | id | select_type | table      | partitions | type        | possible_keys         | key                   | key_len | ref   | rows  | filtered | Extra                                               |
      +----+-------------+------------+------------+-------------+-----------------------+-----------------------+---------+-------+-------+----------+-----------------------------------------------------+
      |  1 | PRIMARY     | <derived2> | NULL       | system      | NULL                  | NULL                  | NULL    | NULL  |     1 |   100.00 | NULL                                                |
      |  1 | PRIMARY     | ifo        | NULL       | index_merge | ItemComments,ItemType | ItemComments,ItemType | 5,767   | NULL  |    88 |   100.00 | Using intersect(ItemComments,ItemType); Using where |
      |  2 | DERIVED     | ItemInfo   | NULL       | ref         | ItemType              | ItemType              | 767     | const | 27378 |   100.00 | NULL                                                |
      +----+-------------+------------+------------+-------------+-----------------------+-----------------------+---------+-------+-------+----------+-----------------------------------------------------+
      

      我想解释为什么我首先使用带限制的订单:我打算以特定的概率随机从表中获取记录。随机索引从python生成并作为变量发送到MySQL。但后来我发现它耗费了很多时间,所以我决定只使用我得到的第一张唱片。

      经过O. Jones和Vijay的鼓舞,我尝试使用max函数,但效果不佳:

      select max(ItemComments) from ItemInfo where ItemType='item_type'
      /* Affected rows: 0  Found rows: 1  Warnings: 0  Duration for 1 query: 6.225 sec. */
      
      explain result:
      +----+-------------+------------+------------+------+---------------+----------+---------+-------+-------+----------+-------+
      | id | select_type | table      | partitions | type | possible_keys | key      | key_len | ref   | rows  | filtered | Extra |
      +----+-------------+------------+------------+------+---------------+----------+---------+-------+-------+----------+-------+
      |  1 | SIMPLE      | ItemInfo   | NULL       | ref  | ItemType      | ItemType | 767     | const | 27378 |   100.00 | NULL  |
      +----+-------------+------------+------------+------+---------------+----------+---------+-------+-------+----------+-------+
      

      感谢所有人对这个问题的贡献。希望您能根据上述信息提供更多解决方案。

3 个答案:

答案 0 :(得分:1)

您的第一个查询升序排序可以利用ItemComment上的索引。

SELECT * ... ORDER BY ... LIMIT 1是一个臭名昭着的性能反模式。为什么?服务器必须对一大堆行进行排序,只是为了丢弃除第一行之外的所有行。

你可以尝试这个(对于你的降序变体)。它更冗长,但效率更高。

   SELECT a.* 
     FROM ItemInfo a
     JOIN (
            SELECT MAX(ItemComments) ItemComments, ItemType
              FROM ItemInfo
             GROUP BY ItemType
          ) maxcomm ON a.ItemType = maxcomm.ItemType
                   AND a.ItemComments = maxcomm.ItemComments
    WHERE a.ItemType = 'item type'

为什么这样做?它使用GROUP BY / MAX()来查找最大值,而不是ORDER BY ... DESC LIMIT 1。子查询会进行搜索。

为了使这项工作尽可能高效,您需要(ItemType, ItemComments)上的复合(多列)索引。使用

创建
ALTER TABLE ItemInfo CREATE INDEX ItemTypeCommentIndex (ItemType, ItemComments);

创建新索引时,请将索引放在ItemType上,因为新索引与该索引是多余的。

MySQL的查询规划器足够智能,可以在运行内部WHERE查询之前查看外部GROUP BY子句,因此不必聚合整个表。

使用该复合索引,MySQL可以使用松散索引扫描来满足子查询。那几乎是奇迹般的快。你应该阅读这个主题。

答案 1 :(得分:1)

请提供CURRENT SHOW CREATE TABLE ItemInfo

对于大多数这些查询,您需要复合索引

INDEX(ItemType, ItemComments)

对于最后一个,你需要

INDEX(ItemComments)

对于特别慢的查询,请提供EXPLAIN SELECT ...

讨论 - 为什么INDEX(ItemType, ItemComments)可以帮助where ItemType="item_type" order by ItemComments desc limit 1

索引的结构为BTree(请参阅维基百科),从而可以非常快速地搜索单个项目,并且可以非常快速地按特定顺序进行扫描。

where ItemType="item_type"说要过滤ItemType,但索引中有很多这样的内容。在此索引中,它们按ItemComments排序(对于给定的ItemType)。方向desc建议以ItemContents的最高值开头;这是索引项的“结束”。最后limit 1表示在找到一个项目后停止。 (有点像在你的Rolodex中找到最后一个“S”。)

因此,查询是将BTree“向下钻取”到复合ItemTypeINDEX(ItemType, ItemContents)条目的末尾并抓取一个条目 - 这是一项非常有效的任务。

实际上SELECT *意味着还有一个步骤,即获取该行的所有列。该信息不在索引中,而是在ItemInfo的BTree中 - 包含所有行的所有列,按PRIMARY KEY排序。

“二级索引”(INDEX(ItemType, ItemComments))隐式包含相关PRIMARY KEY列的副本,因此我们现在具有ItemIDItemType的值。有了这些,我们可以深入查看其他BTree以找到所需的行并获取所有列(*)。

答案 2 :(得分:0)

您的查询将根据where条件选择所有行。之后,它将按顺序依据语句对行进行排序,然后选择第一行。更好的查询类似于

SELECT ifo.* FROM ItemInfo ifo 
JOIN (SELECT MAX(ItemComments) AS max_comnt FROM ItemInfo WHERE ItemType="item_type") inn_ifo 
ON ifo.ItemComments = inn_ifo.max_comnt

由于此查询仅从列中找到最大值。寻找MAX()只是O(n),但最快的排序算法是O(nlogn)。因此,如果您通过statemet避免订单,则查询将执行得更快。 希望这有帮助。