查询速度的巨大差异

Posted

技术标签:

【中文标题】查询速度的巨大差异【英文标题】:Abstruse difference in query speed 【发布时间】:2017-08-03 18:38:40 【问题描述】:

我不明白这两个EXPLAINs 的区别(第 2 行)。也许有人给我一个提示,为什么 mysql 对这些行为如此不同,这会严重影响查询速度。

慢查询持续 12 秒(相当于使用该查询查询所有行)并在整数列上使用连接,而连接的表只有 3 条记录:

SELECT `inv_assets`.`id` AS `id`, `site`.`description` AS `sitename`, 
  (SELECT COALESCE(DATE_FORMAT(CONVERT_TZ(MIN(inspdate),'UTC','Europe/Vienna'),'%Y-%m-%d'),'') 
   FROM `mobuto_inv_inspections` AS `nextinsp` 
   WHERE ((`nextinsp`.`objectlink` = `inv_assets`.`id` 
            AND `nextinsp`.`inspdate` >= NOW()))
   ) AS `nextinsp` 
FROM `mobuto_inv_assets` AS `inv_assets` 
LEFT JOIN `mobuto_inv_sites` AS `site` 
  ON (`site`.`siteid` = `inv_assets`.`site` 
  AND `site`.`_state` IN (2,0)) 
ORDER BY `inv_assets`.`type` ASC LIMIT 0, 20;

+----+--------------------+------------+--------+----------------+---------+---------+------------------------------+-------+----------------------------------------------------+
| id | select_type        | table      | type   | possible_keys  | key     | key_len | ref                          | rows  | Extra                                              |
+----+--------------------+------------+--------+----------------+---------+---------+------------------------------+-------+----------------------------------------------------+
|  1 | PRIMARY            | inv_assets | ALL    | NULL           | NULL    | NULL    | NULL                         | 24857 | Using temporary; Using filesort                    |
|  1 | PRIMARY            | site       | ALL    | PRIMARY,_state | NULL    | NULL    | NULL                         |     3 | Using where; Using join buffer (Block Nested Loop) |
|  2 | DEPENDENT SUBQUERY | nextinsp   | ALL    | inspdate       | NULL    | NULL    | NULL                         |   915 | Using where                                        |
+----+--------------------+------------+--------+----------------+---------+---------+------------------------------+-------+----------------------------------------------------+

快速查询只消耗几分之一秒,在 varchar(32) 列上使用连接,连接表有 1352 条记录:

SELECT `inv_assets`.`id` AS `id`, `guarantor`.`lastname` AS `guarantoruname`, 
      (SELECT COALESCE(DATE_FORMAT(CONVERT_TZ(MIN(inspdate),'UTC','Europe/Vienna'),'%Y-%m-%d'),'') 
       FROM `mobuto_inv_inspections` AS `nextinsp` 
       LEFT JOIN `users` AS `saveuser` 
       ON (`saveuser`.`uid` = `nextinsp`.`saveuser` 
            AND `saveuser`.`_state` = '0') 
       WHERE ((`nextinsp`.`objectlink` = `inv_assets`.`id` 
                AND `nextinsp`.`inspdate` >= NOW()))
       ) AS `nextinsp` 
FROM `mobuto_inv_assets` AS `inv_assets` 
LEFT JOIN `users` AS `guarantor` 
ON (`guarantor`.`uid` = `inv_assets`.`guarantor` 
     AND `guarantor`.`_state` = '0') 
ORDER BY `inv_assets`.`type` ASC LIMIT 0, 20;

+----+--------------------+------------+--------+----------------+---------+---------+---------------------------------+-------+----------------+
| id | select_type        | table      | type   | possible_keys  | key     | key_len | ref                             | rows  | Extra          |
+----+--------------------+------------+--------+----------------+---------+---------+---------------------------------+-------+----------------+
|  1 | PRIMARY            | inv_assets | ALL    | NULL           | NULL    | NULL    | NULL                            | 24857 | Using filesort |
|  1 | PRIMARY            | guarantor  | eq_ref | PRIMARY,_state | PRIMARY | 98      | mobuto_dev.inv_assets.guarantor |     1 | Using where    |
|  2 | DEPENDENT SUBQUERY | nextinsp   | ALL    | inspdate       | NULL    | NULL    | NULL                            |   915 | Using where    |
|  2 | DEPENDENT SUBQUERY | saveuser   | eq_ref | PRIMARY,_state | PRIMARY | 98      | mobuto_dev.nextinsp.saveuser    |     1 | Using where    |
+----+--------------------+------------+--------+----------------+---------+---------+---------------------------------+-------+----------------+

