<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Mysqldump on inetshell</title><link>https://inet.sh/en/tags/mysqldump/</link><description>Recent content in Mysqldump on inetshell</description><generator>Hugo</generator><language>en</language><lastBuildDate>Sat, 26 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://inet.sh/en/tags/mysqldump/index.xml" rel="self" type="application/rss+xml"/><item><title>Importing dumps into Cloud SQL without errors: DEFINER and SUPER (ERROR 1227) on MySQL, extensions and owners on PostgreSQL</title><link>https://inet.sh/en/posts/gcp/cloud-sql-import-dumps-definer-gtid-extensions/</link><pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/en/posts/gcp/cloud-sql-import-dumps-definer-gtid-extensions/</guid><description>A mysqldump or pg_dump file that works on any server fails when imported into Cloud SQL: ERROR 1227 &amp;#39;you need the SUPER privilege&amp;#39; from DEFINER clauses and SET @@GLOBAL.GTID_PURGED, &amp;#39;must be owner of extension plpgsql&amp;#39; and &amp;#39;role does not exist&amp;#39; on PostgreSQL. The export flags Cloud SQL expects, what to do with DEFINER clauses (create the users, strip them, or switch to INVOKER), the sed that comments out EXTENSION statements, the bucket permission, and the import with gcloud sql import sql.</description><content:encoded><![CDATA[<h2 id="tldr">TL;DR</h2>
<p><strong>MySQL</strong>, on the source server:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mysqldump --databases app -h db.example -u dumper -p <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --hex-blob --single-transaction --set-gtid-purged<span class="o">=</span>OFF <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --default-character-set<span class="o">=</span>utf8mb4 --routines --triggers --events <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  <span class="p">|</span> sed -E <span class="s1">&#39;s/DEFINER=`[^`]+`@`[^`]+`//g&#39;</span> <span class="p">|</span> gzip &gt; app.sql.gz
</span></span></code></pre></td></tr></table>
</div>
</div><p><strong>PostgreSQL</strong>, on the source server:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pg_dump app -h db.example -U dumper --format<span class="o">=</span>plain --no-owner --no-acl <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  <span class="p">|</span> sed -E <span class="s1">&#39;s/(DROP|CREATE|COMMENT ON) EXTENSION/-- \1 EXTENSION/g&#39;</span> <span class="p">|</span> gzip &gt; app.sql.gz
</span></span></code></pre></td></tr></table>
</div>
</div><p>And in both cases:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">SA</span><span class="o">=</span><span class="k">$(</span>gcloud sql instances describe my-instance --format<span class="o">=</span><span class="s1">&#39;value(serviceAccountEmailAddress)&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">gcloud storage buckets add-iam-policy-binding gs://my-dumps <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --member<span class="o">=</span><span class="s2">&#34;serviceAccount:</span><span class="nv">$SA</span><span class="s2">&#34;</span> --role<span class="o">=</span>roles/storage.objectAdmin
</span></span><span class="line"><span class="cl">gcloud storage cp app.sql.gz gs://my-dumps/
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">gcloud sql import sql my-instance gs://my-dumps/app.sql.gz --database<span class="o">=</span>app                 <span class="c1"># MySQL</span>
</span></span><span class="line"><span class="cl">gcloud sql import sql my-instance gs://my-dumps/app.sql.gz --database<span class="o">=</span>app --user<span class="o">=</span>postgres  <span class="c1"># PostgreSQL</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>gcloud sql import sql</code> takes plain SQL or <code>.gz</code>: the <em>custom</em> format of <code>pg_dump</code> goes through <code>pg_restore</code> (below).</p>
<h2 id="the-errors-and-where-they-come-from">The errors and where they come from</h2>
<p>On Cloud SQL nobody has <code>SUPER</code> (MySQL) or is a superuser (PostgreSQL); the admin is <code>root@%</code> or <code>postgres</code>, a member of <code>cloudsqlsuperuser</code>. Everything a normal dump takes for granted on your own server breaks right there:</p>
<table>
  <thead>
      <tr>
          <th>Import error</th>
          <th>Engine</th>
          <th>Cause</th>
          <th>Fix</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>ERROR 1227 (42000) at line N: Access denied; you need (at least one of) the SUPER privilege(s) for this operation</code> (on a <code>CREATE … VIEW</code>, <code>TRIGGER</code>, <code>PROCEDURE</code>, <code>FUNCTION</code> or <code>EVENT</code> line)</td>
          <td>MySQL</td>
          <td><code>DEFINER=`user`@`host` </code> for a user that does not exist on the instance, or for <code>root@localhost</code>, which never exists on Cloud SQL. The dump does not carry users (and must not)</td>
          <td>Create those users before importing, or strip the <code>DEFINER=</code> clauses with the <code>sed</code></td>
      </tr>
      <tr>
          <td>The same <code>ERROR 1227</code> in the first lines, at <code>SET @@GLOBAL.GTID_PURGED=…</code></td>
          <td>MySQL</td>
          <td>The source has GTID enabled and <code>mysqldump</code> writes the position, which only <code>SUPER</code> can set</td>
          <td><code>--set-gtid-purged=OFF</code></td>
      </tr>
      <tr>
          <td><code>ERROR 1449 (HY000): The user specified as a definer ('app'@'localhost') does not exist</code> when <strong>using</strong> the view, not when importing</td>
          <td>MySQL</td>
          <td>You left a <code>DEFINER</code> for a user that does not exist on the instance</td>
          <td>Strip the <code>DEFINER</code> or create the user with that exact host</td>
      </tr>
      <tr>
          <td><code>ERROR: must be owner of extension plpgsql</code></td>
          <td>PostgreSQL</td>
          <td>The dump&rsquo;s <code>COMMENT ON EXTENSION plpgsql</code>: on Cloud SQL extensions belong to <code>cloudsqladmin</code></td>
          <td>Comment out the <code>EXTENSION</code> lines with the <code>sed</code></td>
      </tr>
      <tr>
          <td><code>ERROR: role &quot;app_owner&quot; does not exist</code></td>
          <td>PostgreSQL</td>
          <td><code>ALTER … OWNER TO</code> and <code>GRANT … TO</code> for roles from the old server</td>
          <td><code>--no-owner --no-acl</code>, or create the roles first</td>
      </tr>
      <tr>
          <td><code>ERROR: permission denied for schema public</code> or <code>must be owner of …</code> halfway through</td>
          <td>PostgreSQL</td>
          <td>You imported with a <code>--user</code> that does not own what already exists in the database</td>
          <td>Import as <code>postgres</code> into an empty database; reassign afterwards</td>
      </tr>
  </tbody>
