【问题标题】:Optimizing a simple query with many left joins优化具有许多左连接的简单查询
【发布时间】:2017-10-10 13:23:59
【问题描述】:

我有一个相当大的查询,用于在“map_item”上进行用户搜索。

SELECT map_item_name
FROM map_item
LEFT JOIN map_section_item ON map_section_item_item_id = map_item_id
LEFT JOIN map_section ON map_section_id = map_section_item_section_id
LEFT JOIN map_item_flag ON map_item_flag_item_id = map_item_id
LEFT JOIN flag ON flag_id = map_item_flag_flag_id
LEFT JOIN map ON map_id = map_section_map_id
LEFT JOIN place_map ON place_map_map_id = map_id
LEFT JOIN place ON place_id = place_map_place_id
LEFT JOIN place_category ON place_category_place_id = place_id
LEFT JOIN category ON category_id = place_category_category_id
LEFT JOIN review ON review_map_item_id = map_item_id
LEFT JOIN map_price ON map_price_item_id = map_item_id
LEFT JOIN county_list ON place_address_county = county_id

'map_item' 共有 5399 条记录,所有连接的表都没有太多数据。

如果我在没有左连接 (SELECT map_item_name FROM map_item) 的情况下运行此查询,它会按预期在 0.00 秒内返回,但上述带有连接的查询大约需要 10.00 秒。

由于用户可以将不同的过滤器应用于搜索,因此查询中需要所有左连接,但是原始查询需要很长时间才能运行(20 秒左右),并且在删除了大部分我留下了上面的部分查询(这只是左连接),甚至这需要 18 秒才能运行。

这是查询中的解释语句:

+----+-------------+-------------------+--------+----------------------------------+----------------------------------+---------+-----------------------------------------------------------+------+-----------------------------------------------------------------+
| id | select_type | table             | type   | possible_keys                    | key                              | key_len | ref                                                       | rows | Extra                                                           |
+----+-------------+-------------------+--------+----------------------------------+----------------------------------+---------+-----------------------------------------------------------+------+-----------------------------------------------------------------+
|  1 | SIMPLE      | map_item          | ALL    | NULL                             | NULL                             | NULL    | NULL                                                      | 5455 | NULL                                                            |
|  1 | SIMPLE      | map_section_item  | index  | NULL                             | map_section_item_section_id      | 8       | NULL                                                      | 5330 | Using where; Using index; Using join buffer (Block Nested Loop) |
|  1 | SIMPLE      | map_section       | eq_ref | PRIMARY                          | PRIMARY                          | 4       | bestmeal.map_section_item.map_section_item_section_id     |    1 | NULL                                                            |
|  1 | SIMPLE      | map_item_flag     | ALL    | NULL                             | NULL                             | NULL    | NULL                                                      | 1509 | Using where; Using join buffer (Block Nested Loop)              |
|  1 | SIMPLE      | flag              | eq_ref | PRIMARY                          | PRIMARY                          | 4       | bestmeal.map_item_flag.map_item_flag_flag_id              |    1 | Using index                                                     |
|  1 | SIMPLE      | map               | eq_ref | PRIMARY                          | PRIMARY                          | 4       | bestmeal.map_section.map_section_map_id                   |    1 | Using index                                                     |
|  1 | SIMPLE      | place_map         | index  | NULL                             | branch_map_branch_id             | 8       | NULL                                                      | 1275 | Using where; Using index; Using join buffer (Block Nested Loop) |
|  1 | SIMPLE      | place             | eq_ref | PRIMARY                          | PRIMARY                          | 4       | bestmeal.place_map.place_map_place_id                     |    1 | NULL                                                            |
|  1 | SIMPLE      | place_category    | ref    | place_category_place_id          | place_category_place_id          | 4       | bestmeal.place.place_id                                   |    1 | Using index                                                     |
|  1 | SIMPLE      | category          | eq_ref | PRIMARY                          | PRIMARY                          | 4       | bestmeal.place_category.place_category_category_id        |    1 | Using index                                                     |
|  1 | SIMPLE      | review            | ref    | review_map_item_id               | review_map_item_id               | 4       | bestmeal.map_item.map_item_id                             |    1 | Using index                                                     |
|  1 | SIMPLE      | map_price         | ref    | map_price_item_id                | map_price_item_id                | 4       | bestmeal.map_item.map_item_id                             |    1 | Using index                                                     |
|  1 | SIMPLE      | county_list       | eq_ref | PRIMARY                          | PRIMARY                          | 4       | bestmeal.place.place_address_county                       |    1 | Using index                                                     |
+----+-------------+-------------------+--------+----------------------------------+----------------------------------+---------+-----------------------------------------------------------+------+-----------------------------------------------------------------+

