我有2个表(页面和注释),每个表约 13万行。
我想列出没有任何评论的页面(外键是comments.page_id)
如果我执行普通的左外部联接,则需要花费惊人的 750秒以上。 (130k ^ 2 = 17B)。而如果我执行相同的联接,但对表使用子查询,则只需 1秒。
服务器版本:5.6.44-log-MySQL社区服务器(GPL):
SELECT p.id
FROM `pages` AS p
LEFT JOIN `comments` AS c
ON p.id = c.page_id
WHERE c.page_id IS NULL
GROUP BY 1
SELECT p.id
FROM (
SELECT id FROM `pages`
) AS p
LEFT JOIN `comments` AS c
ON p.id = c.page_id
WHERE c.page_id IS NULL
GROUP BY 1
SELECT p.id
FROM `pages` AS p
LEFT JOIN (
SELECT * FROM `comments`
) AS c
ON p.id = c.page_id
WHERE c.page_id IS NULL
GROUP BY 1
SELECT p.id
FROM (
SELECT id FROM `pages`
) AS p
LEFT JOIN (
SELECT * FROM `comments`
) AS c
ON p.id = c.page_id
WHERE c.page_id IS NULL
GROUP BY 1
SELECT p.id
FROM (
SELECT id FROM `pages`
) AS p
LEFT JOIN (
SELECT page_id FROM `comments`
) AS c
ON p.id = c.page_id
WHERE c.page_id IS NULL
GROUP BY 1
SELECT p.id
FROM `pages` AS p
WHERE NOT EXISTS( SELECT page_id FROM `comments`
WHERE page_id = p.id );;
现在,在MySql 5.7版中,上述所有查询中的全部需要“时间太长” 来执行。
在MySql 5.7中,查询1和4具有相同的解释:
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 SIMPLE p NULL index PRIMARY PRIMARY 4 NULL 147626 100.00 Using index; Using temporary; Using filesort
1 SIMPLE c NULL ALL NULL NULL NULL NULL 147790 10.00 Using where; Not exists; Using join buffer (Block Nested Loop)
不幸的是,在MySql 5.6中,我现在无法获得查询1的解释(花费太多时间),但是下面是查询4的解释:
id select_type table type possible_keys key key_len ref rows Extra
---------------------------------------------------------------------------------------------------------------------------
1 PRIMARY <derived2> ALL NULL NULL NULL NULL 147626 Using temporary; Using filesort
1 PRIMARY <derived3> ref <auto_key0> <auto_key0> 4 p.id 10 Using where; Not exists
3 DERIVED comments ALL NULL NULL NULL NULL 147790 NULL
2 DERIVED pages index NULL PRIMARY 4 NULL 147626 Using index
表格:
CREATE TABLE `pages` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`identifier` varchar(250) NOT NULL DEFAULT '',
`reference` varchar(250) NOT NULL DEFAULT '',
`url` varchar(1000) NOT NULL DEFAULT '',
`moderate` varchar(250) NOT NULL DEFAULT 'default',
`is_form_enabled` tinyint(1) unsigned NOT NULL DEFAULT '1',
`date_modified` datetime NOT NULL,
`date_added` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=147627 DEFAULT CHARSET=utf8
CREATE TABLE `comments` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL DEFAULT '0',
`page_id` int(10) unsigned NOT NULL DEFAULT '0',
`website` varchar(250) NOT NULL DEFAULT '',
`town` varchar(250) NOT NULL DEFAULT '',
`state_id` int(10) NOT NULL DEFAULT '0',
`country_id` int(10) NOT NULL DEFAULT '0',
`rating` tinyint(1) unsigned NOT NULL DEFAULT '0',
`reply_to` int(10) unsigned NOT NULL DEFAULT '0',
`comment` text NOT NULL,
`reply` text NOT NULL,
`ip_address` varchar(250) NOT NULL DEFAULT '',
`is_approved` tinyint(1) unsigned NOT NULL DEFAULT '1',
`notes` text NOT NULL,
`is_admin` tinyint(1) unsigned NOT NULL DEFAULT '0',
`is_sent` tinyint(1) unsigned NOT NULL DEFAULT '0',
`sent_to` int(10) unsigned NOT NULL DEFAULT '0',
`likes` int(10) unsigned NOT NULL DEFAULT '0',
`dislikes` int(10) unsigned NOT NULL DEFAULT '0',
`reports` int(10) unsigned NOT NULL DEFAULT '0',
`is_sticky` tinyint(1) unsigned NOT NULL DEFAULT '0',
`is_locked` tinyint(1) unsigned NOT NULL DEFAULT '0',
`is_verified` tinyint(1) unsigned NOT NULL DEFAULT '0',
`date_modified` datetime NOT NULL,
`date_added` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=147879 DEFAULT CHARSET=utf8
为什么会这样? MySql在后台做什么?
这是否仅在MySql或任何其他Sql中发生?
如何编写快速查询以获取所需的信息? (在第5.6、5.7版中)
答案 0 :(得分:3)
长期运行的查询的问题在于,注释表的page_id列上缺少索引。因此,对于页面表中的每一行,您需要检查注释表的所有行。由于您使用的是LEFT JOIN,因此这是唯一可能的连接顺序。 5.6中发生的事情是,当您在FROM子句(又名派生表)中使用子查询时,MySQL将在用于派生表结果的临时表(EXPLAIN输出中的auto_key0)上创建索引。仅选择一列时速度更快的原因是临时表将更小。
在MySQL 5.7中,如果可能,此类派生表将自动合并到主查询中。这样做是为了避免多余的临时表。但是,这意味着您不再具有用于联接的索引。 (有关详细信息,请参见this blog post。)
您可以通过以下两种方法来缩短5.7中的查询时间:
在MySQL 8.0中,您还可以使用优化程序提示来避免合并。就您而言,就像
SELECT /*+ NO_MERGE(c) */ ... FROM
有关如何使用此类提示的示例,请参见this presentation的幻灯片34-37。
答案 1 :(得分:2)
查询1具有“爆炸内爆”综合症。首先,它执行JOIN
;这使行数激增。然后它执行GROUP BY
缩小。
也
每页的评论数等将对您的查询产生影响。
SELECT *
仅在需要知道LEFT JOIN
是否成功时才获取所有列。 (您观察到了。)此外,由于要查找 missing 行,因此不保留任何列。
查询2的速度应不如您所发现的那么快-它需要构建两个临时表(“派生”表),索引其中一个临时表,然后执行外部查询。 (可能是足够新的MySQL版本可以缩短部分工作量;旧版本的工作效率低下而臭名昭著。)
查询3:
尝试
SELECT p.id
FROM `pages` AS p
WHERE NOT EXISTS( SELECT 1 FROM `comments`
WHERE page_id = p.id );
也:
comments
需要INDEX(page_id)