【问题标题】:MySql - How to optimize query with indexes?MySql - 如何使用索引优化查询?
【发布时间】:2018-11-01 17:37:24
【问题描述】:

我们正在尝试从数据库中获取关注者的最新 10 条通知。我们会进行一些连接,以确保我们为关注者获得正确的通知集。如果他们关注的人(他们的领导者)添加了新帖子,则关注者应该只收到在他们开始关注领导者之后添加的帖子的通知(将他们的领导者的旧帖子显示为新通知是没有意义的)。另一个加入是确保我们获得通知的read_at 时间,以便关注者知道它是否已被阅读。这是查询,但它需要 ~9 秒,这太慢了。理想情况下,它应该只需要几毫秒,特别是索引:

查询:

SELECT nf.id, nf.uuid, nf.leader_id, nf.data, nf.created_at, nfr.read_at
FROM notification_followers nf
LEFT JOIN user_follows uf ON uf.leader_id = nf.leader_id AND uf.follower_id = 14 AND uf.follow_status = 'follow'
LEFT JOIN notification_followers_read nfr ON nf.id = nfr.notification_followers_id AND nfr.follower_id = 14
WHERE (nf.created_at > uf.created_at)
ORDER BY nf.id DESC
LIMIT 10

索引:

ALTER TABLE `notification_followers` ADD INDEX `nf_lid_ca_id_idx` (`leader_id`,`created_at`,`id`);
ALTER TABLE `user_follows` ADD KEY`uf_fid_lid_fs_ca_idx` (`follower_id`,`leader_id`,`follow_status`,`created_at`)
ALTER TABLE `notification_followers_read` ADD INDEX `nfr_fid_nfid_ra_idx` (`follower_id`,`notification_followers_id`,`read_at`);

说明:

正确的结果(大约需要 9 秒):

SQL 转储:

SQL DUMP TO REPRODUCE LOCALLY 只需在本地创建 speed_test 数据库并导入文件即可查看所有表数据的慢查询问题(~100K 行)

我们如何优化上述内容以在几毫秒内得到正确的结果?

【问题讨论】:

  • 这是一个内连接,对吧?
  • 不确定,但使用当前LEFT JOIN返回正确结果
  • 在通知被读取或始终可用后,notification_followers_id 是否存在于 notification_followers_read 表中?
  • 读完之后。

标签: mysql sql select left-join innodb


【解决方案1】:

对于这个查询:

SELECT nf.id, nf.uuid, nf.leader_id, nf.data, nf.created_at, nfr.read_at
FROM notification_followers nf JOIN
     user_follows uf 
     ON uf.leader_id = nf.leader_id AND uf.follower_id = 14 AND
        uf.follow_status = 'follow' LEFT JOIN 
     notification_followers_read nfr
     ON nf.id = nfr.notification_followers_id AND nfr.follower_id = 14
WHERE nf.created_at > uf.created_at
ORDER BY nf.id DESC
LIMIT 10;

我会推荐user_follower(leader_id, follower_id, follow_status, created_at)notification_followers_read(notification_followers_id, follower_id, read_at) 上的索引。索引中列的顺序很重要。

请注意,我将第一个 JOIN 更改为内部联接,因为 WHERE 子句无论如何都会将其变为一个。

嗯,让我们尝试重写查询:

SELECT nf.id, nf.uuid, nf.leader_id, nf.data, nf.created_at,
       (SELECT nfr.read_at
        FROM notification_followers_read nfr
        WHERE nf.id = nfr.notification_followers_id AND nfr.follower_id = 14
       ) nfr
FROM (SELECT nf.*
      FROM notification_followers nf 
      WHERE EXISTS (SELECT 1
                    FROM user_follows uf 
                    WHERE uf.leader_id = nf.leader_id AND uf.follower_id = 14 AND
                          uf.follow_status = 'follow' AND nf.created_at > uf.created_at
                   )
      ORDER BY nf.id DESC
      LIMIT 10
     ) nf;

为此,您要确保在 notification_followers(id) 上也有索引。

根据您的数据,使用这种方法,内部子查询可能会更快:

FROM (SELECT nf.*
      FROM user_follows uf JOIN
           notification_followers nf 
           ON uf.leader_id = nf.leader_id AND nf.created_at > uf.created_at
      WHERE uf.follower_id = 14 AND uf.follow_status = 'follow' 
      ORDER BY nf.id DESC
      LIMIT 10
     ) nf

为此,索引为user_follows(follower_id, follow_status, leader_id, created_at)notification_followers(leader_id, created_at, id)。这可能会更快。

【讨论】:

  • 嗯...实现了您的查询/索引,但需要 ~7 秒。顺便说一句,您的查询缺少我添加到您的查询中的LIMIT 10,但仍然需要很长时间才能得到结果。
  • 因此,使用原始索引建议重写的查询在 follower_id 14 上运行得非常快(需要几毫秒),但是当我将其更改为 follower_id 1 时,没有返回结果,即正确,但需要约 4 秒才能返回任何结果。有没有办法让它像14 一样更快地卷土重来?顺便说一句,我尝试用相应的索引替换内部子查询,但对于 14 来说仍然很慢
  • 对我上一条评论有什么反馈吗?
  • @Wonka 。 . .并不真地。不清楚为什么 id 的选择很重要,除非“1”有很多很多与之关联的行。
  • 实际上,1 没有任何关联的行,这就是为什么它需要超过几毫秒才能返回它没有结果的原因。无论哪种方式,您的查询确实有助于提高速度,因此感谢您对重组查询和索引的推荐:)
【解决方案2】:

你应该试试这个。

SELECT nf.id, nf.uuid, nf.leader_id, nf.data, nf.created_at, nfr.read_at 
FROM notification_followers nf 
JOIN user_follows uf ON uf.leader_id = nf.leader_id and nf.created_at > uf.created_at AND uf.follow_status = 'follow'  AND uf.follower_id = 14 
LEFT JOIN notification_followers_read nfr ON nf.id = nfr.notification_followers_id AND nfr.follower_id = 14 
ORDER BY nf.id DESC
LIMIT 10;

创建索引。

ALTER TABLE `notification_followers` ADD INDEX `nf_lid_ca_id_idx`(`leader_id`,`created_at`,`id`);
ALTER TABLE `user_follows` ADD KEY`uf_fid_lid_fs_ca_idx`(`leader_id`,`created_at`,`follow_status`,`follower_id`)
ALTER TABLE `notification_followers_read` ADD INDEX `nfr_fid_nfid_ra_idx`(`notification_followers_id`,`follower_id`,`read_at`);

【讨论】:

  • 您的查询未在问题中返回正确的结果。
  • 我已对您的问题发表评论并根据该假设进行工作,现在我已根据您的评论进行了更正。
  • 因此,id 14 需要大约 4 秒才能返回结果,而不是几毫秒。另一方面,id 1 需要几毫秒才能返回正确的结果(没有项目)。因此,与上面 Gordon Linoff 的回答相反(在我的 cmets 中可以看到)。我希望两者都能得到几毫秒,无论是使用 id 14 还是 1
猜你喜欢
  • 1970-01-01
  • 2012-01-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多