所有这些连接都是针对索引字段进行的,并且连接的表中没有任何不必要的索引可以用来代替预期的索引。

在优化查询方面,我不是专家,但我正在努力找出在保持左连接的同时加快查询速度的方法。我也真的想不出任何替代解决方案可以在不使用连接的情况下返回相同的结果。

是否有人有任何想法可以帮助我提高此查询的性能或使用不同的更快方法完成用户搜索?

编辑 根据要求的表结构:

CREATE TABLE `map_item` (
  `map_item_id` int(11) NOT NULL AUTO_INCREMENT,
  `map_item_account_id` int(11) NOT NULL DEFAULT '0',
  `map_item_category_id` int(11) NOT NULL,
  `map_item_name` varchar(255) DEFAULT NULL,
  `map_item_description` text,
  `map_item_tags` varchar(255) DEFAULT NULL,
  `map_item_type` set('d','f') DEFAULT NULL,
  PRIMARY KEY (`map_item_id`),
  KEY `map_item_account_id` (`map_item_account_id`),
  KEY `map_item_tags` (`map_item_tags`),
  KEY `map_item_category_id` (`map_item_category_id`),
  FULLTEXT KEY `map_item_keyword_search` (`map_item_name`,`map_item_description`,`map_item_tags`),
  FULLTEXT KEY `map_item_name` (`map_item_name`),
  FULLTEXT KEY `map_item_description` (`map_item_description`),
  FULLTEXT KEY `map_item_tags_2` (`map_item_tags`)
) ENGINE=InnoDB AUTO_INCREMENT=5420 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT 

CREATE TABLE `map_section_item` (
  `map_section_item_id` int(11) NOT NULL AUTO_INCREMENT,
  `map_section_item_section_id` int(11) NOT NULL DEFAULT '0',
  `map_section_item_item_id` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`map_section_item_id`),
  KEY `map_section_item_section_id` (`map_section_item_section_id`,`map_section_item_item_id`)
) ENGINE=InnoDB AUTO_INCREMENT=24410 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT

CREATE TABLE `map_section` (
  `map_section_id` int(11) NOT NULL AUTO_INCREMENT,
  `map_section_map_id` int(11) NOT NULL DEFAULT '0',
  `map_section_map_draft_id` int(11) NOT NULL DEFAULT '0',
  `map_section_column` tinyint(1) NOT NULL DEFAULT '1',
  `map_section_name` varchar(255) DEFAULT NULL,
  `map_section_description` text,
  PRIMARY KEY (`map_section_id`),
  KEY `map_section_map_draft_id` (`map_section_map_draft_id`),
  KEY `map_section_map_id` (`map_section_map_id`),
  FULLTEXT KEY `index_name` (`map_section_name`)
) ENGINE=InnoDB AUTO_INCREMENT=4254 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT

