问题描述:
使用人大金仓数据库时,导出的数据有重复,数据为分页查询聚合后导出。
排查
第一步:检查数据库层面的数据
SELECT * FROM test_data WHERE id = '重复记录的ID或其他条件';
数据库中实际上只有一条记录。
第二步:分析程序代码
查看导出数据的核心代码:
Page<ExportData> page;
do {
log.info("查询数据,pageNum={},pageSize={},currentTotal={}", pageNum, pageSize, dataList.size());
page = PageHelper.startPage(pageNum++, pageSize, false);
dataExportMapper.queryData(request);
dataList.addAll(page.getResult());
} while (CollUtil.isNotEmpty(page.getResult()));
对应的SQL查询语句:
select * from (
select TMP_PAGE.*, ROWNUM PAGEHELPER_ROW_ID
from (
select i.id from test_data i
left join test_file f on i.FILE_ID = f.ID
where i.IS_DEL = '0'
order by i.CREATE_TIME
) TMP_PAGE
) where PAGEHELPER_ROW_ID > 0 and PAGEHELPER_ROW_ID <= 1000;
第三步:定位可能的问题点
分析后发现以下几个可能导致数据重复的嫌疑点:
- 表关联问题:左连接
test_file表时,如果被关联表中有多条匹配记录,会导致主表数据重复 - 分页机制问题:分页查询时的排序条件不够唯一
- 数据库特性问题:人大金仓数据库的
ROWNUM机制可能与其他数据库有所不同
解决方案
问题的根本原因在于:排序字段不唯一。
在原来的SQL中,只按照CREATE_TIME进行排序,当存在多条记录具有相同创建时间时,数据库无法保证这些记录的返回顺序在多次查询中保持一致。
解决方案:在排序条件中追加一个或多个唯一字段(如主键ID)。
sql修改如下:
select * from (
select TMP_PAGE.*, ROWNUM PAGEHELPER_ROW_ID
from (
select i.id from test_data i
left join test_file f on i.FILE_ID = f.ID
where i.IS_DEL = '0'
order by i.CREATE_TIME , i.ID -- 增加主键ID确保排序唯一性
) TMP_PAGE
) where PAGEHELPER_ROW_ID > 0 and PAGEHELPER_ROW_ID <= 1000;
原理分析
为什么排序字段不唯一会导致数据重复?
在人大金仓/Oracle数据库中,ROWNUM是在数据被检索后、排序前分配的。如果排序字段不唯一,数据库不保证每次查询时相同排序值的记录顺序一致。这就可能导致:
- 第一次分页查询时,某条记录出现在第1页
- 第二次分页查询时,由于排序不稳定,相同的记录可能"漂移"到第2页
- 最终聚合时,同一条记录被多次加入结果集
经验总结
通过这次排查,我总结了数据重复问题的通用排查思路:
- 确认数据源头:首先检查数据库中是否存在真正的重复数据
- 检查关联查询:确认关联表是否导致主表数据重复
- 分析分页逻辑:检查排序条件是否足够唯一和稳定
- 考虑数据库隔离级别(针对MySQL):
- 在RC(读已提交)隔离级别下,每次SELECT都会生成新的ReadView
- 如果有其他事务提交新增数据,可能导致分页结果出现交集
- 解决方法:确保排序条件明确,且新增数据排在结果集末尾
- 关注数据库差异:不同数据库的分页机制可能有细微差别
最后提醒
分页查询时,务必使用唯一或足够唯一的排序条件,这是一个看似简单却很容易踩坑的地方。特别是在处理数据导出、数据迁移等场景时,这个问题尤为关键。
希望我的经验能帮你少走弯路!如果你也遇到了类似问题,欢迎在评论区交流讨论。
本文基于人大金仓数据库实践经验总结,不同版本可能有所差异,请以实际情况为准。