</table>
<h2 id="mysql-what-to-do-with-definer-clauses">MySQL: what to do with DEFINER clauses</h2>
<p>First see how many there are on the source, and whose:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="k">DISTINCT</span><span class="w"> </span><span class="k">DEFINER</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">INFORMATION_SCHEMA</span><span class="p">.</span><span class="n">VIEWS</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">SELECT</span><span class="w"> </span><span class="k">DISTINCT</span><span class="w"> </span><span class="k">DEFINER</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">INFORMATION_SCHEMA</span><span class="p">.</span><span class="n">TRIGGERS</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">SELECT</span><span class="w"> </span><span class="k">DISTINCT</span><span class="w"> </span><span class="k">DEFINER</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">INFORMATION_SCHEMA</span><span class="p">.</span><span class="n">ROUTINES</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">SELECT</span><span class="w"> </span><span class="k">DISTINCT</span><span class="w"> </span><span class="k">DEFINER</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">INFORMATION_SCHEMA</span><span class="p">.</span><span class="n">EVENTS</span><span class="p">;</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Three ways out, from most to least faithful to the original:</p>
<ol>
<li><strong>Create the users on the instance before importing</strong>, with the same <code>user@host</code>: <code>gcloud sql users create app --host=% --instance=my-instance --password=…</code>. This is what the documentation recommends, and it keeps views and procedures exactly as they were. <code>root@localhost</code> is the exception: it cannot be created on Cloud SQL, so a <code>DEFINER</code> with that value must be changed no matter what.</li>
<li><strong>Strip the <code>DEFINER</code></strong> with the <code>sed</code> above: objects get created with the importing user as definer. Enough when the source users will never exist on Cloud SQL.</li>
<li><strong>Switch to <code>SQL SECURITY INVOKER</code></strong> (<code>sed -E 's/SQL SECURITY DEFINER/SQL SECURITY INVOKER/g'</code> on top of the previous one): the view or procedure runs with the caller&rsquo;s privileges and the definer stops mattering. It changes the permission model; check that the application users can read the underlying tables.</li>
</ol>
<p>The <code>sed</code> is deliberately broad because <code>DEFINER=</code> also shows up inside conditional comments (<code>/*!50013 DEFINER=… SQL SECURITY DEFINER */</code>); deleting just the <code>DEFINER=</code> leaves the rest intact and valid.</p>
<h2 id="mysql-the-flags-one-by-one">MySQL: the flags, one by one</h2>
<table>
  <thead>
      <tr>
          <th>Flag</th>
          <th>Why</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>--hex-blob</code></td>
          <td>Binary columns as hex; without it, a <code>BLOB</code> with odd bytes gets corrupted going through the character set</td>
      </tr>
      <tr>
          <td><code>--single-transaction</code></td>
          <td>Consistent InnoDB snapshot without locking tables; a must in production</td>
      </tr>
      <tr>
          <td><code>--set-gtid-purged=OFF</code></td>
          <td>Do not write <code>SET @@GLOBAL.GTID_PURGED</code>, which Cloud SQL will not let you run</td>
      </tr>
      <tr>
          <td><code>--default-character-set=utf8mb4</code></td>
          <td>Make the dump and the <code>SET NAMES</code> it carries match what is in the tables; avoids the classic <code>Ã©</code></td>
      </tr>
      <tr>
          <td><code>--databases app</code></td>
          <td>Required per the documentation, and never with the <code>mysql</code> database. Adds <code>CREATE DATABASE IF NOT EXISTS</code> and <code>USE app</code>, so <code>--database</code> on import becomes optional</td>
      </tr>
      <tr>
          <td><code>--routines --triggers --events</code></td>
          <td><code>mysqldump</code> only includes triggers by default; procedures, functions and events must be asked for. All of them carry a <code>DEFINER</code></td>
      </tr>
  </tbody>
