Hibernate查询分区表AsyncFcmNotification触发ResultSet耗尽异常如何解决
异常根因与修复方案
根本原因
- 静态统计的循环终止阈值与动态变化的结果集不匹配:程序启动时一次性统计得到的
asyncFcmNotificationsCount=105220是当时满足sent = false条件的记录总量,但循环每执行一轮,都会通过updateUnsendNotif把查出来的记录的sent状态从false更新为true,直接导致后续满足查询条件的真实数据量持续减少,初始的统计值会随着业务推进越来越大于实际剩余的待处理数据量。 - offset深分页越界触发ResultSet异常:当累加的offset值超过当前实际满足查询条件的记录总条数时,SQL执行时的偏移量已经超出了结果集的总长度,数据库返回的结果集游标直接走到末尾,没有可映射的数据行,Hibernate在读取结果集映射实体时就会抛出ResultSet耗尽类的运行时异常。
- 表分区配置放大了深分页的越界影响:由于表是基于timestamp时间列做的分区,查询没有命中有效分区裁剪时,数据库定位offset位置需要扫描所有相关分区的数据,当offset超过实际结果集长度时,全分区扫描完成后游标直接终止,没有返回有效数据,进一步触发了游标耗尽的报错。
修复方案
- 替换静态终止判断逻辑:取消使用程序启动时计算的
asyncFcmNotificationsCount作为循环终止条件,每轮查询完成后判断当前返回批次的实际长度,如果返回空集合,直接跳出循环,不再继续发起下一次查询。核心调整逻辑参考:do { try (HibernateSession session = SessionManager.openSession()) { List<AsyncFcmNotification> batchList = session.createQuery("from AsyncFcmNotification where sent = false order by id asc") .setFirstResult(offset) .setMaxResults(count) .list(); // 无有效数据时直接终止循环,避免offset越界 if (batchList.isEmpty()) { break; } updateUnsendNotif(batchList, session); offset += 100; } logger.info(offset + " notif has updated"); } while (true); - 优化分页模式:从传统offset深分页替换为键集/游标分页,每次查询带上上一轮最后一条记录的唯一主键值,查询条件调整为
where sent = false and id > 上一轮批次的最大id,搭配固定的升序排序,既不会因为结果集动态变化出现越界,也能大幅提升分区表的查询效率,避免全分区扫描定位offset的性能损耗。 - 补全查询排序规则:原查询没有指定固定排序规则,数据库返回的行顺序是随机不稳定的,即使没有出现越界,也可能出现分页重复查询、漏查的问题,必须在查询语句中增加明确的排序字段,保证每轮查询的结果顺序固定一致。
内容的提问来源于stack exchange,提问作者lia
相关产品推荐
相关产品推荐

