{"record":{"id":"41a74d816209d4ff","repo":"apache/seatunnel","slug":"user-does-not-have-the-lock-tables-privilege-req-41a74d","errorCode":null,"errorMessage":"User does not have the 'LOCK TABLES' privilege required to obtain a consistent snapshot by preventing concurrent writes to tables.","messagePattern":"User does not have the 'LOCK TABLES' privilege required to obtain a consistent snapshot by preventing concurrent writes to tables\\.","errorType":"exception","errorClass":"ConnectException","httpStatus":null,"severity":"error","filePath":"seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/io/debezium/connector/mysql/legacy/SnapshotReader.java","lineNumber":445,"sourceCode":"                        readableDatabaseNames.stream()\n                                .filter(filters.databaseFilter())\n                                .collect(Collectors.toSet());\n                logger.info(\"\\tsnapshot continuing with database(s): {}\", includedDatabaseNames);\n\n                if (!isLocked) {\n                    if (!snapshotLockingMode.equals(\n                            MySqlConnectorConfig.SnapshotLockingMode.NONE)) {\n                        // ------------------------------------\n                        // LOCK TABLES and READ BINLOG POSITION\n                        // ------------------------------------\n                        // We were not able to acquire the global read lock, so instead we have to\n                        // obtain a read lock on each table.\n                        // This requires different privileges than normal, and also means we can't\n                        // unlock the tables without\n                        // implicitly committing our transaction ...\n                        if (!connectionContext.userHasPrivileges(\"LOCK TABLES\")) {\n                            // We don't have the right privileges\n                            throw new ConnectException(\n                                    \"User does not have the 'LOCK TABLES' privilege required to obtain a \"\n                                            + \"consistent snapshot by preventing concurrent writes to tables.\");\n                        }\n                        // We have the required privileges, so try to lock all of the tables we're\n                        // interested in ...\n                        logger.info(\n                                \"Step {}: flush and obtain read lock for {} tables (preventing writes)\",\n                                step++,\n                                knownTableIds.size());\n                        lockedTables = new HashSet<>(capturedTableIds);\n                        String tableList =\n                                capturedTableIds.stream()\n                                        .map(tid -> quote(tid))\n                                        .reduce((r, element) -> r + \",\" + element)\n                                        .orElse(null);\n                        if (tableList != null) {\n                            sql.set(\"FLUSH TABLES \" + tableList + \" WITH READ LOCK\");\n                            mysql.executeWithoutCommitting(sql.get());","sourceCodeStart":427,"sourceCodeEnd":463,"githubUrl":"https://github.com/apache/seatunnel/blob/cf67b549a7a6c35fa0beb12d83c62892427ea919/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/io/debezium/connector/mysql/legacy/SnapshotReader.java#L427-L463","documentation":"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.","triggerScenarios":"Running a snapshot (doStart -> execute) in default (locking) snapshot mode while the configured MySQL user was not granted LOCK TABLES, so userHasPrivileges returns false.","commonSituations":"Cloud-managed MySQL (RDS/Aurora) accounts with limited GRANTs; DBAs granting only REPLICATION SLAVE/CLIENT and SELECT; shared accounts with least-privilege policies.","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."],"exampleFix":"// before\nGRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'flinkcdc'@'%';\n// after\nGRANT SELECT, RELOAD, LOCK TABLES, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'flinkcdc'@'%';","handlingStrategy":"validation","validationCode":"// Verify privilege before snapshot\ntry (Statement s = conn.createStatement()) {\n    ResultSet rs = s.executeQuery(\"SHOW GRANTS FOR CURRENT_USER()\");\n    boolean hasLock = false;\n    while (rs.next()) { if (rs.getString(1).contains(\"LOCK TABLES\") || rs.getString(1).contains(\"ALL PRIVILEGES\")) hasLock = true; }\n    if (!hasLock) throw new IllegalStateException(\"Grant LOCK TABLES to the CDC user\");\n}","typeGuard":null,"tryCatchPattern":"try { snapshot(); } catch (ConnectException e) { if (e.getMessage().contains(\"LOCK TABLES\")) { /* switch to schema_only or grant privilege */ } throw e; }","preventionTips":["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."],"tags":["mysql","privileges","snapshot","cdc"],"backgroundTag":"insufficient-permissions","analyzedSha":"cf67b549a7a6c35fa0beb12d83c62892427ea919","analyzedAt":"2026-09-10T21:44:55.265Z","contentChangedAt":"2026-09-10T21:44:55.265Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}