对我来说奇怪的是,当我删除“column-select-part”中连接表的列 (description) 时(而连接仍然存在,恕我直言,mysql 在不使用时不会优化它),速度又回来了(因为mysql不再使用临时表,并且解释看起来和快速的一样,有type=eq_ref)。

但是为什么只有在没有选择列时才对第一个样本有效,而我可以在第二个样本中选择一个!?

CREATE TABLE `mobuto_inv_assets` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `invnum` varchar(10) NOT NULL,
  `oebglcat` varchar(4) NOT NULL,
  `mark` varchar(100) NOT NULL,
  `type` varchar(100) NOT NULL,
  `serialnum` varchar(100) NOT NULL,
  `desc` varchar(100) NOT NULL,
  `site` int(11) NOT NULL DEFAULT '0',
  `licnum` varchar(20) NOT NULL DEFAULT '',
  `inquirer` varchar(100) NOT NULL DEFAULT '',
  `inqdate` date NOT NULL DEFAULT '0000-00-00',
  `supplier` varchar(100) NOT NULL DEFAULT '',
  `suppldate` date NOT NULL DEFAULT '0000-00-00',
  `supplnumber` varchar(30) NOT NULL DEFAULT '',
  `invoicedate` date NOT NULL DEFAULT '0000-00-00',
  `invoicenumber` varchar(30) NOT NULL DEFAULT '',
  `purchaseprice` decimal(11,2) NOT NULL DEFAULT '0.00',
  `leased` varchar(1) NOT NULL DEFAULT 'N',
  `leasingcompany` varchar(100) NOT NULL DEFAULT '',
  `leasingnumber` varchar(30) NOT NULL DEFAULT '',
  `notes` text NOT NULL,
  `inspnotes` text NOT NULL,
  `inactive` varchar(1) NOT NULL DEFAULT 'N',
  `maintain` varchar(1) NOT NULL DEFAULT 'Y',
  `asset` varchar(1) NOT NULL DEFAULT 'Y',
  `inspection` varchar(1) NOT NULL DEFAULT '',
  `inspperson` varchar(100) NOT NULL DEFAULT '',
  `guarantor` varchar(32) NOT NULL DEFAULT '',
  `saveuser` varchar(32) NOT NULL,
  `savetime` int(11) NOT NULL,
  `recordid` varchar(32) NOT NULL,
  `_state` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `invnum` (`invnum`),
  KEY `_state` (`_state`),
  KEY `site` (`site`)
) ENGINE=InnoDB AUTO_INCREMENT=30707 DEFAULT CHARSET=utf8;


