apache/shardingsphere · error · MCPLockingReadStatementException
Locking read statements such as SELECT ... FOR UPDATE are no
Error message
Locking read statements such as SELECT ... FOR UPDATE are not supported by the MCP read-only contract.
What it means
Thrown by SQLStatementSafetyValidator.checkStatement (MCPLockingReadStatementException) when a SelectStatement has a lock clause. SELECT ... FOR UPDATE / FOR SHARE / LOCK IN SHARE MODE takes row locks, which conflicts with the MCP read-only contract, so the validator rejects it with a distinct, more specific error than a generic ban.
Source
Thrown at mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementSafetyValidator.java:91
String text = each.getText().trim();
if (text.startsWith("/*!") || text.toUpperCase(Locale.ENGLISH).startsWith("/*M!")) {
return true;
}
}
return false;
}
private void checkStatement(final SQLStatement sqlStatement) {
if (isBannedStatementType(sqlStatement) || containsExecutableComment(sqlStatement)) {
throw new MCPBannedSQLStatementException();
}
if (sqlStatement instanceof SelectStatement) {
SelectStatement select = (SelectStatement) sqlStatement;
if (select.getInto().isPresent() || select.getOutfile().isPresent()) {
throw new MCPBannedSQLStatementException();
}
if (select.getLock().isPresent()) {
throw new MCPLockingReadStatementException();
}
}
}
private void checkExpression(final ExpressionSegment expression) {
if (expression instanceof FunctionSegment) {
checkFunction((FunctionSegment) expression);
} else if (expression instanceof BinaryOperationExpression && ":=".equals(((BinaryOperationExpression) expression).getOperator())) {
throw new MCPBannedSQLStatementException();
} else if (expression instanceof ColumnSegment) {
ColumnSegment column = (ColumnSegment) expression;
if (column.getOwner().isPresent() && "NEXTVAL".equalsIgnoreCase(column.getIdentifier().getValue())) {
throw new MCPBannedSQLStatementException();
}
}
}
private void checkFunction(final FunctionSegment function) {View on GitHub (pinned to e952770a21)
Solutions
- Drop the lock clause and rely on the tool's read-only semantics for inspection
- If you need atomic claim/update behavior, move it to an application-side transaction over a normal JDBC connection, or use a transaction-capable MCP path explicitly designed for it
- Add optimistic concurrency (version column) instead of pessimistic locking for MCP-driven workflows
Example fix
-- before SELECT * FROM t_order WHERE status='NEW' LIMIT 1 FOR UPDATE; -- after SELECT * FROM t_order WHERE status='NEW' LIMIT 1;
Defensive patterns
Strategy: validation
Validate before calling
String upper = sql.toUpperCase(Locale.ENGLISH);
if (upper.endsWith("FOR UPDATE") || upper.endsWith("FOR SHARE") || upper.endsWith("LOCK IN SHARE MODE")
|| upper.matches("(?s).*\\bFOR (NO KEY )?(UPDATE|SHARE)\\b.*")) {
throw new IllegalArgumentException("Remove lock clauses for MCP read-only access");
} Try / catch
try {
executeSql(sql);
} catch (MCPLockingReadStatementException e) {
executeSql(stripLockClause(sql)); // retry without FOR UPDATE/SHARE
} Prevention
- Map ORM pessimistic-lock queries away from the MCP path
- Prefer optimistic concurrency for read-modify-write workflows
When it happens
Trigger: select.getLock().isPresent() on a parsed SelectStatement — any trailing lock clause such as `SELECT * FROM t WHERE id=1 FOR UPDATE`, `FOR SHARE`, `FOR NO KEY UPDATE`, or MySQL `LOCK IN SHARE MODE`.
Common situations: Reusing ORM-generated pessimistic-lock queries (Hibernate/MyBatis `FOR UPDATE` mappings) through the MCP execute tool; porting application transaction code that reserves rows; agents emulating 'claim next job' patterns via a locked read.
Related errors
- Metadata introspection SQL should use MCP metadata resources
- Statement is not a transaction command.
- Statement is not supported by the MCP contract.
- Only one SQL statement is allowed.
- %s execution_mode is required.
AI-assisted analysis of apache/shardingsphere@e952770a21 (2026-08-14).
Data as JSON: /api/errors/98a50db90f738ef4.
Report an issue: GitHub.