You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MySQL查询因filesort性能下降,求索引优化方案

问题描述

我有一张包含超过1.9亿条记录的notification表,表结构如下:

CREATE TABLE notification (
  _id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,

  recipient CHAR(11) NOT NULL,
  recipient_group CHAR(11),
  topic VARCHAR(25) NOT NULL,
  identifier VARCHAR(60) NOT NULL,
  timestamp TIMESTAMP(3) NOT NULL,
  type VARCHAR(255) NOT NULL,
  actioned BIT NOT NULL DEFAULT 0,
  expiry_timestamp TIMESTAMP DEFAULT NULL,

  INDEX recipient_recipient_group_timestamp_id (recipient, recipient_group, timestamp DESC, _id DESC),
  INDEX topic_identifier (topic, identifier),
  INDEX expiry_timestamp (expiry_timestamp),
  UNIQUE recipient_recipient_group_topic_identifier (recipient, recipient_group, topic, identifier)
) CHARACTER SET ascii COLLATE ascii_bin;

我需要查询某一收件人属于指定组的、基于timestamp的所有通知,执行的查询语句如下:

explain
select * from notification
where (recipient = 'recipient' and (recipient_group = 'group' or recipient_group is null)
    and (expiry_timestamp > {ts '2018-06-26 08:00:00.0'} or expiry_timestamp is null)
    and timestamp > {ts '1970-01-01 00:00:00.0'} and type in ('TYPE'))
order by timestamp desc, _id desc limit 10;

当用户的通知数量较多时,该查询性能较差,因为MySQL在对timestamp和_id排序时使用了filesort。执行计划如下:

+----+-------------+--------------+------------+-------------+----------------------------------------------------------------------------------------------------+--------------------------------------------+---------+-------------+------+----------+----------------------------------------------------+
| id | select_type | table        | partitions | type        | possible_keys                                                                                      | key                                        | key_len | ref         | rows | filtered | Extra                                              |
+----+-------------+--------------+------------+-------------+----------------------------------------------------------------------------------------------------+--------------------------------------------+---------+-------------+------+----------+----------------------------------------------------+
|  1 | SIMPLE      | notification | NULL       | ref_or_null | recipient_recipient_group_topic_identifier,recipient_recipient_group_timestamp_id,expiry_timestamp | recipient_recipient_group_topic_identifier | 23      | const,const |    2 |     5.01 | Using index condition; Using where; Using filesort |
+----+-------------+--------------+------------+-------------+----------------------------------------------------------------------------------------------------+--------------------------------------------+---------+-------------+------+----------+----------------------------------------------------+

请问是否可以通过添加或修改索引来提升查询性能?

补充:若移除where条件中的“or recipient_group is null”,MySQL会使用recipient_recipient_group_timestamp_id索引。

优化方案

1. 拆分查询并合并结果

由于recipient_group = 'group'和recipient_group is null的OR条件导致现有复合索引无法高效发挥作用,最可靠的方式是把原查询拆成两个独立子查询,分别处理两种情况,再用UNION ALL合并结果后排序:

(SELECT * FROM notification
 WHERE recipient = 'recipient'
   AND recipient_group = 'group'
   AND (expiry_timestamp > {ts '2018-06-26 08:00:00.0'} OR expiry_timestamp IS NULL)
   AND timestamp > {ts '1970-01-01 00:00:00.0'}
   AND type IN ('TYPE')
 ORDER BY timestamp DESC, _id DESC LIMIT 10)
UNION ALL
(SELECT * FROM notification
 WHERE recipient = 'recipient'
   AND recipient_group IS NULL
   AND (expiry_timestamp > {ts '2018-06-26 08:00:00.0'} OR expiry_timestamp IS NULL)
   AND timestamp > {ts '1970-01-01 00:00:00.0'}
   AND type IN ('TYPE')
 ORDER BY timestamp DESC, _id DESC LIMIT 10)
ORDER BY timestamp DESC, _id DESC LIMIT 10;

每个子查询都能直接利用recipient_recipient_group_timestamp_id索引,避免filesort;最后合并后最多只有20条数据,排序的性能开销可以忽略。

2. 新增针对性复合索引(MySQL 8.0+适用)

如果不想拆分查询,可以针对recipient+type+排序字段的组合建覆盖索引,同时包含expiry_timestamp避免回表:

CREATE INDEX idx_recipient_type_ts_id_expiry ON notification (
    recipient,
    type,
    timestamp DESC,
    _id DESC
) INCLUDE (recipient_group, expiry_timestamp);

或者针对recipient_group IS NULL的场景单独建函数索引:

CREATE INDEX idx_recipient_null_group_ts_id ON notification (
    recipient,
    (recipient_group IS NULL),
    timestamp DESC,
    _id DESC
) INCLUDE (type, expiry_timestamp);

这类索引能帮助优化器快速定位符合条件的数据,同时直接利用索引顺序完成排序。

3. 调整现有索引为覆盖索引

把现有的recipient_recipient_group_timestamp_id索引扩展为覆盖索引,包含查询需要的type和expiry_timestamp字段:

DROP INDEX recipient_recipient_group_timestamp_id ON notification;
CREATE INDEX idx_recipient_group_ts_id_type_expiry ON notification (
    recipient,
    recipient_group,
    timestamp DESC,
    _id DESC
) INCLUDE (type, expiry_timestamp);

这样即使需要过滤type和expiry_timestamp,也不需要回表查询,但由于OR条件的存在,优化器不一定会选择该索引,所以拆分查询的方式稳定性更高。

内容的提问来源于stack exchange,提问作者Lakmal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 12:55:20