TL;DR
MySQL, on the source server:
| |
PostgreSQL, on the source server:
| |
And in both cases:
| |
gcloud sql import sql takes plain SQL or .gz: the custom format of pg_dump goes through pg_restore (below).
The errors and where they come from
On Cloud SQL nobody has SUPER (MySQL) or is a superuser (PostgreSQL); the admin is root@% or postgres, a member of cloudsqlsuperuser. Everything a normal dump takes for granted on your own server breaks right there:
| Import error | Engine | Cause | Fix |
|---|---|---|---|
ERROR 1227 (42000) at line N: Access denied; you need (at least one of) the SUPER privilege(s) for this operation (on a CREATE … VIEW, TRIGGER, PROCEDURE, FUNCTION or EVENT line) | MySQL | DEFINER=`user`@`host` for a user that does not exist on the instance, or for root@localhost, which never exists on Cloud SQL. The dump does not carry users (and must not) | Create those users before importing, or strip the DEFINER= clauses with the sed |
The same ERROR 1227 in the first lines, at SET @@GLOBAL.GTID_PURGED=… | MySQL | The source has GTID enabled and mysqldump writes the position, which only SUPER can set | --set-gtid-purged=OFF |
ERROR 1449 (HY000): The user specified as a definer ('app'@'localhost') does not exist when using the view, not when importing | MySQL | You left a DEFINER for a user that does not exist on the instance | Strip the DEFINER or create the user with that exact host |
ERROR: must be owner of extension plpgsql | PostgreSQL | The dump’s COMMENT ON EXTENSION plpgsql: on Cloud SQL extensions belong to cloudsqladmin | Comment out the EXTENSION lines with the sed |
ERROR: role "app_owner" does not exist | PostgreSQL | ALTER … OWNER TO and GRANT … TO for roles from the old server | --no-owner --no-acl, or create the roles first |
ERROR: permission denied for schema public or must be owner of … halfway through | PostgreSQL | You imported with a --user that does not own what already exists in the database | Import as postgres into an empty database; reassign afterwards |
MySQL: what to do with DEFINER clauses
First see how many there are on the source, and whose:
| |
Three ways out, from most to least faithful to the original:
- Create the users on the instance before importing, with the same
user@host:gcloud sql users create app --host=% --instance=my-instance --password=…. This is what the documentation recommends, and it keeps views and procedures exactly as they were.root@localhostis the exception: it cannot be created on Cloud SQL, so aDEFINERwith that value must be changed no matter what. - Strip the
DEFINERwith thesedabove: objects get created with the importing user as definer. Enough when the source users will never exist on Cloud SQL. - Switch to
SQL SECURITY INVOKER(sed -E 's/SQL SECURITY DEFINER/SQL SECURITY INVOKER/g'on top of the previous one): the view or procedure runs with the caller’s privileges and the definer stops mattering. It changes the permission model; check that the application users can read the underlying tables.
The sed is deliberately broad because DEFINER= also shows up inside conditional comments (/*!50013 DEFINER=… SQL SECURITY DEFINER */); deleting just the DEFINER= leaves the rest intact and valid.
MySQL: the flags, one by one
| Flag | Why |
|---|---|
--hex-blob | Binary columns as hex; without it, a BLOB with odd bytes gets corrupted going through the character set |
--single-transaction | Consistent InnoDB snapshot without locking tables; a must in production |
--set-gtid-purged=OFF | Do not write SET @@GLOBAL.GTID_PURGED, which Cloud SQL will not let you run |
--default-character-set=utf8mb4 | Make the dump and the SET NAMES it carries match what is in the tables; avoids the classic é |
--databases app | Required per the documentation, and never with the mysql database. Adds CREATE DATABASE IF NOT EXISTS and USE app, so --database on import becomes optional |
--routines --triggers --events | mysqldump only includes triggers by default; procedures, functions and events must be asked for. All of them carry a DEFINER |
If the user you dump with lacks PROCESS, mysqldump 8.0.21+ fails right away with “Access denied; you need (at least one of) the PROCESS privilege(s)” while querying tablespaces: add --no-tablespaces.
PostgreSQL: plain or custom
--no-owner --no-acl make everything imported belong to --user, with no GRANT to phantom roles. If the application connects as another role, create the role first and use --user=app, or reassign afterwards:
| |
Create the extensions the database needs (pg_trgm, postgis, uuid-ossp…) before importing, as postgres, in the target database: CREATE EXTENSION IF NOT EXISTS pg_trgm;. Only the ones on Cloud SQL’s supported extensions list work; plpgsql is already there. With that in place, commenting out the dump’s CREATE EXTENSION lines removes nothing.
If you prefer the custom format (-Fc), the extension filter goes into the pg_restore table of contents, exactly as Google documents it, and you restore through the Cloud SQL Auth Proxy:
| |
-j 4 restores in parallel, which the import from the bucket does not do.
Traps
- The bucket role goes to the instance’s service account, not yours: the docs ask for
storage.objectAdmin(for import alonestorage.objects.getandlist, that isobjectViewer, are enough). Without it, “The service account does not have the required permissions for the bucket” even though you can read the object yourself. - One operation at a time per instance: with a backup or export running, the import fails with “operation already in progress”.
gcloud sql operations list --instance=my-instanceshows it. - Large dumps: the import is serial and slow; launch it with
--asyncand watch it withgcloud sql operations wait OPERATION_ID --timeout=unlimited, orgclouddies on you along the way. pg_dumpversion: use the same major version as the target or lower.pg_dump17 writesSET transaction_timeout = 0;, which a PostgreSQL 16 rejects with “unrecognized configuration parameter” and aborts the import.utf8mb4on the instance:--default-character-set=utf8mb4in the dump does not fix an instance created withlatin1; checkcharacter_set_serverin the instance flags before importing.- The DEFINER
sedalso touches data: if some row literally containsDEFINER=`x`@`y`(rare, but a CMS that stores SQL can), skip it with a pattern anchored toCREATE, or review the dump diff.
See also
- PostgreSQL: admin cheat sheet — roles,
pg_dump,REASSIGN OWNEDand friends. - Import and export using SQL dump files (MySQL), DEFINER clause and pg_dump / pg_restore (PostgreSQL) in the Cloud SQL documentation.