MyCATApache/Mycat-Server · error
ER_USER_READ_ONLY
ER_USER_READ_ONLY
Error message
User readonly
What it means
When a statement falls into the default case of ServerQueryHandler.query (i.e. a write or otherwise non-handled statement type) and the current user is configured as read-only (readOnly flag on the front-end connection), MyCat rejects it with ER_USER_READ_ONLY and message 'User readonly'.
Solutions
- Use a non-read-only MyCat user for write operations.
- If writes are legitimate, set readOnly=false (or remove readOnly) for that user in server.xml and reconnect.
- Check application connection config points at the correct user profile.
Example fix
// before (server.xml) <user name="app"><property name="readOnly">true</property>...</user> // after <user name="app"><property name="readOnly">false</property>...</user>
Defensive patterns
Strategy: validation
Validate before calling
// Check statement type against user's read-only profile before executing
boolean isWrite = matchesAny(sql, "INSERT", "UPDATE", "DELETE", "CREATE", "DROP", "ALTER", "TRUNCATE");
if (isWrite && currentUserReadOnly) {
throw new SecurityException("user is read-only; refuse write: " + sql);
} Try / catch
try {
stmt.execute(sql);
} catch (SQLException e) {
if (e.getErrorCode() == 1820 /* ER_USER_READ_ONLY */) {
throw new SecurityException("Switch to a writable MyCat user for this operation", e);
}
throw e;
} Prevention
- Separate read-only and write connection pools per user
- Review server.xml readOnly flags in deployment checklists
- Run migrations with an account that has write privileges
When it happens
Trigger: A user marked read-only (readOnly=true in server.xml user config) issues INSERT/UPDATE/DELETE/DDL or any statement reaching the default branch.
Common situations: Reporting/analytics user accidentally used for writes; app deployed with wrong credentials; DDL migrations run under a read-only account.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- rehashNodeDir must be writable
- rename file failed for upload file
- rename old file failed for upload file
AI-assisted analysis of MyCATApache/Mycat-Server@65f8d8beb7 (2026-09-11).
Data as JSON: /api/errors/a21abfb516ae64a2.
Report an issue: GitHub.
Appendix: source
Thrown at src/main/java/io/mycat/server/ServerQueryHandler.java:165
e.printStackTrace();
String msg = "Mycat is not connected to zookeeper!!\n";
msg += "Please start zookeeper and restart mycat so that this mycat can temporarily execute the migration command.If other mycat does not connect to this zookeeper, they will not be able to perceive changes in the migration task.\n";
msg += "After starting zookeeper,you can command tas follow:\n\nmigrate -table=schema.test -add=dn2,dn3 -force=true\n\nto perform the migration.\n";
LOGGER.error(e.getMessage());
LOGGER.error(msg);
c.writeErrMessage(ErrorCode.ER_UNKNOWN_ERROR, msg);
}
break;
}
case ServerParse.LOCK:
c.lockTable(sql);
break;
case ServerParse.UNLOCK:
c.unLockTable(sql);
break;
default:
if(readOnly){
LOGGER.warn(new StringBuilder().append("User readonly:").append(sql).toString());
c.writeErrMessage(ErrorCode.ER_USER_READ_ONLY, "User readonly");
break;
}
c.execute(sql, rs & 0xff);
}
switch (sqlType) {
case ServerParse.SELECT:
case ServerParse.DELETE:
case ServerParse.UPDATE:
case ServerParse.INSERT:
case ServerParse.COMMAND:
// curd 在后面会更新
break;
default:
c.setExecuteSql(null);
}
}View on GitHub (pinned to 65f8d8beb7)