apache/seatunnel · error · DebeziumException
User does not have the 'LOCK TABLES' privilege required to o
Error message
User does not have the 'LOCK TABLES' privilege required to obtain a consistent snapshot by preventing concurrent writes to tables.
What it means
When the MySQL connector cannot use a consistent global read lock (MySqlConnector's minimal blocking strategy falls back to table-level locking), it needs to LOCK TABLES on every table being snapshotted so concurrent writes cannot corrupt snapshot consistency. Before doing so it checks the account privileges; if the MySQL user lacks the LOCK TABLES privilege it throws this DebeziumException instead of silently producing an inconsistent snapshot.
Source
Thrown at seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/io/debezium/connector/mysql/MySqlSnapshotChangeEventSource.java:566
"Writes to MySQL tables prevented for a total of {}",
Strings.duration(lockReleased - globalLockAcquiredAt));
globalLockAcquiredAt = -1;
}
private void tableLock(
RelationalSnapshotContext<MySqlPartition, MySqlOffsetContext> snapshotContext)
throws SQLException {
// ------------------------------------
// LOCK TABLES and READ BINLOG POSITION
// ------------------------------------
// We were not able to acquire the global read lock, so instead we have to obtain a read
// lock on each table.
// This requires different privileges than normal, and also means we can't unlock the tables
// without
// implicitly committing our transaction ...
if (!connection.userHasPrivileges("LOCK TABLES")) {
// We don't have the right privileges
throw new DebeziumException(
"User does not have the 'LOCK TABLES' privilege required to obtain a "
+ "consistent snapshot by preventing concurrent writes to tables.");
}
// We have the required privileges, so try to lock all of the tables we're interested in ...
LOGGER.info(
"Flush and obtain read lock for {} tables (preventing writes)",
snapshotContext.capturedTables);
if (!snapshotContext.capturedTables.isEmpty()) {
final String tableList =
snapshotContext.capturedTables.stream()
.map(tid -> quote(tid))
.collect(Collectors.joining(","));
connection.executeWithoutCommitting("FLUSH TABLES " + tableList + " WITH READ LOCK");
}
tableLockAcquiredAt = clock.currentTimeInMillis();
metrics.globalLockAcquired();
}
View on GitHub (pinned to cf67b549a7)
Solutions
- Grant the CDC user the privilege: `GRANT LOCK TABLES ON *.* TO 'user'@'host';` then FLUSH PRIVILEGES and restart the connector.
- If a consistent snapshot is not required for your setup, set `snapshot.locking.mode=none` (only safe when no concurrent writes occur or snapshot happens on a quiesced replica).
- Run the snapshot against a read replica where writes are blocked and minimal locking can be avoided.
- Verify with `SHOW GRANTS FOR 'user'@'host';` that LOCK TABLES is actually present for the exact host the connector connects from.
Example fix
// before (connector user created without locking rights) CREATE USER 'st_cdc'@'%' IDENTIFIED BY '***'; GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'st_cdc'@'%'; // after CREATE USER 'st_cdc'@'%' IDENTIFIED BY '***'; GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT, LOCK TABLES ON *.* TO 'st_cdc'@'%'; FLUSH PRIVILEGES;
Defensive patterns
Strategy: validation
Validate before calling
// validate CDC user privileges before starting the connector
try (Connection c = DriverManager.getConnection(url, user, pass);
Statement s = c.createStatement();
ResultSet rs = s.executeQuery("SHOW GRANTS FOR CURRENT_USER()")) {
boolean hasLock = false;
while (rs.next()) {
if (rs.getString(1).toUpperCase().contains("LOCK TABLES")) { hasLock = true; }
}
if (!hasLock) throw new IllegalStateException("CDC user lacks LOCK TABLES privilege");
} Try / catch
try {
startConnector(config);
} catch (DebeziumException e) {
if (e.getMessage().contains("'LOCK TABLES' privilege")) {
// remediate: GRANT LOCK TABLES ON *.* TO user@host, or set snapshot.locking.mode=none
} else throw e;
} Prevention
- Standardize a CDC provisioning script that grants SELECT, REPLICATION SLAVE, REPLICATION CLIENT and LOCK TABLES
- Run `SHOW GRANTS` in pre-deployment checks
- On managed MySQL, confirm the cloud platform permits global LOCK TABLES grant
- Prefer snapshot.locking.mode=none on read replicas where writes are already blocked
When it happens
Trigger: snapshot.locking.mode is not 'none' and the configured MySQL user fails connection.userHasPrivileges("LOCK TABLES") during the tableLock phase of the snapshot, i.e. the account was never granted LOCK TABLES (a global/static privilege grantable only at global level).
Common situations: Using a least-privilege CDC account with only SELECT/REPLICATION privileges; cloud-managed MySQL where the DBA did not grant LOCK TABLES; connector configs that disable minimal blocking so the table-lock path runs (e.g. explicit execute.blocking.thread or minimal blocked without privileges for FLUSH TABLES WITH READ LOCK fallback).
Understand the failure class
Background: "You do not have permission" / 403 Forbidden errors: authenticated but not allowed — causes and fixes across open-source libraries — this error's family across 31 libraries.
Related errors
- User does not have the 'LOCK TABLES' privilege required to o
- Tables were locked explicitly, but to get a consistent snaps
- Unexpected error while connecting to MySQL and looking at pr
- Cannot read the binlog filename and position via '<showMaste
- Interrupted while emitting initial DROP TABLE events
AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10).
Data as JSON: /api/errors/e7714f761df5173d.
Report an issue: GitHub.