1876 字
约 6 分钟
3
聊天系统中的历史消息分页查询设计:基于游标分页方案

聊天系统中的历史消息分页查询设计:基于游标分页方案

在即时通讯系统中,一个常见需求是:

当用户第一次打开聊天窗口时,需要加载之前的聊天记录。

例如:

用户 A 打开和用户 B 的聊天页面:

用户A
 |
 | 打开聊天窗口
 |
 ↓

请求历史消息

 ↓

服务器返回最近聊天记录

 ↓

用户继续向上滑动

 ↓

加载更早的消息

看似简单,但当聊天数据量非常大时,如何高效查询历史消息成为一个重要问题。

本文将介绍聊天系统中 历史消息分页查询接口的设计,并重点分析为什么采用 游标分页(Cursor Pagination),而不是传统分页。


1. 业务场景分析

假设我们的聊天系统每天产生大量消息:

chat_message 表

+----+---------+---------+----------------+
| id | user_id | room_id | content        |
+----+---------+---------+----------------+
| 1  | 1001    | 2001    | hello          |
| 2  | 1002    | 2001    | hi             |
| 3  | 1001    | 2001    | how are you    |
|... | ...     | ...     | ...            |
+----+---------+---------+----------------+

当用户进入聊天室:

第一次:

加载最近20条消息

继续向上滑:

加载更早20条消息

例如:

第一次请求:

GET /message/history?roomId=1001

返回:

消息10000
消息9999
...
消息9981

第二次:

GET /message/history?
roomId=1001
cursor=9981

返回:

消息9980
消息9979
...
消息9961

这种模式就是游标分页。


2. 为什么不用传统分页?

很多系统第一反应是:

page=1
size=20

例如:

SELECT *
FROM chat_message
WHERE room_id=1001
ORDER BY id DESC
LIMIT 20 OFFSET 0;

第二页:

SELECT *
FROM chat_message
WHERE room_id=1001
ORDER BY id DESC
LIMIT 20 OFFSET 20;

看起来没有问题。

但是随着数据量增加,会出现 深度分页问题


3. 深度分页问题

假设聊天室已经积累:

1亿条消息

用户查询:

第500万页

SQL:

LIMIT 20 OFFSET 100000000

数据库执行过程:

扫描前100000000条数据

↓

丢弃这些数据

↓

返回最后20条

也就是说:

用户只需要20条数据。

数据库却需要处理:

100000020条数据

随着 OFFSET 增大:

  • 查询越来越慢
  • CPU 消耗增加
  • IO 增加
  • 数据库压力变大

这就是:

深度分页问题


4. 游标分页思想

游标分页的核心:

不告诉数据库跳过多少条,而是告诉数据库从哪里继续查询。

例如:

第一次:

查询最新20条

结果:

id
10000
9999
9998
...
9981

记录最后一条:

cursor = 9981

下一次:

告诉数据库:

从 id < 9981 的地方继续查

SQL:

SELECT *
FROM chat_message
WHERE room_id = 1001
AND id < 9981
ORDER BY id DESC
LIMIT 20;

数据库执行:

定位 id=9981

↓

向前扫描20条

↓

返回结果

时间复杂度基本稳定。


5. 历史消息接口设计

接口定义

查询历史消息

GET /api/chat/history

请求参数:

{
    "roomId":1001,
    "cursor":9981,
    "limit":20
}

参数说明:

参数 说明
roomId 聊天室ID
cursor 游标位置
limit 查询数量

第一次请求:

没有 cursor:

{
    "roomId":1001,
    "limit":20
}

表示:

查询最新20条消息

6. 返回结构设计

响应:

{
    "code":200,
    "data":{
        "list":[
            {
                "id":9980,
                "senderId":1001,
                "content":"hello",
                "createTime":"2026-01-12 10:00:00"
            }
        ],
        "nextCursor":9961,
        "hasMore":true
    }
}

字段说明:

list

当前消息列表:

20条聊天记录

nextCursor

下一次查询游标:

例如:

9961

下一次:

id < 9961

hasMore

是否还有更多数据:

true:
还有历史消息


false:
已经到底

7. 数据库设计优化

聊天消息表:

CREATE TABLE chat_message
(
    id BIGINT PRIMARY KEY,

    room_id BIGINT NOT NULL,

    sender_id BIGINT NOT NULL,

    content TEXT,

    create_time DATETIME,


    INDEX idx_room_id_id(room_id,id)

);

关键索引:

(room_id,id)

为什么?

查询:

WHERE room_id=1001
AND id < 9981
ORDER BY id DESC
LIMIT 20

数据库可以直接利用索引:

room_id
   |
   |
   +---- id倒序扫描

         9980
         9979
         9978

         ...

避免全表扫描。


8. 为什么聊天系统特别适合游标分页?

聊天消息有几个特点:

① 数据只增加,不修改

消息通常:

insert
insert
insert

很少:

update
delete

非常适合使用 ID 作为游标。


② 查询方向固定

用户查看历史:

最新
 ↓
更早
 ↓
更早

天然符合:

id < cursor

③ 数据量巨大

大型 IM 系统:

每天可能产生:

亿级消息

传统分页无法支撑。


9. 关于消息顺序问题

通常消息 ID 使用:

方案1:数据库自增 ID

例如:

10001
10002
10003

优点:

  • 简单
  • 索引效率高

缺点:

  • 分布式环境困难

方案2:雪花算法 ID

例如:

174324234234234

特点:

时间递增:

旧消息

↓

新消息

适合:

  • 分布式 IM
  • 微服务架构

10. 游标分页完整流程

用户打开聊天:

客户端

GET history

↓

服务器

查询最新20条

↓

返回:

messages

cursor

↓

客户端展示

用户继续上滑:

携带cursor

↓

服务器:

WHERE id < cursor

↓

返回更早消息

循环:

最新消息

↓

上一页

↓

上一页

↓

直到结束

11. 游标分页与普通分页对比

对比 OFFSET分页 游标分页
实现难度 简单 稍复杂
查询性能 后期下降明显 稳定
深分页
数据变化影响
适合数据量 小数据 海量数据
聊天记录

12. 实际 IM 系统架构

一个完整聊天系统通常:

客户端

   |
   |

HTTP

   |

历史消息服务

   |

MySQL

   |

chat_message表



实时消息:

WebSocket

   |

消息网关

   |

消息服务

其中:

HTTP:

负责:

历史消息查询
登录
用户信息

WebSocket:

负责:

实时聊天
消息推送
在线状态

两者结合:

HTTP + WebSocket

=
完整IM系统

总结

在聊天系统中,历史消息查询是一个高频业务。

当数据量较小时:

page + size

也可以满足需求。

但随着聊天记录增长:

千万级
亿级

传统分页会出现严重的深度分页问题。

因此大型 IM 系统通常采用:

基于游标(Cursor)的分页方案

核心思想:

不要告诉数据库跳过多少条数据

而是告诉数据库

从哪个位置继续查询

最终实现:

  • 查询性能稳定
  • 支持海量消息
  • 用户滑动加载流畅

这也是微信、QQ、Slack 等即时通讯系统加载历史消息时常见的设计方案。

聊天系统中的历史消息分页查询设计:基于游标分页方案
http://clxhxhhr.top/posts/404/
作者
clxstart
发布于
2026-09-01
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。