我想获取在三个表中分开的数据:
app_android_devices
:
id | associated_user_id | registration_id
app_android_devices_settings
:
owner_id | is_user_id | notifications_receive | notifications_likes_only
app_android_devices_favorites
:
owner_id | is_user_id | image_id
owner_id
是来自id
的{{1}}或来自app_android_devices
的{{1}}。
这是因为我的应用程序的用户应该能够登录他们的帐户或匿名使用该应用程序。如果用户登录,他将在所有设备上具有相同的设置和喜欢。
如果设备被匿名使用,则associated_user_id
为0,或者来自另一个表的用户ID。{/ p>
现在我收到了以下问题:
is_user_id
确定设备是否应接收有关新评论的推送通知。我已设置以下键:
associated_user_id
我注意到上面的查询真的很慢。如果我在该查询上运行EXPLAIN,我发现MySQL根本没有使用任何键,尽管列出了possible_keys。
我该怎么做才能加快查询速度?
答案 0 :(得分:3)
拥有如此复杂的JOIN
条件会让每个人的生活变得艰难。对于想要了解您的查询的开发人员以及希望为您提供您所要求的内容而更喜欢更高效操作的查询优化器而言,这会让生活变得艰难。
所以,当你告诉我这个查询很慢而且没有使用任何索引时,我想要做的第一件事是将它拆开并将其与更简单的JOIN
条件一起重新组合。
从描述此查询的方式来看,is_user_id
列听起来像是一种状态变量,告诉您用户是否登录到您的应用。这至少可以说是尴尬的;如果s.is_user_id != f.is_user_id
会发生什么?为什么将它存储在两个表中?就此而言,为什么要将它存储在您的数据库中,而不是存储在cookie中?
也许有些事情我不理解你在这里要去的功能。在任何情况下,我认为我想要摆脱的第一件事就是OR
条件中的JOIN
。我将尽量避免对查询中哪些值表示用户输入做出太多假设;这里有一个稍微通用的示例,说明可能如何将这些JOIN
条件重写为两个UNION
语句的SELECT
:
SELECT ... FROM
app_android_devices d
JOIN
app_android_devices_settings s ON d.id = s.owner_id
JOIN
app_android_devices_favorites f ON d.id = f.owner_id
WHERE s.is_user_id = 0 AND f.is_user_id = 0 AND ...
UNION ALL
SELECT ... FROM
app_android_devices d
JOIN
app_android_devices_settings s ON d.associated_user_id = s.owner_id
JOIN
app_android_devices_favorites f ON d.associated_user_id = f.owner_id
WHERE s.is_user_id = 1 AND f.is_user_id = 1 AND ...
如果这两个查询命中了索引并且非常有选择性,那么您可能不会注意到UNION
操作所需的额外开销(创建临时表)。看起来结果集中的一个甚至可能是空的,在这种情况下UNION
的成本应为零。
WHERE s.notifications_receive=1
AND (s.notifications_likes_only=0 OR f.image_id=86);
这并不太神秘 - 只有当notifications_receive
设置为true时才需要结果,并且仅当notifications_likes_only
设置为false或所请求的图片是"最喜欢的& #34;图片。根据{{1}}的状态,您可能甚至不关心收藏表 - 除非绝对必要,否则甚至不要从该表中读取是不是很好?
这似乎是EXISTS()
的一个好例子。不要加入notifications_likes_only
,而是尝试使用以下条件:
app_android_devices_favorites
在WHERE s.notifications_receive = 1
AND (s.notifications_likes_only = 0
OR EXISTS(SELECT 1 FROM app_android_devices_favorites
WHERE image_id = 86 AND owner_id = s.owner_id)
子查询中尝试SELECT
的内容并不重要;有些人更喜欢EXISTS()
,我喜欢*
,但即使您提供了特定的列,也不会影响执行计划。