apache/seatunnel · error · ConnectException
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
SnapshotReader.execute performs a consistent snapshot by locking all captured tables with FLUSH TABLES WITH READ LOCK semantics, which requires the LOCK TABLES privilege. Before locking, it checks connectionContext.userHasPrivileges("LOCK TABLES") and throws a ConnectException if the MySQL user lacks it, to avoid an inconsistent snapshot under concurrent writes.
Source
Thrown at seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/io/debezium/connector/mysql/legacy/SnapshotReader.java:445
readableDatabaseNames.stream()
.filter(filters.databaseFilter())
.collect(Collectors.toSet());
logger.info("\tsnapshot continuing with database(s): {}", includedDatabaseNames);
if (!isLocked) {
if (!snapshotLockingMode.equals(
MySqlConnectorConfig.SnapshotLockingMode.NONE)) {
// ------------------------------------
// 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 (!connectionContext.userHasPrivileges("LOCK TABLES")) {
// We don't have the right privileges
throw new ConnectException(
"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(
"Step {}: flush and obtain read lock for {} tables (preventing writes)",
step++,
knownTableIds.size());
lockedTables = new HashSet<>(capturedTableIds);
String tableList =
capturedTableIds.stream()
.map(tid -> quote(tid))
.reduce((r, element) -> r + "," + element)
.orElse(null);
if (tableList != null) {
sql.set("FLUSH TABLES " + tableList + " WITH READ LOCK");
mysql.executeWithoutCommitting(sql.get());View on GitHub (pinned to cf67b549a7)
Solutions
- Grant the connector user the LOCK TABLES privilege: GRANT LOCK TABLES ON dbname.* TO 'user'@'%';
- Or switch snapshot.mode to 'schema_only' / 'never' to skip the locking snapshot.
- Or use snapshot.locking.minimal (default) so locks are only held briefly / not required when binlog position can be obtained without full lock.
- Ask the DBA to provision a dedicated CDC user with SELECT, RELOAD, LOCK TABLES, REPLICATION SLAVE, REPLICATION CLIENT.
Example fix
// before GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'flinkcdc'@'%'; // after GRANT SELECT, RELOAD, LOCK TABLES, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'flinkcdc'@'%';
Defensive patterns
Strategy: validation
Validate before calling
// Verify privilege before snapshot
try (Statement s = conn.createStatement()) {
ResultSet rs = s.executeQuery("SHOW GRANTS FOR CURRENT_USER()");
boolean hasLock = false;
while (rs.next()) { if (rs.getString(1).contains("LOCK TABLES") || rs.getString(1).contains("ALL PRIVILEGES")) hasLock = true; }
if (!hasLock) throw new IllegalStateException("Grant LOCK TABLES to the CDC user");
} Try / catch
try { snapshot(); } catch (ConnectException e) { if (e.getMessage().contains("LOCK TABLES")) { /* switch to schema_only or grant privilege */ } throw e; } Prevention
- Include LOCK TABLES in the standard CDC user GRANT playbook.
- On RDS/managed MySQL, confirm which grants the platform allows before planning snapshots.
- Prefer snapshot.locking.minimal to reduce lock requirements.
When it happens
Trigger: Running a snapshot (doStart -> execute) in default (locking) snapshot mode while the configured MySQL user was not granted LOCK TABLES, so userHasPrivileges returns false.
Common situations: Cloud-managed MySQL (RDS/Aurora) accounts with limited GRANTs; DBAs granting only REPLICATION SLAVE/CLIENT and SELECT; shared accounts with least-privilege policies.
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
- 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
- Unexpected error while connecting to MySQL and looking at pr
AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10).
Data as JSON: /api/errors/41a74d816209d4ff.
Report an issue: GitHub.