朋友Feed的查询优化 - MySQL

时间:2012-09-24 15:27:45

标签: mysql optimization social-networking social

我在朋友提要查询中遇到了我的奇怪问题 - 这是背景:

我有3张桌子

checkin - around 13m records
users - around 250k records
friends - around 1.5m records

在签入表中 - 它列出了用户执行的活动。 (这里有很多索引,但是在user_id,created_at和(user_id,created_at)上有一个索引。 users表只是基本用户信息user_id上有索引。 friends表有user_id,target_id和is_approved。 (user_id,is_approved)字段有一个索引。

在我的查询中,我试图仅删除任何用户的基本朋友提要 - 所以我一直在这样做:

SELECT checkin_id, created_at
FROM checkin
WHERE (user_id IN (SELECT friend_id from friends where user_id = 1 and is_approved = 1) OR user_id = 1)
ORDER by created_at DESC
LIMIT 0, 15

查询的目的只是为所有用户的朋友及其活动提取checkin_id和created_at。这是一个非常简单的查询,但是当用户的朋友有大量的近期活动时,这个查询非常快,这里是EXPLAIN:

 id     select_type     table   type    possible_keys   key     key_len     ref     rows    Extra
 1  PRIMARY     checkin     index   user_id,user_id_2   created_at  8   NULL    15  Using where
 2  DEPENDENT SUBQUERY friends  eq_ref    user_id,friend_id,is_approved,friend_looku...     PRIMARY     8   const,func  1   Using where

作为解释,user_id是user_id的简单索引 - 而user_id_2是user_id和created_at的索引。在friends表上,friends_lookup是user_id和is_approved的索引。

这是一个非常简单的查询,并且完成了:显示0到14行(总共15行,查询花了0.0073秒)。

然而,当用户的朋友活动不是很近并且没有大量数据时,相同的查询大约需要5-7秒,并且它与前一个查询具有相同的EXPLAIN - 但需要更长时间。

它似乎对更多的朋友没有影响,似乎加速了最近的活动。

是否有任何提示,任何人都必须优化这些查询,以确保无论活动​​如何都能以相同的速度运行?

服务器设置

这是一个运行16GB RAM的专用MySQL服务器。它运行的是Ubuntu 10.10,而MySQL的版本是5.1.49

更新

所以大多数人建议删除IN片段并将它们移动到INNER JOIN:

SELECT c.checkin_id, c.created_at
FROM checkin c
INNER JOIN friends f ON c.user_id = f.friend_id
WHERE f.user_id =1
AND f.is_approved =1
ORDER BY c.created_at DESC
LIMIT 0 , 15

此查询差10倍 - 如EXPLAIN中所述:

 id     select_type     table   type    possible_keys   key     key_len     ref     rows    Extra
 1  SIMPLE  f   ref     PRIMARY,user_id,friend_id,is_approved,friend_looku...   friend_lookup   5   const,const     938     Using temporary; Using filesort
 1  SIMPLE  c   ref     user_id,user_id_2   user_id     4   untappd_prod.f.friend_id    71  Using where

此查询的目标是获取所有朋友活动,并在同一查询中获取您的活动(而不必创建两个查询并将结果合并在一起并按created_at排序)。我也无法删除user_id上的索引,因为它是另一个查询的重要部分。

有趣的是,当我在一个没有很多活动的用户帐户上运行此查询时,我得到了这个解释:

 id     select_type     table   type    possible_keys   key     key_len     ref     rows    Extra
 1  SIMPLE  f   index_merge     PRIMARY,user_id,friend_id,is_approved,friend_looku...    user_id,friend_lookup  4,5     NULL    11  Using intersect(user_id,friend_lookup); Using wher...
 1  SIMPLE  c   ref     user_id,user_id_2   user_id     4   untappd_prod.f.friend_id    71  Using where

有什么建议吗?

2 个答案:

答案 0 :(得分:2)

所以..你有几件事情在这里......

  1. 在解释计划中..通常优化器会在“key”中选择什么,而不是在possible_keys中选择什么。这就是为什么当数据不是最近时需要扫描更多记录时的体验。

  2. 上必要..你不需要另一个指数为USER_ID签表只(USER_ID,created_at)和created_at ..优化器将使用(USER_ID,created_at),因为user_id是第一阶。

  3. 试试这个..

    1. 使用朋友和签入之间的联接并删除in子句,这样朋友就成了驱动表,你应该首先看到解释计划的执行路径。

    2. 完成1后,您应该确保checkin在执行路径中使用(user_id,created_dt)索引。

    3. 为OR条件编写另一个查询,其中checkin表中的user_id为1.我认为你的数据集对于这两个集合应该是互斥的,它应该是正常的..否则你不需要首先是IN子句之后的OR条件。

    4. 删除自己的user_id索引,因为你有user_id,created_at index。

    5. - 您的目标是使用键下的索引而不仅仅是可能的键。

      这应该照顾旧的非近期签到以及最近的签到。

答案 1 :(得分:0)

我的第一个建议是删除从属子查询并将其转换为连接。我发现MySQL不擅长处理这些类型的查询。试试这个:

SELECT c.checkin_id, c.created_at
FROM checkin c
INNER JOIN friends f
   ON c.user_id = f.friend_id
WHERE f.user_id = 1
   AND f.is_approved = 1
ORDER by c.created_at DESC
LIMIT 0, 15

我的第二个建议,因为你有一个专用服务器,就是为你的所有表使用InnoDB存储引擎。确保调整默认的InnoDB设置,尤其是对于innodb_buffer_pool_size:http://www.mysqlperformanceblog.com/2007/11/03/choosing-innodb_buffer_pool_size/