</table>
<p>If the user you dump with lacks <code>PROCESS</code>, <code>mysqldump</code> 8.0.21+ fails right away with <em>&ldquo;Access denied; you need (at least one of) the PROCESS privilege(s)&rdquo;</em> while querying tablespaces: add <code>--no-tablespaces</code>.</p>
<h2 id="postgresql-plain-or-custom">PostgreSQL: plain or custom</h2>
<p><code>--no-owner --no-acl</code> make everything imported belong to <code>--user</code>, with no <code>GRANT</code> to phantom roles. If the application connects as another role, create the role first and use <code>--user=app</code>, or reassign afterwards:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="n">REASSIGN</span><span class="w"> </span><span class="n">OWNED</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">postgres</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="n">app</span><span class="p">;</span><span class="w">   </span><span class="c1">-- connected as postgres, in the imported database
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Create the extensions the database needs (<code>pg_trgm</code>, <code>postgis</code>, <code>uuid-ossp</code>…) <strong>before</strong> importing, as <code>postgres</code>, in the target database: <code>CREATE EXTENSION IF NOT EXISTS pg_trgm;</code>. Only the ones on Cloud SQL&rsquo;s supported extensions list work; <code>plpgsql</code> is already there. With that in place, commenting out the dump&rsquo;s <code>CREATE EXTENSION</code> lines removes nothing.</p>
<p>If you prefer the <em>custom</em> format (<code>-Fc</code>), the extension filter goes into the <code>pg_restore</code> table of contents, exactly as Google documents it, and you restore through the Cloud SQL Auth Proxy:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pg_dump app -h db.example -U dumper --format<span class="o">=</span>custom --no-owner --no-acl &gt; app.dmp
</span></span><span class="line"><span class="cl">pg_restore --list app.dmp <span class="p">|</span> sed -E <span class="s1">&#39;s/(.*EXTENSION )/; \1/g&#39;</span> &gt; app.toc
</span></span><span class="line"><span class="cl">pg_restore --use-list<span class="o">=</span>app.toc --no-owner --no-acl -j <span class="m">4</span> -h 127.0.0.1 -U postgres -d app app.dmp
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>-j 4</code> restores in parallel, which the import from the bucket does not do.</p>
<h2 id="traps">Traps</h2>
<ul>
<li><strong>The bucket role goes to the instance&rsquo;s service account</strong>, not yours: the docs ask for <code>storage.objectAdmin</code> (for import alone <code>storage.objects.get</code> and <code>list</code>, that is <code>objectViewer</code>, are enough). Without it, <em>&ldquo;The service account does not have the required permissions for the bucket&rdquo;</em> even though you can read the object yourself.</li>
<li><strong>One operation at a time per instance</strong>: with a backup or export running, the import fails with <em>&ldquo;operation already in progress&rdquo;</em>. <code>gcloud sql operations list --instance=my-instance</code> shows it.</li>
<li><strong>Large dumps</strong>: the import is serial and slow; launch it with <code>--async</code> and watch it with <code>gcloud sql operations wait OPERATION_ID --timeout=unlimited</code>, or <code>gcloud</code> dies on you along the way.</li>
<li><strong><code>pg_dump</code> version</strong>: use the same major version as the target or lower. <code>pg_dump</code> 17 writes <code>SET transaction_timeout = 0;</code>, which a PostgreSQL 16 rejects with <em>&ldquo;unrecognized configuration parameter&rdquo;</em> and aborts the import.</li>
<li><strong><code>utf8mb4</code> on the instance</strong>: <code>--default-character-set=utf8mb4</code> in the dump does not fix an instance created with <code>latin1</code>; check <code>character_set_server</code> in the instance flags before importing.</li>
<li><strong>The DEFINER <code>sed</code> also touches data</strong>: if some row literally contains <code>DEFINER=`x`@`y` </code> (rare, but a CMS that stores SQL can), skip it with a pattern anchored to <code>CREATE</code>, or review the dump diff.</li>
</ul>
<h2 id="see-also">See also</h2>
<ul>
<li><a href="/en/posts/databases/postgresql-admin-cheat-sheet/">PostgreSQL: admin cheat sheet</a> — roles, <code>pg_dump</code>, <code>REASSIGN OWNED</code> and friends.</li>
<li><a href="https://docs.cloud.google.com/sql/docs/mysql/import-export/import-export-sql">Import and export using SQL dump files (MySQL)</a>, <a href="https://docs.cloud.google.com/sql/docs/mysql/import-export#definer-clause">DEFINER clause</a> and <a href="https://docs.cloud.google.com/sql/docs/postgres/import-export/import-export-dmp">pg_dump / pg_restore (PostgreSQL)</a> in the Cloud SQL documentation.</li>
</ul>
]]></content:encoded></item></channel></rss>