带有多个选择语句的 MySQL 右外连接非常慢

Posted

技术标签:

【中文标题】带有多个选择语句的 MySQL 右外连接非常慢【英文标题】:MySQL right outer join with multiple select statements is very slow 【发布时间】:2019-12-28 07:41:46 【问题描述】:

我有 3 个表格,如下所示:

解析表(table1)

+----+---------+-----+------------+--------+
| id | created | num | resolution | score  |
+----+---------+-----+------------+--------+

作者表(table2)

+----+---------+--------+------+------+--------+
| id | created | author | file | tags | res_id |
+----+---------+--------+------+------+--------+

状态表(table3)

+----+------+-------------+----------+--------+-----------+
| id | name | description | category | status | author_id |
+----+------+-------------+----------+--------+-----------+

我正在使用 2 个 select 语句根据它们的 ID 从同一个表中获取记录:

statement1(分辨率数 = 1357)

select * 
from table1, table2, table3 
where table1.num=1357 
  and table1.id=table2.res_id
  and table2.id=table3.author_id;

statement2(分辨率数 = 1358)

select * 
from table1, table2, table3 
where table1.num=1358 
  and table1.id=table2.res_id 
  and table2.id=table3.author_id;

注意:每个语句返回 100,000+ 条记录

我使用右外连接来获取 statement2 中不存在于 statement1 结果中的记录,如下所示:

select * from (
  select * 
  from table1, table2, table3 
  where table1.num=1357 
    and table1.id=table2.res_id 
    and table2.id=table3.author_id
) as tab1
right outer join (
  select * 
  from table1, table2, table3 
  where table1.num=1358 
    and table1.id=table2.res_id 
    and table2.id=table3.author_id
) as tab2 on tab1.name=tab2.name
         and tab1.category=tab2.category
         and tab1.author=tab2.author
where tab1.name is NULL
  and tab1.category is NULL
  and tab1.author is NULL

这适用于少量记录,但在我的情况下,总共有 200,000 条记录,通常需要 11 分钟才能返回所需的结果。

如何优化查询以更快地获取结果?

PS:

对table1.num、table2.res_id、table3.author_id应用索引后

显示创建表table1;

CREATE TABLE `table1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `created` datetime NOT NULL,
  `num` int(11) NOT NULL,
  `resolution` varchar(100) NOT NULL,
  `score` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `num` (`num`)
  KEY `num_index` (`num`)
) ENGINE=InnoDB AUTO_INCREMENT=2552 DEFAULT CHARSET=latin1

显示创建表table2;

CREATE TABLE `table2` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `created` datetime NOT NULL,
  `author` varchar(200) NOT NULL,
  `file` varchar(200) NOT NULL,
  `tags` varchar(200) DEFAULT NULL,
  `res_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `table2_res_id_48009073_fk_table1_id` (`res_id`),
  KEY `res_id_index` (`res_id`),
  CONSTRAINT `table2_res_id_48009073_fk_table1_id ` FOREIGN KEY (`res_id`) REFERENCES `table1` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=686972 DEFAULT CHARSET=latin1

显示创建表table3;

