mysql - 在连接表列

时间:2016-09-29 00:20:56

标签: mysql sql doctrine-orm sql-order-by

EDITED:按要求添加完整查询。

从本质上讲,我有一个帖子表,一对一地链接到一个转贴表,类似于Twitter。我想加载在重新发布时(如果存在)或原始帖子的时间排序的帖子。但是,使用单个查询的排序过程非常慢(可能是因为COALESCE(x,y)没有充分利用MySQL索引)。两个相关表上的时间列都已编制索引。

我的查询看起来像这样。

SELECT * FROM Post p LEFT JOIN p.reposts ON ... WHERE ... 
ORDER BY COALESCE(r.time, p.time) LIMIT 0, 10

因为我正在使用DAL,所以更准确地说(伪ish):

SELECT * FROM Post p LEFT JOIN p.reposts repost ON (p.id = repost.post_id AND    
repost.time = (
  SELECT MIN(r.time) FROM Repost r WHERE p.id = r.post_id
  AND r.user_id IN (1, 2, 3...) AND r.user_id NOT IN (4, 5, 6...))
))
WHERE (repost IS NOT NULL OR p.author_id IN (1, 2, 3...)) 
AND p.author_id NOT IN (4, 5, 6...)
ORDER BY COALESCE(repost.time, p.time) LIMIT 0, 10

在上面,ON子句确保最多连接一个repost(我想要的那个)。 COALESCE是必要的,因为如果帖子没有重新发布,则r可能为NULL。查询的行为与预期相同 - 省略ORDER BY子句时快速,或仅在p.time等索引列上使用。这是预期的,因为Post表大100k +行。

查询说明

编辑:更好地解释查询应该做什么。值得注意的是这里的逻辑是有效的 - 我得到了我想要的数据。问题是应用ORDER BY子句导致查询运行速度减慢约50倍,因为MySQL无法在连接表上使用带COALESCE的索引。

  • 加载10个帖子的列表,这些帖子由一组用户(跟随)创作或由同一组(紧随其后)重新发布,按最新排序。
  • 帖子应在帖子发布时或第一次转发时订购。
  • 忽略不同组中用户的帖子和重新发布(已屏蔽)

  • 获取帖子:从帖子中选择

  • 由用户在以下集合中获取最早的重新发布:LEFT JOIN ON ... r.time =(SELECT MIN(r.time)...)
  • 在以下集合中过滤掉用户未创作或重新发布的帖子:WHERE(repost IS NOT NULL ...)
  • 订单是第一个转贴(如果存在)或发布时间:ORDER BY COALESCE(repost.time,p.time)
  • 最多加载10个帖子:LIMIT 0,10

更新

我发现:

...ORDER BY repost.time DESC

除非我还添加:

,否则也会产生缓慢的结果
...WHERE repost.id IS NOT NULL...

在这种情况下,查询速度很快。这让我相信真正的问题是对可空列索引进行排序。我也尝试过:

... ORDER BY CASE WHEN repost.id IS NULL p.time ELSE repost.time END DESC

哪个没用。

更新2

由于MySQL使用b-tree作为其索引,似乎不可能以我想要的方式利用索引。因此,我目前最好的想法是将每个原始帖子作为其作者的“转贴”处理,然后在转贴表上执行我的选择和订单,例如。

SELECT * FROM Repost r LEFT JOIN r.post ON ... WHERE ... ORDER BY r.time DESC

1 个答案:

答案 0 :(得分:0)

这里的问题就像我在问题的更新2中描述的那样。 MySQL使用索引快速执行ORDER BY操作。更具体地说,MySQL使用B-trees索引列(例如时间戳 - p.time / r.time),这会占用更多空间但允许更快的排序。

我的查询的问题是它按两个表中的时间列排序,使用重新发布表中的时间戳(如果可用),否则使用post表。由于MySQL无法组合两个表中的B树,因此无法对来自两个不同表的列执行快速索引排序。

我以两种方式修改了我的查询和表结构来解决这个问题。

1)首先根据被阻止的用户执行过滤,因此只需要对当前用户可访问的帖子进行排序。这不是问题的根源,而是实际的优化。 e.g。

SELECT * FROM (SELECT * FROM Post p WHERE p.author_id NOT IN (4, 5, 6...))...

2)将每个帖子视为其作者的转发,因此每个帖子都保证有一个可连接的重新发布和repost.time进行索引和排序。 e.g。

SELECT * FROM (...) LEFT JOIN p.reposts repost ON (p.id = repost.post_id AND 
repost.time = (
  SELECT MIN(r.time) FROM Repost r WHERE p.id = r.post_id
  AND r.user_id IN (1, 2, 3...) AND r.user_id NOT IN (4, 5, 6...))
))
WHERE (repost.id IS NOT NULL) ORDER BY repost.time DESC LIMIT 0, 10

在一天结束时,问题归结为ORDER BY - 这种方法将查询时间从大约8秒减少到20毫秒。