MyCATApache/Mycat-Server · error · SQLNonTransientException

"ChildTable multi insert not provided"

Error message

"ChildTable multi insert not provided"

What it means

Mycat's child-table (ER partition) insert routing only supports single-row inserts. When parserChildTable detects a multi-row VALUES clause (isMultiInsert), it cannot distribute individual rows per parent lookup and throws SQLNonTransientException.

Solutions

  1. Split the multi-row insert into separate single-row INSERT statements, one per child row
  2. If the table does not truly need ER partitioning, remove isChildTable/joinKey config and use normal sharding (then batch inserts go through parserBatchInsert)
  3. Perform the batch insert directly on backend nodes when the parent node is known
  4. Wrap rows in a transaction of individual inserts to keep atomicity

Example fix

// before
INSERT INTO order_detail (id, order_id, item) VALUES (1,100,'a'),(2,100,'b');
// after
INSERT INTO order_detail (id, order_id, item) VALUES (1,100,'a');
INSERT INTO order_detail (id, order_id, item) VALUES (2,100,'b');
Defensive patterns

Strategy: validation

Validate before calling

// refuse multi-row inserts into child tables before routing
if (extractInsertColumns(sql) != null && countValuesTuples(sql) > 1 && tableConfig.isChildTable()) {
    throw new IllegalArgumentException("Child table inserts must be single-row; split the batch");
}

Type guard

static boolean isSingleRowInsert(MySqlInsertStatement ins) {
    return ins.getValuesList() == null || ins.getValuesList().size() <= 1;
}

Try / catch

try {
    insertBatch(rows);
} catch (SQLNonTransientException e) {
    if ("ChildTable multi insert not provided".equals(e.getMessage())) {
        // fall back to per-row inserts inside a transaction
        rows.forEach(this::insertSingle);
        return;
    }
    throw e;
}

Prevention

When it happens

Trigger: `INSERT INTO child_table (..., joinKey, ...) VALUES (...), (...), ...` — a batch/multi-row insert — into a table marked as childTable in schema.xml.

Common situations: ORM batch flush (Hibernate/JDBC batch rewriting as multi-values); bulk data seeding scripts copying many child rows at once; MyBatis foreach-generated multi-row inserts.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of MyCATApache/Mycat-Server@65f8d8beb7 (2026-09-11). Data as JSON: /api/errors/a817f49ff411e56c. Report an issue: GitHub.

Appendix: source

Thrown at src/main/java/io/mycat/route/parser/druid/impl/DruidInsertParser.java:133

	private boolean isMultiInsert(MySqlInsertStatement insertStmt) {
		return (insertStmt.getValuesList() != null && insertStmt.getValuesList().size() > 1) || insertStmt.getQuery() != null;
	}
	
	private RouteResultset parserChildTable(SchemaConfig schema, RouteResultset rrs,
			String tableName, MySqlInsertStatement insertStmt) throws SQLNonTransientException {
		TableConfig tc = schema.getTables().get(tableName);
		
		String joinKey = tc.getJoinKey();
		int joinKeyIndex = getJoinKeyIndex(insertStmt.getColumns(), joinKey);
		if(joinKeyIndex == -1) {
			String inf = "joinKey not provided :" + tc.getJoinKey()+ "," + insertStmt;
			LOGGER.warn(inf);
			throw new SQLNonTransientException(inf);
		}
		if(isMultiInsert(insertStmt)) {
			String msg = "ChildTable multi insert not provided" ;
			LOGGER.warn(msg);
			throw new SQLNonTransientException(msg);
		}
		
		String joinKeyVal = insertStmt.getValues().getValues().get(joinKeyIndex).toString();

		
		String sql = insertStmt.toString();
		
		// try to route by ER parent partion key
		RouteResultset theRrs = RouterUtil.routeByERParentKey(null,schema, ServerParse.INSERT,sql, rrs, tc,joinKeyVal);
		if (theRrs != null) {
			rrs.setFinishedRoute(true);
			return theRrs;
		}

		// route by sql query root parent's datanode
		String findRootTBSql = tc.getLocateRTableKeySql().toLowerCase() + joinKeyVal;
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("find root parent's node sql "+ findRootTBSql);

View on GitHub (pinned to 65f8d8beb7)