1

我在朋友提要查询方面遇到了奇怪的麻烦 - 这是背景:

我有 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。这是一个非常简单的查询,但是当用户的朋友最近有大量活动时,这个查询非常快,下面是解释:

 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

有什么建议吗?

4

2 回答 2

2

所以..你有一些事情发生在这里..

  1. 在解释计划中......通常优化器会选择“key”中的内容,而不是可能的keys中的内容。这就是为什么当数据不是最新的时需要扫描更多记录的原因。

  2. 仅在签入表( user_id, created_at )和 created_at 是必要的..您不需要 user_id 的另一个索引..优化器将使用 (user_id, created_at ),因为 user_id 是第一顺序。

尝试这个..

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

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

  3. 为签入表中的 user_id 为 1 的 OR 条件编写另一个查询。我认为您的数据集对于这两个集合应该是互斥的,然后应该没问题..否则您不需要在 IN 之后有 OR 条件条款放在首位。

  4. 当您拥有 user_id, created_at 索引时,删除它自己的 user_id 索引。

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

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

于 2012-09-24T15:52:06.907 回答
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/

于 2012-09-24T15:51:19.767 回答