我在朋友提要查询中遇到了我的奇怪问题 - 这是背景:
我有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
有什么建议吗?
答案 0 :(得分:2)
所以..你有几件事情在这里......
在解释计划中..通常优化器会在“key”中选择什么,而不是在possible_keys中选择什么。这就是为什么当数据不是最近时需要扫描更多记录时的体验。
上必要..你不需要另一个指数为USER_ID签表只(USER_ID,created_at)和created_at ..优化器将使用(USER_ID,created_at),因为user_id是第一阶。
试试这个..
使用朋友和签入之间的联接并删除in子句,这样朋友就成了驱动表,你应该首先看到解释计划的执行路径。
完成1后,您应该确保checkin在执行路径中使用(user_id,created_dt)索引。
为OR条件编写另一个查询,其中checkin表中的user_id为1.我认为你的数据集对于这两个集合应该是互斥的,它应该是正常的..否则你不需要首先是IN子句之后的OR条件。
删除自己的user_id索引,因为你有user_id,created_at index。
- 您的目标是使用键下的索引而不仅仅是可能的键。
这应该照顾旧的非近期签到以及最近的签到。
答案 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/