MySQL LEFT JOIN 或 WHERE IN SUBQUERY
Posted
技术标签:
【中文标题】MySQL LEFT JOIN 或 WHERE IN SUBQUERY【英文标题】:MySQL LEFT JOIN or WHERE IN SUBQUERY 【发布时间】:2016-05-27 19:05:11 【问题描述】:我需要一条建议,现在正在构建一个应用程序,我需要在相当大的表上运行一些查询,可能以非常频繁的速度运行,所以我正在努力获得最佳的方法性能。
我有以下 2 个表:
专辑:
+---------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| eventid | int(11) | NO | MUL | NULL | |
| album | varchar(200) | NO | | NULL | |
| filename | varchar(200) | NO | | NULL | |
| obstacle_time | time | NO | | NULL | |
+---------------+--------------+------+-----+---------+----------------+
和关键字:
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| eventid | int(11) | NO | MUL | NULL | |
| filename | varchar(200) | NO | | NULL | |
| bibnumbers | varchar(200) | NO | | NULL | |
| gender | varchar(20) | YES | | NULL | |
| top_style | varchar(20) | YES | | NULL | |
| pants_style | varchar(20) | YES | | NULL | |
| other | varchar(20) | YES | | NULL | |
| cap | varchar(200) | NO | | NULL | |
| tshirt | varchar(200) | NO | | NULL | |
| pants | varchar(200) | NO | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
两个表都声明了一个 unique_index,它是 eventid+filename
列的约束。
两个表都包含有关某些图像的信息,但相册表可立即使用(只要我有图像),而关键字表通常在手动标记图像后几天后可用
现在,一旦启用标记,我将让人们搜索所有类型的东西,但由于结果可能很大(高达 10.000 或更多),所以我只以小块显示它们,因此浏览器无法获取试图加载大量图像而被杀死,因此我的服务器将受到大量查询请求的影响(每次访问者滚动到页面底部时,ajax 查询将返回下一个图像块)。
现在我的问题是,以下哪个查询的性能更好:
SELECT `albums`.`filename`,`basket`.`id`,`albums`.`id`,`obstacle_time`
FROM `albums`
LEFT JOIN `basket`
ON `basket`.`eventid` = `albums`.`eventid`
AND `basket`.`fileid` = `albums`.`id`
AND `basket`.`visitor_id` = 1
LEFT JOIN `keywords`
ON `keywords`.`eventid` = `albums`.`eventid`
AND `albums`.`filename` = `keywords`.`filename`
WHERE
`albums_2015`.`eventid` = 1
AND `album` LIKE '%string%'
AND `obstacle_time` >= '08:00:00'
AND `obstacle_time` <= '14:11:10'
AND `gender` = 1
AND `top_style` REGEXP '[[:<:]]0[[:>:]]|[[:<:]]1[[:>:]]'
AND `cap` = '2'
AND `tshirt` = '1'
AND `pants` = '3'
ORDER BY `obstacle_time`
LIMIT X, 10
或在WHERE
中使用IN
CLAUSE,例如:
SELECT `albums`.`filename`,`basket`.`id`,`albums`.`id`,`obstacle_time`
FROM `albums`
LEFT JOIN `basket`
ON `basket`.`eventid` = `albums`.`eventid`
AND `basket`.`fileid` = `albums`.`id`
AND `basket`.`visitor_id` = 1
WHERE
`albums_2015`.`eventid` = 1
AND `album` LIKE '%string%'
AND `obstacle_time` >= '08:00:00'
AND `obstacle_time` <= '14:11:10'
AND `filename` IN (
SELECT `filename`
FROM `keywrods`
WHERE
`eventid` = 1
AND `gender` = 1
AND `top_style` REGEXP '[[:<:]]0[[:>:]]|[[:<:]]1[[:>:]]'
AND `cap` = '2'
AND `tshirt` = '1'
AND `pants` = '3'
)
ORDER BY `obstacle_time`
LIMIT X, 10
我曾研究过类似的问题,但无法确定最佳的行动方案。
到目前为止,我的理解是:
使用LEFT JOIN
利用索引,但是!!!如果我使用它,即使我只需要一个小得多的结果集,我也会得到表的完全连接,因此连接数千行然后过滤掉大部分几乎是一种浪费。
使用 IN 和子查询没有索引???我对此不是 100% 确定的,我使用的是 mysql 5.6,据我所知,因为 5.6 甚至子查询都会自动为我的 MySQL 编制索引。我认为这种方法在结果被显着过滤时有好处,不确定如果子查询将返回所有可能的文件名是否会有任何好处。
作为脚注问题:
我是否应该考虑在第一次查询时将整个结果返回给客户端,并使用客户端 (html) 技术逐步加载图像,而不是每次都重新查询服务器?
我是否应该考虑将 2 个表合并为 1 个表,这会对性能产生多大影响? (由于各种原因可能会很棘手,这在问题中没有地位)
谢谢。
编辑 1
解释 JOIN 查询:
+----+-------------+---------------+--------+---------------+--------------+---------+----------------------------------------+------+----------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------------+--------+---------------+--------------+---------+----------------------------------------+------+----------------------------------------------------+
| 1 | SIMPLE | albums_2015 | ref | unique_index | unique_index | 4 | const | 6475 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | basket | ALL | NULL | NULL | NULL | NULL | 2 | Using where; Using join buffer (Block Nested Loop) |
| 1 | SIMPLE | keywords_2015 | eq_ref | unique_index | unique_index | 206 | const,mybibnumber.albums_2015.filename | 1 | Using index |
+----+-------------+---------------+--------+---------------+--------------+---------+----------------------------------------+------+----------------------------------------------------+
使用在哪里:
+----+-------------+---------------+--------+---------------+--------------+---------+----------------------------------------+------+----------------------------------------------------+--+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | |
+----+-------------+---------------+--------+---------------+--------------+---------+----------------------------------------+------+----------------------------------------------------+--+
| 1 | SIMPLE | albums_2015 | ref | unique_index | unique_index | 4 | const | 6475 | Using where; Using temporary; Using filesort | |
| 1 | SIMPLE | keywords_2015 | eq_ref | unique_index | unique_index | 206 | const,mybibnumber.albums_2015.filename | 1 | Using where | |
| 1 | SIMPLE | basket | ALL | NULL | NULL | NULL | NULL | 2 | Using where; Using join buffer (Block Nested Loop) | |
+----+-------------+---------------+--------+---------------+--------------+---------+----------------------------------------+------+----------------------------------------------------+--+
编辑 2
我无法设置 SQL Fiddler(不断出错),因此我在其中一台服务器上创建了一个测试数据库。
地址:http://188.165.217.185/phpmyadmin/,用户:temp_test,通过:test_temp
我仍在构建整个东西,我还没有填写所有值,例如 top_style、pants_style 等,因此更适合测试场景的查询是:
在哪里:
SELECT `albums_2015`.`filename`,
`albums_2015`.`id`,
`obstacle_time`
FROM `albums_2015`
WHERE `albums_2015`.`eventid` = 1
AND `album` LIKE '%'
AND `obstacle_time` >= '08:00:00'
AND `obstacle_time` <= '14:11:10'
AND `filename` IN (SELECT `filename`
FROM `keywords_2015`
WHERE eventid = 1
AND
`bibnumbers` REGEXP '[[:<:]]113[[:>:]]|[[:<:]]106[[:>:]]')
ORDER BY `obstacle_time`
LIMIT 0, 10
左连接
SELECT `albums_2015`.`filename`,`albums_2015`.`id`,`obstacle_time`
FROM `albums_2015`
LEFT JOIN `keywords_2015`
ON `keywords_2015`.`eventid` = `albums_2015`.`eventid`
AND `albums_2015`.`filename` = `keywords_2015`.`filename`
WHERE
`albums_2015`.`eventid` = 1
AND `album` LIKE '%'
AND `obstacle_time` >= '08:00:00'
AND `obstacle_time` <= '14:11:10'
AND `bibnumbers` REGEXP '[[:<:]]113[[:>:]]|[[:<:]]106[[:>:]]'
ORDER BY `obstacle_time`
LIMIT 0, 10
【问题讨论】:
使用 EXPLAIN 前缀运行这些查询的结果是什么? 添加了说明,你可以忽略专辑和专辑的名称差异_2015 和我只是为了这个问题更漂亮的一样。 【参考方案1】:更多提示:
如果您必须处理多表查询,最好使用索引连接,不要介意添加一些索引来加快查询速度(索引占用空间,但在 INT
字段上它什么都不是,你得到的比失去的多)。
在大表的情况下,将数据缓存在远程表中通常是个好主意。
TAG_table
上的插入触发器缓存远程表中显示的部分(如专辑概览的标签名称)可以帮助您将连接查询保持在下降频率。
小心
REGEX
,它会严重损害性能。添加新表来拆分数据是一个更好的主意(并使用本机优化的索引)
对于大而频繁的查询的
WHERE
子句中的每个字段,您都应该有一个索引。如果你不能放一个,那么你的数据库模型已经搞砸了,需要改变。
【讨论】:
谢谢,很快:1) 我已经有一个索引 - ADD UNIQUEunique_index
( eventid
, filename
) - 在专辑和关键字表上。 2)我会考虑缓存。 3)这是混乱的位,因为列号是可变的(例如,第一行在 bibnumbers 字段中有 3 个不同的值,下一行 0,下一行 8 等)因此坚持使用 REGEXP 方法。 4) 谢谢
@EmilBorconi 你能添加一些表格的数据吗?大概 5 到 10 行(我好像误解了你的用法)
好的,我已经创建了一个测试环境
@EmilBorconi 好的,我真的不喜欢你的数据库方案......bibnumbers
上的正则表达式绝对不是一个好主意,因为你甚至没有索引...... . 你应该看看如何在表***.com/questions/377375/…之间创建一个数据库/拆分你的数据
@EmilBorconi 一个数据库最多可以占用 1M 行而没有任何问题(我以 7M 为例)。所以对我来说,你不应该害怕吐出你的数据来获得一个“逻辑”存储,使用你的id
字段来加入。这是一个关系数据库而不是 CSV 文件。您应该避免重复数据(缓存部分除外)并利用分组 SQL 功能利用联接和索引。您应该为您的数据关系建立一个模型,以获得存储它们的好方法(这可能取决于您必须针对它运行的查询)以上是关于MySQL LEFT JOIN 或 WHERE IN SUBQUERY的主要内容,如果未能解决你的问题,请参考以下文章
mysql查询,left join(求并集),where(求交集)
在 WHERE 子句中使用连接列时,Mysql 未在 LEFT JOIN 中使用索引