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).

Solutions

  1. Verify the composite rowKey column names in the mapping exactly match (case-insensitively) the column names arriving in the DML.
  2. Remove stale/renamed columns from the rowKey list.
  3. Confirm the update genuinely changed a composite-key column; if not, the regular update() path should handle it.
  4. 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

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.

Appendix: 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)