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

  1. Grant the CDC user the privilege: `GRANT LOCK TABLES ON *.* TO 'user'@'host';` then FLUSH PRIVILEGES and restart the connector.
  2. 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).
  3. Run the snapshot against a read replica where writes are blocked and minimal locking can be avoided.
  4. 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

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


AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10). Data as JSON: /api/errors/e7714f761df5173d. Report an issue: GitHub.