CREATE TABLE `map_item_flag` (
  `map_item_flag_id` int(11) NOT NULL AUTO_INCREMENT,
  `map_item_flag_item_id` int(11) NOT NULL DEFAULT '0',
  `map_item_flag_flag_id` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`map_item_flag_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1547 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT

CREATE TABLE `flag` (
  `flag_id` int(11) NOT NULL AUTO_INCREMENT,
  `flag_category_id` int(11) NOT NULL DEFAULT '0',
  `flag_name` varchar(255) DEFAULT NULL,
  `flag_description` varchar(255) DEFAULT NULL,
  `flag_img` varchar(255) DEFAULT NULL,
  `flag_order` tinyint(2) NOT NULL DEFAULT '0',
  PRIMARY KEY (`flag_id`),
  KEY `flag_category_id` (`flag_category_id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT

CREATE TABLE `map` (
  `map_id` int(11) NOT NULL AUTO_INCREMENT,
  `map_account_id` int(11) NOT NULL DEFAULT '0',
  `map_name` varchar(255) DEFAULT NULL,
  `map_description` text,
  `map_type` set('d','f') DEFAULT NULL,
  `map_layout` set('columns','tabs','collapsed') DEFAULT NULL,
  PRIMARY KEY (`map_id`),
  KEY `map_account_id` (`map_account_id`)
) ENGINE=InnoDB AUTO_INCREMENT=138 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT

CREATE TABLE `place_map` (
  `place_map_id` int(11) NOT NULL AUTO_INCREMENT,
  `place_map_place_id` int(11) NOT NULL DEFAULT '0',
  `place_map_map_id` int(11) NOT NULL DEFAULT '0',
  `place_map_active` tinyint(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`place_map_id`),
  KEY `branch_map_branch_id` (`place_map_place_id`,`place_map_map_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2176 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT

CREATE TABLE `place` (
  `place_id` int(11) NOT NULL AUTO_INCREMENT,
  `place_account_id` int(11) NOT NULL DEFAULT '0',
  `place_name` varchar(120) DEFAULT NULL,
  `place_alias` varchar(255) DEFAULT NULL,
  `place_description` text,
  `place_address_line_one` varchar(100) DEFAULT NULL,
  `place_address_line_two` varchar(100) DEFAULT NULL,
  `place_address_line_three` varchar(100) DEFAULT NULL,
  `place_address_town` varchar(100) DEFAULT NULL,
  `place_address_county` int(11) NOT NULL DEFAULT '0',
  `place_address_postcode` varchar(10) DEFAULT NULL,
  `place_address_latitude` decimal(11,8) DEFAULT NULL,
  `place_address_longitude` decimal(11,8) DEFAULT NULL,
  `place_phone` varchar(20) DEFAULT NULL,
  `place_email` varchar(255) DEFAULT NULL,
  `place_website` varchar(120) DEFAULT NULL,
  `place_flag_initial_email` tinyint(1) NOT NULL DEFAULT '0',
  `place_audit_admin_id` int(11) NOT NULL DEFAULT '0',
  `place_last_audit_datetime` datetime DEFAULT NULL,
  `place_created_by_admin_id` int(11) NOT NULL DEFAULT '0',
  `place_created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `place_tried_google` int(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`place_id`),
  KEY `place_account_id` (`place_account_id`),
  KEY `place_address_county` (`place_address_county`),
  KEY `place_alias` (`place_alias`),
  KEY `place_audit_admin_id` (`place_audit_admin_id`),
  KEY `place_created_by_admin_id` (`place_created_by_admin_id`),
  FULLTEXT KEY `place_name` (`place_name`),
  FULLTEXT KEY `place_keyword_search` (`place_name`,`place_address_town`),
  FULLTEXT KEY `place_address_town` (`place_address_town`)
) ENGINE=InnoDB AUTO_INCREMENT=135167 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT

CREATE TABLE `place_category` (
  `place_category_id` int(11) NOT NULL AUTO_INCREMENT,
  `place_category_place_id` int(11) NOT NULL DEFAULT '0',
  `place_category_category_id` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`place_category_id`),
  UNIQUE KEY `place_category_place_id` (`place_category_place_id`,`place_category_category_id`)
) ENGINE=InnoDB AUTO_INCREMENT=208987 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT

CREATE TABLE `category` (
  `category_id` int(11) NOT NULL AUTO_INCREMENT,
  `category_name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`category_id`)
) ENGINE=InnoDB AUTO_INCREMENT=168 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT

CREATE TABLE `review` (
  `review_id` int(11) NOT NULL AUTO_INCREMENT,
  `review_user_id` int(11) NOT NULL DEFAULT '0',
  `review_place_id` int(11) NOT NULL DEFAULT '0',
  `review_map_item_id` int(11) NOT NULL DEFAULT '0',
  `review_otm_item_name` varchar(156) DEFAULT NULL,
  `review_headline` varchar(255) DEFAULT NULL,
  `review_message` text,
  `review_rating` tinyint(1) NOT NULL DEFAULT '0',
  `review_datetime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `review_edited_datetime` datetime DEFAULT NULL,
  `review_hidden` tinyint(1) NOT NULL DEFAULT '0',
  `review_deleted` tinyint(1) NOT NULL DEFAULT '0',
  `review_status` set('pending','published','hidden','deleted') NOT NULL,
  PRIMARY KEY (`review_id`),
  KEY `review_map_item_id` (`review_map_item_id`),
  KEY `review_place_id` (`review_place_id`),
  KEY `review_user_id` (`review_user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT

CREATE TABLE `map_price` (
  `map_price_id` int(11) NOT NULL AUTO_INCREMENT,
  `map_price_item_id` int(11) NOT NULL DEFAULT '0',
  `map_price_label` varchar(50) DEFAULT NULL,
  `map_price_value` decimal(10,2) DEFAULT NULL,
  PRIMARY KEY (`map_price_id`),
  KEY `map_price_item_id` (`map_price_item_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5872 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT

CREATE TABLE `county_list` (
  `county_id` int(11) NOT NULL AUTO_INCREMENT,
  `county_country_id` int(11) NOT NULL DEFAULT '0',
  `county_name` varchar(120) DEFAULT NULL,
  `county_alias` varchar(120) DEFAULT NULL,
  PRIMARY KEY (`county_id`),
  KEY `county_alias` (`county_alias`),
  KEY `county_country_id` (`county_country_id`)
) ENGINE=InnoDB AUTO_INCREMENT=142 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT

【问题讨论】:

  • 你有索引吗?
  • 确保使用 INT 作为 id 字段数据类型,并在这些外键上创建索引。
  • 对不起,我应该在原帖中提到。所有连接都在索引字段上连接。此查询中使用的所有表都没有不必要的索引。
  • 这听起来可能很有趣,但请尝试为服务器使用更好的机器/优化。我看不出你还能做些什么来增强查询。很想知道您是否设法获得任何表演时间。
  • 我们能看到所有相关表的 SHOW CREATE TABLE 语句吗? (顺便说一句,没有点外部连接表可以从中选择任何列,但我认为这不是您的查询)。哦,并且始终限定您的列 - 我也会建议不同的命名策略。

标签: mysql optimization query-optimization


【解决方案1】:

看看这些行:

LEFT JOIN map_section_item ON map_section_item_item_id = map_item_id

| 1 |简单 | map_section_item |索引 |空 | map_section_item_section_id | 8 |空 | 5330 |使用哪里;使用索引;使用连接缓冲区(块嵌套循环)| |

通知“5330”。这意味着它必须搜索大约 5330 个项目才能找到所需的行。

使用简单的INDEX(map_section_item_item_id),它将直接转到所需的一(或几)行。这将使查询运行得更快。

互相重复JOIN,至少对于那些“行”> 1 的人。

为什么是LEFT?每个“正确”的表是否可以选择性地丢失数据?

附带问题:不要在所有内容前加上表名;太杂乱了。

【讨论】:

  • 我不知道为什么我没有意识到我错过了这些索引,但这运作良好。我添加了第一个索引map_section_item_item_id,它将查询从 9.85 秒增加到 5.71 秒。第二个索引map_item_flag_item_id 达到了 3.52 秒。第三个指数place_map_map_id 将其降低到 1.40 秒,这是一个很大的进步。我仍然会考虑进一步减少这个问题,并且必须在数据库增长之前解决这个问题,因为它目前仍处于早期阶段。你所说的连接是正确的,表中不一定有数据。
  • 另外,我刚刚运行了完整的原始查询,它从大约 18 秒下降到了 0.10 秒。
【解决方案2】:

对于 MySQL,请尝试使用 STRAIGHT_JOIN 子句...

SELECT STRAIGHT_JOIN map_item_name
FROM map_item
LEFT JOIN ...

STRAIGHT_JOIN 告诉 MySQL 按照我列出的顺序进行查询。这样,它会强制将 map_item 作为主表,将所有其余的作为查找辅助表...

【讨论】:

  • 使用 STRAIGHT_JOIN 在 9.95 秒内运行查询,而不在 9.87 秒内运行
  • FORCE INDEX 一样,STRAIGHT_JOIN 今天可能会有所帮助,但明天会让事情变得更糟。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-04-04
  • 2012-06-05
  • 1970-01-01
  • 2011-12-05
  • 2019-03-08
  • 2011-04-29
  • 1970-01-01
相关资源
最近更新 更多