2016-09-29 89 views
2

EDITED:按请求添加完整查询。mysql - 在连接表列上优化ORDER BY COALESCE

实质上,我有一个帖子的表格,其中链接了一对多的转贴表,类似于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 

更精确地(伪ISH),因为我使用DAL:

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子句确保至多一个转贴(一个我想)接合。 COALESCE是必要的,因为如果帖子未被转贴,则r可能为NULL。该查询的行为如预期 - 当ORDER BY子句被省略时,或者仅在像p.time这样的索引列上使用时,速度很快。这是预料之中的,因为邮政表是大型的100k +行。

查询说明

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

  • 加载10个帖子的列表,这些帖子是由一组用户创作的(后面)或由同一集合(后面)转发的,由最近排序的。
  • 帖子应按帖子发布时间或第一次转发时间排序。
  • 忽略一组不同的(阻塞)职位和转播用户

  • 获取帖子:从帖子

    选择
  • 由跟随集合中的用户获取最早转贴:LEFT JOIN ON ... r.time =(SELECT MIN(r.time)...)
  • 过滤掉未被用户创作或转贴的文章,其中包括:WHERE(转贴不是NULL ...)
  • 订购是第一个转载(如果存在)或发布时间:ORDER BY COALESCE(repost.time,p.time)
  • 负载最多10个帖子:LIMIT 0,10

UPDATE

我发现:

...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树为它的索引的事实,现在看来,这将是不可能以充分利用我想要的方式索引。因此,我目前最好的想法是将每个原始帖子视为其作者的“转贴”,然后在转贴表上执行我的选择和订购,例如,

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

“我不会发布我的整个查询,因为它非常复杂。”那么这个练习是毫无意义的。一个不同的查询会有不同的性能问题 – e4c5

+0

如果它有帮助,我可以发布整个事情。但我不认为所有的WHERE和ON都必须相关。我已经剥离并在我自己的测试中省略了各个部分 - 似乎肯定ORDER BY子句和相关的LEFT JOIN导致了痛点。 – CaptainStiggz

+0

基本问题是,按表达式排序需要它生成一个包含所有结果的中间表,以便它可以计算每行的表达式。它不能使用索引来优化它。 – Barmar

回答

0

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

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

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

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

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

2)对待每一个岗位作为其作者一个重新发布,使每一个岗位是保证有一个可连接转贴和repost.time在其上的索引和排序。例如

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 ms。