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

  1. Grant the connector user the LOCK TABLES privilege: GRANT LOCK TABLES ON dbname.* TO 'user'@'%';
  2. Or switch snapshot.mode to 'schema_only' / 'never' to skip the locking snapshot.
  3. Or use snapshot.locking.minimal (default) so locks are only held briefly / not required when binlog position can be obtained without full lock.
  4. 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

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


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