apache / apache/incubator-seata

AT模式下,使用mybatis-plus的ASSIGN_ID类型主键,在回滚时出错

Open
#4,070 9 comments 0 reactions 0 assignees View on GitHub
task: help-wanted
Dominant language
Java
Stars
26k
Forks
8.8k
Avg merge
1d 8h
Merged PRs (30d)
4

Description

- [ ] I have searched the [issues](https://github.com/seata/seata/issues) of this repository and believe that this is not a duplicate.

### Ⅰ. Issue Description
seata使用的是最新develop分支代码,在AT模式下,使用mybatis-plus的ASSIGN_ID类型主键,在回滚时出错

### Ⅱ. Describe what happened

```
2021-10-12 19:33:24.990 [rpcDispatch_RMROLE_1_7_12] INFO io.seata.rm.datasource.undo.AbstractUndoExecutor:265 - compare row failed, rowKey 1447888055128473601_1447888055128473601, reason [newRow is null]
2021-10-12 19:33:24.992 [rpcDispatch_RMROLE_1_7_12] INFO io.seata.rm.datasource.DataSourceManager:43 - branchRollback failed. branchType:[AT], xid:[192.168.204.1:8091:4422721550536028208], branchId:[4422721550536028209], resourceId:[****************&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true], applicationData:[null]. reason:[Branch session rollback failed and try again later xid = 192.168.204.1:8091:4422721550536028208 branchId = 4422721550536028209 Has dirty records when undo.]
2021-10-12 19:33:24.992 [rpcDispatch_RMROLE_1_7_12] INFO io.seata.rm.AbstractRMHandler:131 - Branch Rollbacked result: PhaseTwo_RollbackFailed_Retryable
2021-10-12 19:33:25.997 [rpcDispatch_RMROLE_1_8_12] INFO i.s.c.r.processor.client.RmBranchRollbackProcessor:56 - rm handle branch rollback process:xid=192.168.204.1:8091:4422721550536028208,branchId=4422721550536028209,branchType=AT,resourceId=**********&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true,applicationData=null
2021-10-12 19:33:25.998 [rpcDispatch_RMROLE_1_8_12] INFO io.seata.rm.AbstractRMHandler:123 - Branch Rollbacking: 192.168.204.1:8091:4422721550536028208 4422721550536028209 ***************&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true

```

### Ⅲ. Describe what you expected to happen
跟踪源码发现在执行 BaseTransactionalExecutor 类的
```java
protected TableRecords buildTableRecords(Map> pkValuesMap) throws SQLException {
SQLInsertRecognizer recognizer = (SQLInsertRecognizer)sqlRecognizer;
List pkColumnNameList = getTableMeta().getPrimaryKeyOnlyName();
StringBuilder prefix = new StringBuilder("SELECT ");
StringBuilder suffix = new StringBuilder(" FROM ").append(getFromTableInSQL());
// build check sql
String firstKey = pkValuesMap.keySet().stream().findFirst().get();
int rowSize = pkValuesMap.get(firstKey).size();
suffix.append(WHERE).append(SqlGenerateUtils.buildWhereConditionByPKs(pkColumnNameList, rowSize, getDbType()));
StringJoiner selectSQLJoin = new StringJoiner(", ", prefix.toString(), suffix.toString());
List insertColumns = recognizer.getInsertColumns();
if (ONLY_CARE_UPDATE_COLUMNS && CollectionUtils.isNotEmpty(insertColumns)) {
Set columns = new HashSet<>(recognizer.getInsertColumns());
//这句执行后会将主键 ID 放入集合columns中,此时columns中存在 id 和 ID 两个主键,导致后续在回滚比对的过程中 主键的取值会变成 “主键值_主键值”
columns.addAll(pkColumnNameList);
for (String columnName : columns) {
selectSQLJoin.add(columnName);
}
} else {
selectSQLJoin.add(" * ");
}
ResultSet rs = null;
try (PreparedStatement ps = statementProxy.getConnection().prepareStatement(selectSQLJoin.toString())) {

int paramIndex = 1;
for (int r = 0; r < rowSize; r++) {
for (int c = 0; c < pkColumnNameList.size(); c++) {
List pkColumnValueList = pkValuesMap.get(pkColumnNameList.get(c));
int dataType = tableMeta.getColumnMeta(pkColumnNameList.get(c)).getDataType();
ps.setObject(paramIndex, pkColumnValueList.get(r), dataType);
paramIndex++;
}
}
rs = ps.executeQuery();
return TableRecords.buildRecords(getTableMeta(), rs);
} finally {
IOUtil.close(rs);
}
}

```
出错原因是:
上述源码方法中 columns.addAll(pkColumnNameList); 这句执行后会将主键 ID 放入集合columns中,此时columns中存在 id 和 ID 两个主键(其实是同一个字段,只是大小写的区别),例如:
columns:
result= {HashSet@25379} size=8
0="role name"
1="create by"
2="update_time"
3="create_time"
4="role_code"
5="id"
6="ID"
7="update_by"

这会导致后续在回滚比对的过程中 DataCompareUtils类中rowListToMap方法会将主键的取值解析成 “主键值_主键值”导致比对不上,在DataCompareUtils类的compareRows方法中报 newRow is null 错误,也就是开头说的错误。
请问这个Bug还是我使用的有问题?

### Ⅳ. How to reproduce it (as minimally and precisely as possible)

1. xxx
2. xxx
3. xxx

### Ⅴ. Anything else we need to know?

### Ⅵ. Environment:

- JDK version :1.8
- Seata version: 1.5.0-SNAPSHOT
- OS :windows
- Others:

Contributor guide

Open the contributing guide

Research direction

Start with BaseTransactionalExecutor.buildTableRecords and trace how DataCompareUtils.rowListToMap and compareRows handle the selected primary-key columns. Reproduce an AT rollback using MyBatis-Plus ASSIGN_ID with the case-variant primary-key names shown in the report; done means the rollback comparison no longer creates a duplicated row key and the branch rollback succeeds.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
databases, distributed-systems
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.