S4 on MySQL: the DML-only failure holds, but the fix is *privileges*, not ownership
- Upgrade
- 26.0.0 → 26.7.1
- Scale
- 1,000 users, 2 realms
- Database
- MySQL 8.4 (LTS), single node, container mysql:8.4
- Topology
- single container
- Host
- laptop (Docker 29)
- Condition
- DB user without DDL rights
- Elapsed
- run A failed at 13s; run B ready in ~15s (Liquibase + realm migrators ~5s)
Summary
This is the first run against an engine other than Postgres, and it closes the ☐ "holds on MySQL/MariaDB/Oracle" row in S4. Two findings, one expected and one that changes what we tell customers:
-
A DML-only account fails the migration on MySQL, same as Postgres — so the failure mode generalises. It fails even earlier on MySQL than on Postgres: before any Keycloak changeset runs, at Liquibase's own changelog-table setup (
ALTER TABLE DATABASECHANGELOG ADD PRIMARY KEY (ID, AUTHOR, FILENAME)), not at the first Keycloak DDL changeset. -
The fix is different. On Postgres the S4 record found that granting DDL is not enough —
DROP INDEXrequires ownership, which noGRANTconfers. MySQL has no ownership concept: granting the DDL privileges (CREATE, ALTER, DROP, INDEX, REFERENCES, CREATE VIEW, SHOW VIEWonkeycloak.*) makes the same migration succeed cleanly. The pre-flight question is therefore engine-specific: on Postgres, "does the account own the objects"; on MySQL, "does the account hold the DDL privileges".
Environment
| Field | Value |
|---|---|
| Keycloak from → to | 26.0.0 → 26.7.1 |
| Distribution / start mode | quay.io/keycloak/keycloak official image, start w/ external DB |
| Database | MySQL 8.4 (LTS), single node, container mysql:8.4 |
| DB flags | sql_generate_invisible_primary_key=OFF (8.4 enables it by default and it breaks Keycloak migrations with error 1068), utf8mb4 / utf8mb4_unicode_ci |
| Adverse scenario | S4 — the account Keycloak connects as is the variable |
| Dataset scale | 1,000 users, 2 realms |
| Seeding method | partialImport (bin/seed-realm.sh --profile typical) |
| Topology | single container |
| Host | laptop (Docker 29) |
| JVM heap | -Xms1g -Xmx4g |
| Elapsed clock | run A failed at 13s; run B ready in ~15s (Liquibase + realm migrators ~5s) |
Procedure
The whole run needed a MySQL toolchain first. Then:
# run A — DML only
CREATE USER 'kc_dml'@'%' IDENTIFIED BY 'kc_dml';
GRANT SELECT, INSERT, UPDATE, DELETE ON keycloak.* TO 'kc_dml'@'%';
# .env: KC_DB=mysql, COMPOSE_FILE=docker-compose.yml:docker-compose.mysql.yml,
# KC_DB_USERNAME=kc_dml, KC_DB_PASSWORD=kc_dml
./bin/upgrade.sh 26.7.1 # FAILED, 13s
# run B — DML + DDL (no ownership concept to add)
GRANT CREATE, ALTER, DROP, INDEX, REFERENCES, CREATE VIEW, SHOW VIEW
ON keycloak.* TO 'kc_dml'@'%';
./bin/restore.sh baseline-26.0.0-mysql-typical-1k
./bin/upgrade.sh 26.7.1 # READY
Results
| Run | Account's rights | Outcome | Schema after | Users |
|---|---|---|---|---|
| A | DML only (SELECT, INSERT, UPDATE, DELETE) | exit 1, refused to start | model 26.0.0, changelog untouched | 1,000 |
| B | DML + DDL (CREATE, ALTER, DROP, INDEX, REFERENCES, CREATE VIEW, SHOW VIEW) | READY, clean | model 26.7.1, index audit 121/121 | 1,000 |
Run A failure — earlier than Postgres, and not a Keycloak changeset
ERROR: liquibase.exception.ChangeLogParseException:
liquibase.exception.SetupException: liquibase.exception.DatabaseException:
ALTER command denied to user 'kc_dml'@'172.19.0.3' for table 'DATABASECHANGELOG'
[Failed SQL: (1142) ALTER TABLE keycloak.DATABASECHANGELOG ADD PRIMARY KEY (ID, AUTHOR, FILENAME)]
Contrast with Postgres S4, where the first failure was a real Keycloak changeset
(20.0.0-12964-supported-dbs-edb-migration, DROP INDEX IDX_GROUP_ATT_BY_NAME_VALUE
→ "must be owner of index"). On MySQL, Liquibase 26.7.1 wants a primary key on its
own DATABASECHANGELOG table that the 26.0.0-era Liquibase did not create, and it
tries to add it before touching any Keycloak changeset. A DML-only account is
therefore rejected at the door, not mid-migration.
Run B — DDL privileges are sufficient
Granting the DDL set (still no ownership concept — MySQL has none) let the same
migration run to completion: Updating database → realm migrators 26.1.0 → 26.7.0
(~5s), migration_model 26.7.1, index audit 121 correct / 0 missing / 0 wrong
shape, all 1,000 users intact.
Verification
| Claim | Evidence |
|---|---|
| DML-only account fails on MySQL | run A log above, ☑ reproduced once |
| DDL-privileged account succeeds on MySQL | run B migration_model 26.7.1 ☑ |
| Index audit clean after the MySQL migration | 121 correct / 0 missing / 0 wrong shape ☑ |
| Realm survives the MySQL upgrade | users/count = 1000 ☑ |
| Toolchain round-trips (snapshot/restore) on MySQL | restore.sh reported "schema 26.0.0, no re-migration", users 1000 ☑ |