CREATE TABLE `table3` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(200) NOT NULL,
  `description` longtext NOT NULL,
  `category` varchar(200) NOT NULL,
  `status` varchar(20) NOT NULL,
  `author_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `table3_ffe73c23` (`author_id`),
  KEY `author_id_index` (`author_id`)
  CONSTRAINT `table3_id_e47d088c_fk_table2_id` FOREIGN KEY (`author_id`) REFERENCES `table2` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12042452 DEFAULT CHARSET=latin1

解释

+------+----------------+-----------+---------+--------------------------------------------------------------------------------------+-------------------------------------------------------------+--------------+--------------+-------+------------------------------------+
| id     | select_type | table      | type        | possible_keys                                                                                | key                                                                    | key_len     | ref             | rows | Extra                                  |
+------+----------------+-----------+---------+--------------------------------------------------------------------------------------+-------------------------------------------------------------+--------------+--------------+-------+------------------------------------+
|    1   | SIMPLE      | table1   | const   | PRIMARY,num,num_index                                                             | num                                                                  | 4               | const        | 1       | Using index                         |
|    1   | SIMPLE      | table2   | ref        | PRIMARY,table2_res_id_48009073_fk_table1_id,res_id_index.  | table2_res_id_48009073_fk_table1_id.           | 4               | const         | 7106 |                                            |
|    1.  | SIMPLE      | table3   | ref       | table3_ffe73c23,author_id_index                                                  | table3_ffe73c23                                                | 4               | t2.id           | 9       |                                            |
|    1   | SIMPLE      | table1   | const    | PRIMARY,num,num_index                                                           | num                                                                   | 4               | const         | 1       | Using where; Using index  |
|    1   | SIMPLE      | table2   | ref        | PRIMARY, table2_res_id_48009073_fk_table1_id,res_id_index  | table2_res_id_48009073_fk_table1_id.           | 4               | t1.id.          | 213   | Using where                       |
|    1   | SIMPLE      | table3   | ref        | table3_ffe73c23, author_id_index                                                 | table3_ffe73c23                                                | 4               | t2.id           | 9       | Using where; Not exists     |
+------+----------------+----------+----------+-------------------------------------------------------------------------------------+-------------------------------------------------------------+---------------+---------------+-------+-----------------------------------+

【问题讨论】:

尝试将子查询的结果放在临时表中,以便为它们添加索引。 请提供三个表的SHOW CREATE TABLE tablename语句。所提供的信息将为回答您的问题提供很大帮助。 @VinayPotluri 谢谢。我现在正在调查你的问题。请注意,RickJames 建议的索引已经存在,因此您无需再次添加它们。我会放弃这些,这样它们就不会减慢您插入这些表的速度。 什么版本的 mysql @VinayPotluri - 请提供EXPLAIN SELECT ... 【参考方案1】:
where table1.num=1357 
  and table1.id=table2.res_id
  and table2.id=table3.author_id;

请求这些索引:

table1:  INDEX(num)
table2:  INDEX(res_id)
table3:  INDEX(author_id)

进行这些更改,提供SHOW CREATE TABLE,然后我们可以更好地查看问题的其余部分。

description 通常有多大?如果它通常大于几 KB,那么搬运它(table3.*tab3.*)的成本非常高。如果您不需要结果中的列,则拼写* 以便排除它。

如果您确实在结果中需要description,仍然执行上述操作,但随后将另一个JOIN 添加回table3 以获取它以及任何其他不需要的列连接。

【讨论】:

请注意,tab1tab2 是派生表,因此您不能为它们创建索引。 (存在派生表这一事实可能是性能问题的主要原因,但如果它们提供 SHOW CREATE TABLE 语句,我将在我自己的答案中对此进行讨论。) @WillemRenzema - 哎呀。我删除了它们。 @VinayPotluri - 看起来你已经有了这些索引。好吧,回到绘图板。【参考方案2】:

尝试以下重写的查询:

SELECT
table1.*,
table2.*,
table3.*
FROM table1
INNER JOIN table2
ON table2.res_id = table1.id
INNER JOIN table3
ON table3.author_id = table2.id
LEFT OUTER JOIN (
  SELECT
  table3.name,
  table3.category,
  table2.author
  FROM table1
  INNER JOIN table2
  ON table2.res_id = table1.id
  INNER JOIN table3
  ON table3.author_id = table2.id
  WHERE table1.num = 1357
) tab1
ON tab1.name = table3.name
AND tab1.category = table3.category
AND tab1.author = table2.author
WHERE table1.num = 1358
AND tab1.name IS NULL

单独的查询可能会显示出一些改进。如果没有太大改善,请尝试以下新索引:

table2: INDEX(res_id, author)
table3: INDEX(author_id, category, name)

然后报告结果。

注意:尝试查询时,请确保运行两次并丢弃第一个结果,以便填充 InnoDB 缓冲区缓存。

【讨论】:

我尝试了几次运行您的解决方案,但性能似乎没有提高。没有索引 - 集合中的 1631 行(53 分钟 11.668 秒) - 有索引 - 集合中的 1631 行(16 分钟 50.934 秒)。在我的情况下,连接并没有比 where 子句快

以上是关于带有多个选择语句的 MySQL 右外连接非常慢的主要内容,如果未能解决你的问题,请参考以下文章

mysql开发之join语句学习

MySql的回顾五:多表查询下(内联/左外/右外/自连接/交叉)-1999语法

两个表之间的右外连接

数据库的内连接外连接(左外连接右外连接全外连接)以及交叉连接(转)

左外连接和右外连接的区别

左外连接与右外连接区别?