使用seata的过程如果undo_log表一直存留多条记录,大概是seata回滚的时候失败了,一种提示compare row failed,可以看一下你update的时候是不是把主键也放到set后面一起更新导致。
存在下面问题就是脏数据问题。
Has dirty records when undo.
数据校验代码如下:
/** * Data validation. * * @param conn the conn * @return return true if data validation is ok and need continue undo, and return false if no need continue undo. * @throws SQLException the sql exception such as has dirty data */ protected boolean dataValidationAndGoOn(Connection conn) throws SQLException { TableRecords beforeRecords = sqlUndoLog.getBeforeImage(); TableRecords afterRecords = sqlUndoLog.getAfterImage(); // Compare current data with before data // No need undo if the before data snapshot is equivalent to the after data snapshot. Result<Boolean> beforeEqualsAfterResult = DataCompareUtils.isRecordsEquals(beforeRecords, afterRecords); if (beforeEqualsAfterResult.getResult()) { if (LOGGER.isInfoEnabled()) { LOGGER.info("Stop rollback because there is no data change " + "between the before data snapshot and the after data snapshot."); } // no need continue undo. return false; } // Validate if data is dirty. TableRecords currentRecords = queryCurrentRecords(conn); // compare with current data and after image. Result<Boolean> afterEqualsCurrentResult = DataCompareUtils.isRecordsEquals(afterRecords, currentRecords); if (!afterEqualsCurrentResult.getResult()) { // If current data is not equivalent to the after data, then compare the current data with the before // data, too. No need continue to undo if current data is equivalent to the before data snapshot Result<Boolean> beforeEqualsCurrentResult = DataCompareUtils.isRecordsEquals(beforeRecords, currentRecords); if (beforeEqualsCurrentResult.getResult()) { if (LOGGER.isInfoEnabled()) { LOGGER.info("Stop rollback because there is no data change " + "between the before data snapshot and the current data snapshot."); } // no need continue undo. return false; } else { if (LOGGER.isInfoEnabled()) { if (StringUtils.isNotBlank(afterEqualsCurrentResult.getErrMsg())) { LOGGER.info(afterEqualsCurrentResult.getErrMsg(), afterEqualsCurrentResult.getErrMsgParams()); } } if (LOGGER.isDebugEnabled()) { LOGGER.debug("check dirty data failed, old and new data are not equal, " + "tableName:[" + sqlUndoLog.getTableName() + "]," + "oldRows:[" + JSON.toJSONString(afterRecords.getRows()) + "]," + "newRows:[" + JSON.toJSONString(currentRecords.getRows()) + "]."); } throw new SQLException("Has dirty records when undo."); } } return true; }
记录行比较代码如下:
private static Result<Boolean> compareRows(TableMeta tableMetaData, List<Row> oldRows, List<Row> newRows) { // old row to map Map<String, Map<String, Field>> oldRowsMap = rowListToMap(oldRows, tableMetaData.getPrimaryKeyOnlyName()); // new row to map Map<String, Map<String, Field>> newRowsMap = rowListToMap(newRows, tableMetaData.getPrimaryKeyOnlyName()); // compare data for (Map.Entry<String, Map<String, Field>> oldEntry : oldRowsMap.entrySet()) { String key = oldEntry.getKey(); Map<String, Field> oldRow = oldEntry.getValue(); Map<String, Field> newRow = newRowsMap.get(key); if (newRow == null) { return Result.buildWithParams(false, "compare row failed, rowKey {}, reason [newRow is null]", key); } for (Map.Entry<String, Field> oldRowEntry : oldRow.entrySet()) { String fieldName = oldRowEntry.getKey(); Field oldField = oldRowEntry.getValue(); Field newField = newRow.get(fieldName); if (newField == null) { return Result.buildWithParams(false, "compare row failed, rowKey {}, fieldName {}, reason [newField is null]", key, fieldName); } Result<Boolean> oldEqualsNewFieldResult = isFieldEquals(oldField, newField); if (!oldEqualsNewFieldResult.getResult()) { return oldEqualsNewFieldResult; } } } return Result.ok(); }
如果将主键放到set后面一起更新会导致在记录行比较的时候key=主键_主键,两个主键连接自然取不到数据,出现newRow is null也就理所当然了,所以,归根结底这个问题导致的脏数据是因为你将主键放到set后面一起更新所导致的。
注意:
在使用对象的copyProperties是要ignoreProperties忽略掉一些不需要更新的对象,特别是把主键也copy到更新对象,导致set后面主键一起更新,这样容易引起seata的回滚异常。
发表评论
2022-05-28 16:31:36回复