alibaba/canal · error · RuntimeException
没有更新复合主键的RowKey
Error message
没有更新复合主键的RowKey
What it means
HbaseSyncService.deleteAndInsert() handles UPDATEs that modify a composite rowKey column. It first checks whether ANY composite rowKey column appears in the updated ('old') columns; if none of the rowKey columns were updated, the method cannot have been entered correctly and throws '没有更新复合主键的RowKey' (no composite rowKey column was updated).
Source
Thrown at client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java:332
String[] rowKeyColumns = hbaseMapping.getRowKey().trim().split(",");
int index = 0;
int i = 1;
boolean complete = false;
Set<byte[]> rowKeys = new HashSet<>();
List<HRow> rows = new ArrayList<>();
for (Map<String, Object> r : data) {
// 拼接老的rowKey
List<String> updateSubRowKey = new ArrayList<>();
for (String rowKeyColumnName : rowKeyColumns) {
for (String updateColumn : old.get(index).keySet()) {
if (rowKeyColumnName.equalsIgnoreCase(updateColumn)) {
updateSubRowKey.add(rowKeyColumnName);
}
}
}
if (updateSubRowKey.isEmpty()) {
throw new RuntimeException("没有更新复合主键的RowKey");
}
StringBuilder oldRowKey = new StringBuilder();
StringBuilder newRowKey = new StringBuilder();
for (String rowKeyColumnName : rowKeyColumns) {
newRowKey.append(r.get(rowKeyColumnName).toString()).append("|");
if (!updateSubRowKey.contains(rowKeyColumnName)) {
// 从data取
oldRowKey.append(r.get(rowKeyColumnName).toString()).append("|");
} else {
// 从old取
oldRowKey.append(old.get(index).get(rowKeyColumnName).toString()).append("|");
}
}
int len = newRowKey.length();
newRowKey.delete(len - 1, len);
len = oldRowKey.length();
oldRowKey.delete(len - 1, len);
byte[] newRowKeyBytes = Bytes.toBytes(newRowKey.toString());View on GitHub (pinned to 87be50e876)
Solutions
- Verify the composite rowKey column names in the mapping exactly match (case-insensitively) the column names arriving in the DML.
- Remove stale/renamed columns from the rowKey list.
- Confirm the update genuinely changed a composite-key column; if not, the regular update() path should handle it.
- Re-test the mapping with a known update on one key column.
Example fix
# before (rowKey col name typo) hbaseMapping: rowKey: user_id,ts # DML columns are 'userId','ts' # after (match actual column names) hbaseMapping: rowKey: userId,ts
Defensive patterns
Strategy: validation
Validate before calling
// Verify composite rowKey column names match actual DML column names
String[] rowKeyCols = hbaseMapping.getRowKey().split(",");
Set<String> dmlCols = dml.getData().get(0).keySet();
for (String c : rowKeyCols) {
boolean found = dmlCols.stream().anyMatch(d -> d.equalsIgnoreCase(c.trim()));
if (!found) throw new IllegalStateException("rowKey col '" + c + "' not present in DML columns " + dmlCols);
} Try / catch
try {
// invoked internally from update() on composite-key change
} catch (RuntimeException e) {
if (e.getMessage() != null && e.getMessage().contains("复合主键")) {
logger.error("Composite rowKey column names do not match DML columns; fix mapping");
}
throw e;
} Prevention
- Make composite rowKey column names match DML column names (case-insensitive).
- Remove stale columns from the rowKey list after schema renames.
- Test composite-key updates against the mapping in a staging environment.
When it happens
Trigger: deleteAndInsert is invoked (because an update path detected a composite-key column change) but the intersection of the updated column set and the rowKey column list is empty — logically the row did not actually change a key column, yet the delete-insert branch ran.
Common situations: Composite rowKey column names in the mapping do not case-match the column names in the DML old/data maps; a logic path mismatch; rowKey column list has a typo so equalsIgnoreCase never matches.
Related errors
AI-assisted analysis of alibaba/canal@87be50e876 (2026-08-14).
Data as JSON: /api/errors/a8d1abdd22ca2289.
Report an issue: GitHub.