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. 这是针对 "按应用 + 时间范围查询对话记录" 这类核心场景的性能优化,也是标注为 "游标查询核心索引" 的原因。
相关推荐
Je1lyfish1 天前
CMU15-445 (2026 Spring) Project#2 - B+ Tree
linux·数据结构·数据库·c++·sql·spring·oracle
Schengshuo1 天前
【Oracle11g SQL详解】UPDATE 和 DELETE 操作的正确使用
数据库·sql
2401_883035461 天前
数据分析与科学计算
jvm·数据库·python
gp3210261 天前
MSSQL2022的一个错误:未在本地计算机上注册“Microsoft.ACE.OLEDB.16.0”提供程序
数据库·microsoft
oradh1 天前
Oracle 19c 单机安装总结_linux7
数据库·oracle
qq_390760391 天前
简单的线程安全日志记录器
开发语言·数据库·c#
闻哥1 天前
MySQL索引核心原理:B+树生成、页分裂与页合并全解析
java·jvm·b树·mysql·adb·面试·springboot
青柠代码录1 天前
【MySQL】DISTINCT 详解
数据库·mysql
数据知道1 天前
MongoDB查询执行计划解读:executionStats详细分析与性能诊断
数据库·mongodb
筵陌1 天前
MySQL Connector/C API的使用
数据库·mysql