MySQL联合索引

java 复制代码
-- 对话历史表
create table chat_history
(
    id          bigint auto_increment comment 'id' primary key,
    message     text                               not null comment '消息',
    messageType varchar(32)                        not null comment 'user/ai',
    appId       bigint                             not null comment '应用id',
    userId      bigint                             not null comment '创建用户id',
    createTime  datetime default CURRENT_TIMESTAMP not null comment '创建时间',
    updateTime  datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
    isDelete    tinyint  default 0                 not null comment '是否删除',
    INDEX idx_appId (appId),                       -- 提升基于应用的查询性能
    INDEX idx_createTime (createTime),             -- 提升基于时间的查询性能
    INDEX idx_appId_createTime (appId, createTime) -- 游标查询核心索引
) comment '对话历史' collate = utf8mb4_unicode_ci;

你想了解在chat_history表中,联合索引idx_appId_createTime (appId, createTime)被标注为 "游标查询核心索引" 且能 "避免回表" 的具体含义,核心是搞清楚这个联合索引如何减少 / 消除查询时的 "回表" 操作,提升性能。

先理解核心概念:回表

MySQL 的 InnoDB 存储引擎中,索引分为两类:

  1. 聚簇索引(主键索引) :叶子节点直接存储整行数据(这是 InnoDB 表的默认特性,主键就是聚簇索引)。
  2. 二级索引(非主键索引,如普通索引 / 联合索引) :叶子节点只存储索引列的值 + 主键 id(不会存整行数据)。

当你执行查询时,如果:

  • 条件用到了二级索引(比如where appId=1);
  • 但查询的字段(select后的列)不在这个二级索引的列中;

数据库会先通过二级索引找到匹配记录的主键 id ,再拿着这个 id 去聚簇索引(主键索引)中查整行数据 ------ 这个 "先找 id,再查行" 的额外步骤,就是回表。回表会增加磁盘 IO,降低查询效率。

为什么这个联合索引能避免回表?

idx_appId_createTime (appId, createTime)联合二级索引 ,它的叶子节点存储的内容是:(appId值, createTime值, 主键id)(InnoDB 的二级索引默认附带主键)。

对于 "游标查询"(比如分页加载对话记录、按时间滚动加载),这类查询的典型场景是:

sql

sql 复制代码
-- 游标查询示例:按appId筛选,按createTime排序,分页获取记录
SELECT id, message, messageType, appId, createTime 
FROM chat_history 
WHERE appId = 123 AND createTime > '2026-01-01' 
ORDER BY createTime LIMIT 100;
场景 1:完全避免回表(覆盖索引)

如果你的查询字段全部包含在联合索引 + 主键中 (比如只查id, appId, createTime):

sql

sql 复制代码
SELECT id, appId, createTime 
FROM chat_history 
WHERE appId = 123 AND createTime > '2026-01-01' 
ORDER BY createTime;

此时数据库只需要扫描idx_appId_createTime这个索引,就能从叶子节点中直接拿到appId、createTime、id不需要回表(这就是 "覆盖索引" 特性)。

场景 2:减少回表次数(核心优化)

即使你要查messagemessageType等不在索引中的字段(必须回表),这个联合索引依然能极大优化性能:

  • 没有这个联合索引时:可能需要全表扫描,或先单查appId索引,再对结果按createTime排序(文件排序),最后批量回表;
  • 有这个联合索引时:数据库先通过appId+createTime快速定位到符合条件的主键 id(索引是有序的,无需额外排序),再用这些 id 批量回表 ------ 回表次数大幅减少,且避免了文件排序,这也是 "避免回表" 的核心价值(实际是减少回表的开销)。

总结

  1. 回表是二级索引查询时,需要通过主键再查整行数据的额外操作,会降低性能;
  2. 该联合索引利用覆盖索引 特性,对appId+createTime的游标查询(分页 / 滚动加载),要么完全避免回表,要么大幅减少回表次数;
  3. 这是针对 "按应用 + 时间范围查询对话记录" 这类核心场景的性能优化,也是标注为 "游标查询核心索引" 的原因。
相关推荐
予枫的编程笔记2 小时前
【MySQL修炼篇】从踩坑到精通:事务隔离级别的3大异常(脏读/幻读/不可重复读)解决方案
数据库·mysql·后端开发·数据库事务·事务隔离级别·rr级别·脏读幻读不可重复读
一起养小猫3 小时前
Flutter for OpenHarmony 实战:记账应用数据统计与可视化
开发语言·jvm·数据库·flutter·信息可视化·harmonyos
世界尽头与你3 小时前
(修复方案)CVE-2023-22047: Oracle PeopleSoft Enterprise PeopleTools 未授权访问漏洞
数据库·安全·oracle·渗透测试
韩立学长3 小时前
【开题答辩实录分享】以《智能大学宿舍管理系统的设计与实现》为例进行选题答辩实录分享
数据库·spring boot·后端
Henry Zhu1234 小时前
数据库(五):反规范化
数据库
Mr_Xuhhh4 小时前
MySQL函数详解:日期、字符串、数学及其他常用函数
java·数据库·sql
he___H5 小时前
Redis高级数据类型
数据库·redis·缓存
霖霖总总5 小时前
[小技巧60]深入解析 MySQL Online DDL:MySQL Online DDL、pt-osc 与 gh-ost 机制与最佳实践
数据库·mysql
爱学习的阿磊5 小时前
使用PyTorch构建你的第一个神经网络
jvm·数据库·python