代码之家  ›  专栏  ›  技术社区  ›  Wonka

如何用索引优化查询?

  •  1
  • Wonka  · 技术社区  · 8 年前

    我们正在尝试从数据库中获取跟踪者的最新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`);
    

    解释:

    enter image description here

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

    enter image description here

    SQL转储:

    SQL DUMP TO REPRODUCE LOCALLY 只需创建 speed_test 本地数据库和导入文件以查看所有表数据的慢速查询问题 (约10万行) .

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

    2 回复  |  直到 8 年前
        1
  •  3
  •   Gordon Linoff    8 年前

    对于此查询:

    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) . 这可能更快。

        2
  •  0
  •   rajat    8 年前

    你应该试试这个。

    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`);
    
    推荐文章