选择与左连接中的子查询

Ale*_*xis 4 mysql performance optimization mysql-5.7 query-performance

通常我必须在获取其他非相关行的查询中返回某些行的计数。

例如一个表用户一个表评论和一个表图片

User:
id
nickname

Review:
id
to_user_id
from_user_id
rating

Picture:
id:
user_id
url
Run Code Online (Sandbox Code Playgroud)

假设我想在一个查询中检索“给定”userId 的所有图片 url 的昵称以及评论该用户的人数。

我认为在执行此查询时的第一种也是简单的方法是:

SELECT
  u.nickname
  (SELECT count(*) FROM review WHERE to_user_id = u.id) as reviewCount,
  p.url
FROM user
LEFT JOIN picture ON p.user_id = u.id
WHERE 
  u.id = 1
Run Code Online (Sandbox Code Playgroud)

这样做的另一种方法是没有那个子选择并通过在正确的 user_id 上加入评论表

SELECT 
 u.nickname,
 r.reviewCount,
 p.url
FROM user u 
LEFT JOIN (
    SELECT to_user_id, count(*) reviewCount FROM review GROUP BY to_user_id
 ) r ON r.to_user_id = u.id
LEFT JOIN picture ON p.user_id = u.id 
WHERE u.id = 1;
Run Code Online (Sandbox Code Playgroud)

我不是数据库查询性能和调优方面的专家。如果一个解决方案比另一个更好,有人可以向我解释吗?(或者如果有其他更好的解决方案)?

编辑: 抱歉忘了提及。我正在使用最新的 MySQL

jka*_*lik 6

您没有指定您正在使用哪个 RDBMS。我在这里写的大部分内容应该是相当独立的,但我主要有 MySQL 的经验,所以也许不同的系统允许一些其他优化。

(SELECT count(*) FROM review WHERE to_user_id = u.id) as reviewCount是一个依赖子查询- 它将为结果中的每一行执行。即使一个执行速度很快,潜在的数千个执行也会使它变慢。

中的一个JOIN派生表- 它只会执行一次并具体化到一个临时表中,然后该临时表将连接到您的其他表。如果查询速度很快(可以使用 index on (to_user_id)),那很好。但在这种情况下,即使对于没有真正显示在结果中的用户,计数也会被计算在内。但是..你可以把条件放在那里(to_user_id = 1而不是GROUP BY)。

但是为了让事情变得不那么简单,新版本中存在一些优化。通过在 MariaDB 10(和 IIRC MySQL 5.7,但我没有验证)中使用子查询缓存,可以使依赖子查询更快。这意味着在您的情况下,结果中的所有行都具有u.id = 1->to_user_id = 1并且子查询实际上只会执行一次,然后将使用缓存的结果。如果可用,则两个版本之间的差异将很小。

就我个人而言,大多数时候我更喜欢你的第二个版本,但在某些情况下,第一个版本会更快 - 我曾经有一个查询,它不能简单地以正确的方式限制 JOINED 子查询中的行,而是切换到依赖子查询实际上只读取了几个唯一的组合。