游标

一、引言

在处理大量数据时,分页(Pagination) 是常用的技术手段。然而,使用 LIMITOFFSET 进行分页在数据量很大时可能会导致性能问题。相反,使用 游标(Cursor)基于游标的分页(Cursor-based Pagination) 可以提高查询效率。


二、为什么尽量使用游标而非分页?

1. 分页的性能问题

  • 高偏移量性能低下:使用 LIMIT OFFSET 进行分页,当 OFFSET 值很大时,数据库需要扫描大量数据才能跳过指定的记录数,这会导致查询性能显著下降。

  • 全表扫描风险:在没有合适索引的情况下,分页查询可能导致全表扫描,增加了 I/O 开销。

2. 游标(基于游标的分页)的优势

  • 更高效的查询:基于游标的分页利用索引,直接定位数据的位置,不需要跳过大量记录,查询性能更高。

  • 数据一致性:在数据频繁变化的情况下,游标分页能更好地保持数据的一致性,避免数据遗漏或重复。

  • 避免重复和遗漏:基于唯一标识(如 id)的游标分页,能够精确地定位数据,避免分页过程中出现的数据重复或遗漏问题。


三、游标分页的原理

游标分页,也称为 基于键集的分页(Keyset Pagination),通过使用上一次查询结果的唯一标识(如自增 id、时间戳等),在下一次查询时作为游标进行定位,获取后续的数据。

  • 基本思想:使用 WHERE 子句筛选出比上次最后一条记录大的数据,配合 LIMIT 获取下一页数据。

  • 优点:利用索引查找,性能与数据量无关,查询速度更快。


四、优化后的示例(非存储过程)

1. 数据表结构

假设有一个 users 表:

id name email
1 用户1 user1@example.com
2 用户2 user2@example.com
... ... ...
100 用户100 user100@example.com

2. 传统分页的缺点

传统分页查询(第 N 页,每页 15 条):

SELECT id, name, email FROM users
ORDER BY id ASC
LIMIT 15 OFFSET (N - 1) * 15;
  • 问题:当 N 很大时,OFFSET 也很大,数据库需要扫描和跳过大量记录,性能下降。

3. 使用游标分页的优化查询

步骤:

  1. 获取初始游标值:首次查询时,无需游标,或设置为 0

  2. 查询数据:使用游标值作为条件,获取下一页的数据。

  3. 更新游标值:将本次查询结果的最后一条记录的 id,作为下一次查询的游标。

示例:

  • 首次查询(初始游标为 0):

    SELECT id, name, email FROM users
    WHERE id > 0
    ORDER BY id ASC
    LIMIT 15;
    
  • 后续查询(假设上次最后一条记录的 id 为 15):

    SELECT id, name, email FROM users
    WHERE id > 15
    ORDER BY id ASC
    LIMIT 15;
    
  • 更新游标值:

    • 在应用程序中,将本次查询结果的最后一个 id 保存为新的游标值,用于下次查询。

4. 示例代码(Java 实现)

假设使用 JDBC 进行数据库操作:

public List<User> getUsersAfterId(int lastId, int pageSize) throws SQLException {
    String sql = "SELECT id, name, email FROM users WHERE id > ? ORDER BY id ASC LIMIT ?";
    List<User> userList = new ArrayList<>();

    try (Connection conn = dataSource.getConnection();
         PreparedStatement pstmt = conn.prepareStatement(sql)) {
        pstmt.setInt(1, lastId);
        pstmt.setInt(2, pageSize);

        try (ResultSet rs = pstmt.executeQuery()) {
            while (rs.next()) {
                User user = new User();
                user.setId(rs.getInt("id"));
                user.setName(rs.getString("name"));
                user.setEmail(rs.getString("email"));
                userList.add(user);
            }
        }
    }
    return userList;
}

使用方法:

  • 首次查询(lastId = 0):

    List<User> usersPage1 = getUsersAfterId(0, 15);
    
  • 后续查询(使用上次的最后一个 id):

    int lastId = usersPage1.get(usersPage1.size() - 1).getId();
    List<User> usersPage2 = getUsersAfterId(lastId, 15);
    

5. 前端参数的传递

  • 首次请求/api/users?lastId=0

  • 响应数据

    {
        "data": [/* 用户数据数组 */],
        "lastId": 15
    }
    
  • 下一次请求:使用上次返回的 lastId

    /api/users?lastId=15


五、游标分页的注意事项

  1. 必须有连续且有序的唯一标识符:如自增的 id、唯一的时间戳等。

  2. 适用于固定排序的情况:游标分页通常基于某个字段的排序(如 id ASC),在排序方式固定的情况下效果最佳。

  3. 无法直接跳转到任意页:游标分页更适合“上一页”“下一页”的翻页方式,而不适合直接跳转到指定页码。

  4. 数据的一致性:在数据可能被插入或删除的情况下,游标分页能更好地保持数据的一致性,避免传统分页可能出现的记录重复或遗漏。


六、游标分页与 B+ 树索引的关系

  • 利用索引:游标分页使用了索引字段(如 id)进行筛选和排序,数据库可以利用 B+ 树索引高效地定位和读取数据。

  • 避免全表扫描:相比高偏移量的 LIMIT OFFSET,游标分页的查询条件可以使数据库直接跳转到指定位置,避免扫描不必要的行。


七、总结

  • 性能优势:在数据量较大的情况下,游标分页比传统的 LIMIT OFFSET 分页性能更高。

  • 实现简单:无需使用存储过程,可以在应用程序层面实现,代码简单易懂。

  • 适用场景:适用于需要高效处理大量数据且只需“上一页”“下一页”导航的场景。

  • 优先选择游标分页:在可能的情况下,应尽量使用游标分页来替代传统的分页方式,以获得更好的性能和数据一致性。

  • 结合实际场景:根据具体的业务需求和数据规模,选择合适的分页策略,确保系统的性能和稳定性。


参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容