MySQL 慢查询优化之-降序索引

MySQL Slow Query Optimization - Descending Index

Posted by HaoDu on August 10, 2022

起因

查看 php-fpm 慢日志时发现有个方法频繁超时(默认超时时间为 10s),通过本地及生产访问,猜测是 MySQL 慢查询问题。 找到阿里云后台 平均执行 10+s,最长执行时长 50s……,阿里云这个建议点了之后一直转圈😓

开始优化

先来看一下表结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
create table notifications
(
    id              char(36)        not null
        primary key,
    type            varchar(255)    not null,
    notifiable_type varchar(255)    not null,
    notifiable_id   bigint unsigned not null,
    data            text            not null,
    read_at         timestamp       null,
    created_at      timestamp       null,
    updated_at      timestamp       null
)
    collate = utf8mb4_unicode_ci;

create index notifications_notifiable_type_notifiable_id_index
    on notifications (notifiable_type, notifiable_id);

下面是慢查询语句

1
2
3
4
5
6
7
   select *
   from `notifications`
   where `notifications`.`notifiable_type` = '巴拉巴拉'
     and `notifications`.`notifiable_id` = 2
   order by `created_at` desc,
            `read_at`
   limit 15 offset 0;

这个 notifications 是 Laravel 自带的消息通知系统。

explain 分析一下,Extra 格外引人注目,filesort 可不是个好东西,覆盖索引可破之,于是加上覆盖索引 notifiable_type, notifiable_id,created_at,read_at ,结果还是不走这个索引,而且我发现去掉 read_at 的排序条件后,就走这个索引了,Extra 列显示 Backward index scan

于是 Google “两个字段排序对索引的影响”,发现两个字段如果排序方向不一致是不会走索引的,但是 MySQL8.0 新增了 降序索引 可破,这是个好东西。 于是加上这个索引

1
2
create index idx_created_at_read_at
on notifications (notifiable_type, notifiable_id, created_at desc, read_at);

终于走索引了,没有额外的排序,试了下查询时间,从50s 降到 300ms,收益几百倍,爽。

总结

避免额外排序可以使用覆盖索引。

降序索引可以不用排序,直接扫描二级索引树就得到了排好的数据。