查询速度的巨大差异
Posted
技术标签:
【中文标题】查询速度的巨大差异【英文标题】:Abstruse difference in query speed 【发布时间】:2017-08-03 18:38:40 【问题描述】:我不明白这两个EXPLAIN
s 的区别(第 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
.id
和nextinsp
.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
.site
和site
._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 的值。以上是关于查询速度的巨大差异的主要内容,如果未能解决你的问题,请参考以下文章
在 Access 中使用 ODBC 连接到 MS SQL Server 2012:手动调用查询和在 VBA 中调用查询之间的巨大时间差异