CREATE TABLE `mobuto_inv_sites` (
  `siteid` int(11) NOT NULL AUTO_INCREMENT,
  `description` varchar(100) NOT NULL,
  `saveuser` varchar(32) NOT NULL,
  `savetime` int(11) NOT NULL,
  `recordid` varchar(32) NOT NULL,
  `_state` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`siteid`),
  KEY `_state` (`_state`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;


mysql> SHOW INDEX FROM mobuto_inv_assets;
+-------------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table             | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| mobuto_inv_assets |          0 | PRIMARY  |            1 | id          | A         |       24857 |     NULL | NULL   |      | BTREE      |         |               |
| mobuto_inv_assets |          0 | invnum   |            1 | invnum      | A         |       24857 |     NULL | NULL   |      | BTREE      |         |               |
| mobuto_inv_assets |          1 | _state   |            1 | _state      | A         |           4 |     NULL | NULL   |      | BTREE      |         |               |
+-------------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

@Wilson Hauck 要求的更改:

mobuto_inv_assets 中的site 列添加了索引(将执行速度降低了近半秒) 似乎第一个查询中缺少列nextinsp。格式化查询时可能会丢失。当然应该和快的一样 删除了 saveuser 连接,因为它没有在那里使用(又节省了 2 秒)并更新了它的 EXPLAIN(删除了最后一行)

SHOW INDEX FROM mobuto_inv_sites 已添加

+------------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table            | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+------------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| mobuto_inv_sites |          0 | PRIMARY  |            1 | siteid      | A         |           3 |     NULL | NULL   |      | BTREE      |         |               |
| mobuto_inv_sites |          1 | _state   |            1 | _state      | A         |           3 |     NULL | NULL   |      | BTREE      |         |               |
+------------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

【问题讨论】:

我们能看到 SHOW CREATE TABLE 语句 - 主要针对 inv_assets 表吗? 查看EXPLAIN的key 【参考方案1】:

您的第一个查询比第二个查询更少使用键。解释计划中的 possible_keys 列显示了可以使用密钥的位置,但是,key 列显示了它们实际使用的位置。

我建议,如果没有看到您的数据库的结构,请在您的 JOIN 和 WHERE 子句中更多地使用这些键来加快速度。

当你说你正在修改选择列并且速度变化时,我会确保查询没有被缓存。

【讨论】:

是的,查询没有被缓存。附加信息被添加到问题中。需要时可以放置其他/更改的索引。【参考方案2】:

12 秒的第一次查询可能是由 ROWS 列线索引起的,总共考虑了 24857*3*915*1 = 68,232,465 行。少于 1 秒的第二个查询 ROWS 列线索仅考虑 24857*1*915*1 = 22,744,155 总行。第一个查询使用块嵌套循环处理是延迟响应的主要原因。 请发布 SHOW CREATE TABLE mobuto_inv_assets 和 mobuto_inv_sites 的结果。还请张贴 SHOW INDEX FROM mobuto_inv_assets 和 mobuto_inv_sites 的结果。有了这些附加信息,有人可能会建议对 SELECT .... 查询进行改进,以避免块嵌套循环处理,因为 RBAR(Row By Agonizing Row 处理)对 CPU 来说非常耗时。可能需要额外的索引。

【讨论】:

感谢您到目前为止的分析。我在问题中添加了请求的信息。【参考方案3】:

感谢您发布您的两个 SHOW CREATE TABLE,非常有帮助。 请考虑添加索引 ALTER TABLE mobuto_inv_sites 添加索引站点 -- 如果您的系统空间允许。 此外,查询 1 的 EXPLAIN 显示与查询不匹配。 该查询没有引用我在 EXPLAIN 中看到的 nextinsp 或 saveused。 当您有机会再次测试并指出所需的执行时间减少时,请在创建索引后替换查询 1 的 EXPLAIN。 如果你能发布结果也很好 SHOW INDEX FROM mobuto_inv_sites 以便我们查看您的数据范围和基数。

【讨论】:

感谢分析!问题已按要求更新。请看一下【参考方案4】:

如果 inv_assets 行填充了 ACCURATE _state 数据 考虑将 query1 更改为以下内容: 选择inv_assets.id AS id, site.description AS sitename, (SELECT COALESCE(DATE_FORMAT(CONVERT_TZ(MIN(inspdate),'UTC','Europe/Vienna'),'%Y-%m-%d'),'') 发件人mobuto_inv_inspections 发件人nextinsp 在哪里 ((nextinsp.objectlink = inv_assets.idnextinsp.inspdate >= NOW())) ) 作为nextinsp 发件人mobuto_inv_assets 发件人inv_assets 其中inv_assets._state = 2 或inv_assets._state = 0 左加入mobuto_inv_sites AS site 开(site.siteid = inv_assets.sitesite._state IN (2,0)) ORDER BY inv_assets.type ASC LIMIT 0, 20;

EXPLAIN 应该避免表扫描和随后的块嵌套循环处理。

如果 inv_assets 中的 _state 数据在每一行上都不是准确的,这将不起作用。

2017-08-10 更新 09:42 CT 请发布 QUERY、EXPLAIN 结果、SHOW CREATE TABLE tblname 为所涉及的表和 SHOW INDEX FROM tblname 为所涉及的表。

【讨论】:

我真的不知道 ACCURATE 是什么意思,但我已经尝试过在 _state 列上没有任何引用,但速度没有变化。注意:_state 可以(当前)保存从 0 到 2 的值。

以上是关于查询速度的巨大差异的主要内容,如果未能解决你的问题,请参考以下文章

没有“-std=c99”的巨大 fprintf 速度差异

oracle 10g 和 9i 之间巨大的查询执行时间差异

分析 API/查询资源管理器和界面之间的巨大差异

SQLITE 3.7.13 和 3.8.0 之间的性能差异

在 Access 中使用 ODBC 连接到 MS SQL Server 2012:手动调用查询和在 VBA 中调用查询之间的巨大时间差异

Python 3.5 与 2.7 之间字​​符串连接的巨大时间差异