前两天在处理两个系统数据同步时发现一个问题比较诡异,特此记录。

问题表象为: List 和 Set 的 contains 方法失效,返回值永远都是 false 。

接下来啰嗦一下前因后果,这个功能是将生产环境的交易数据从 SQL Server 同步到 MySQL ,完全使用自己公司的工具,相当于通过 SQL 读数据后写入到 MySQL 。

方案是以数据的交易时间作为同步标记,但是生产数据是外方的多节点系统,数据的先后顺序和交易时间不是完全一致;为了防止数据的遗漏,所以每天定时检查之前一天的数据是否完整,如果不完整则查询匹配两边库中交易记录的主键,将遗漏的数据通过主键查询后在写入到 MySQL 。

查询匹配差异ID的代码大致如下

1
2
3
4
5
6
7
8
9
10
List<Long> ids = queryIdsFromMySQL(yesterday);
List<Long> omissiveIds = new ArrayList<>();
Cursor cursor = queryIdsFromSqlServer(yesterday);
while(cursor.hasNext()){
Record record = cursor.next();
Long id = record.getLong(1);
if(ids.contains(id){
omissiveIds.add(id);
}
}

问题的原因是: 该数据的 Id 在 MySQL 中的存储类型为 BigInt ,通过 List<Long> ids = queryIdsFromMySQL(yesterday); 获取到集合类型实际上是 BigDecimal ,虽然两个类型没有继承关系。那么到这里大家应该就清楚为什么 contains 的结果都是 false 了吧。 ArrayList 的 contains 方法是通过 equals 方法判断的, HashSet 的 contains 方法是通过 HashMap 的 hash 方法判断的,所以结果永远是 false 。

解决方案: 只需保证通过 MySQL 查询的集合中的数据类型和通过 SQL Server 光标读取 ID 的类型真实一致即可。
我的方法是将读取 MySQL 的集合泛型修改为 BigDecimal ,也将读取 SQL Server 光标的 ID 的类型也转换为 BigDecimal ,此时再通过 contains 判断后即可获取到遗漏的 ID 了。

done