Files
nexus/sreweekly/articles/471/03-the-state-of-online-schema-migrations-in-mysql.html
2026-09-12 17:23:01 +08:00

192 lines
90 KiB
HTML

<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><meta name="theme-color" content="#111111"/><meta name="user-signed-in" content="false"/><title>The State of Online Schema Migrations in MySQL — PlanetScale</title><meta name="description" content="Learn about the options for running non-blocking schema changes natively to MySQL, using Vitess, or other tools"/><meta name="robots"/><meta property="og:url" content="https://planetscale.com/blog/state-of-online-schema-migrations-in-mysql"/><meta property="og:type" content="website"/><meta property="og:title" content="The State of Online Schema Migrations in MySQL — PlanetScale"/><meta property="og:image" content="https://planetscale.com/assets/state-of-online-schema-migrations-in-mysql-social-CrLOQ2AR.jpg"/><meta property="og:description" content="Learn about the options for running non-blocking schema changes natively to MySQL, using Vitess, or other tools"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:site" content="@PlanetScale"/><meta property="twitter:creator" content="@PlanetScale"/><meta property="twitter:url" content="https://planetscale.com/blog/state-of-online-schema-migrations-in-mysql"/><meta property="twitter:title" content="The State of Online Schema Migrations in MySQL — PlanetScale"/><meta property="twitter:description" content="Learn about the options for running non-blocking schema changes natively to MySQL, using Vitess, or other tools"/><meta property="twitter:image" content="https://planetscale.com/assets/state-of-online-schema-migrations-in-mysql-social-CrLOQ2AR.jpg"/><link rel="canonical" href="https://planetscale.com/blog/state-of-online-schema-migrations-in-mysql"/><link rel="preconnect" href="https://planetscale-images.imgix.net"/><link nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0=" rel="icon" href="/favicon.ico" type="image/x-icon" sizes="16x16"/><link nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0=" rel="icon" href="/icon.png" type="image/png" sizes="32x32"/><link nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0=" rel="apple-touch-icon" href="/apple-touch-icon.png" type="image/png" sizes="32x32"/><link nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0=" rel="manifest" href="/manifest.webmanifest"/><link rel="modulepreload" href="/assets/entry.client-3vubyXrk.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/jsx-runtime-DwfQwkRq.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/components-_bNmAApg.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/_.well-known_.mcp.server-card_.json_-Sx7XeH3e.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/index-mKTXLmHu.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/errorBoundaries-DhW4jVYt.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/root-DbOv4-98.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/lib-Dg89tQ22.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/analytics.client-DM6E8o1h.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/SiteHeader-C2U5gvDH.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/current-9yDxj94E.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/clsx-eT0YPcGk.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/bugs-38ilEoW0.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/keyboard-D-uXZORL.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/use-tab-direction-dKm-S3Ck.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/blog-pXH7ptHJ.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/blog._slug-Ch_92qsH.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/ContentImage-Dh6VEOUl.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/BlogCategoryLink-DmQyn0gp.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/Details-BSB_b6hI.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/Skittle-CDFOPRjH.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/SiteFooter-B2Gq9u2j.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/Vimeo-00PQJDli.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/YouTube-CMfaljVr.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/date-CJTFH3uT.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/use-inert-others-BMJ6-xOX.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/description-Cf6FZmDe.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/use-is-mounted-uQsUZyP9.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="modulepreload" href="/assets/types-DvonrUFF.js" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0="/><link rel="stylesheet" href="/assets/styles-ns8XBZ1D.css"/><script nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0=">window.ENV = {"IMAGE_CDN":"https://planetscale-images.imgix.net","IMAGE_CDN_ENABLED":"true","INTERNAL_API":"https://api.planetscale.com","RELEASE":"117b8aaf-965c-42bc-b013-5f72770de4d9","SENTRY_DSN":"https://bd81903b44804e22a06bdc0c1a91b303@o499952.ingest.us.sentry.io/4504531942572032"}</script></head><body class="flex min-h-screen flex-col"><div class="bg-neki px-3 py-1 text-center font-medium text-gray-900 dark:font-semibold"><span>Neki, sharded Postgres, is now available.</span> <span class="whitespace-nowrap"><a href="https://auth.planetscale.com/sign-up" class="whitespace-nowrap bg-gray-900 px-sm font-semibold text-white">Get started</a></span></div><header class="relative mb-6 mt-4 bg-primary"><div class="flex flex-col gap-y-3 px-3 sm:px-5 container max-w-7xl"><div class="grid w-full grid-cols-[auto_1fr] grid-rows-1 items-center lg:items-start lg:gap-3"><a aria-label="Go to homepage" class="col-start-1 col-end-2 h-4 w-4 rounded-full text-primary lg:hidden" href="/" data-discover="true"><svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none" viewBox="0 0 40 40"><path fill="currentColor" d="M0 20C0 8.954 8.954 0 20 0c8.121 0 15.112 4.84 18.245 11.794l-26.45 26.45a20 20 0 0 1-3.225-1.83L24.984 20H20L5.858 34.142A19.94 19.94 0 0 1 0 20M39.999 20.007 20.006 40c11.04-.004 19.99-8.953 19.993-19.993"></path></svg></a><div class="group col-start-2 col-end-3 row-start-1 flex shrink-0 items-center justify-end gap-1.5 lg:gap-3"><div class="flex flex-row gap-2 lg:flex-col lg:gap-1 xl:flex-row"><div class="flex items-center justify-end gap-1 lg:h-4"><a href="https://auth.planetscale.com/sign-in" class="font-semibold text-primary hover:text-orange">Sign in</a></div><div class="flex items-center justify-end gap-0.5 lg:h-4"><form class="btn-sm hidden sm:inline-flex" action="/api/demo-sessions" method="post"><button type="submit" class="btn btn-outline btn-sm hidden sm:inline-flex">View sandbox</button></form><a class="btn btn-sm" href="/contact" data-discover="true">Get in touch</a></div></div></div><div class="col-start-1 col-end-2 flex items-center gap-x-3 lg:row-start-1 lg:h-4"><a aria-label="Go to homepage" class="col-start-1 col-end-2 hidden h-4 w-4 rounded-full text-primary lg:block" href="/" data-discover="true"><svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none" viewBox="0 0 40 40"><path fill="currentColor" d="M0 20C0 8.954 8.954 0 20 0c8.121 0 15.112 4.84 18.245 11.794l-26.45 26.45a20 20 0 0 1-3.225-1.83L24.984 20H20L5.858 34.142A19.94 19.94 0 0 1 0 20M39.999 20.007 20.006 40c11.04-.004 19.99-8.953 19.993-19.993"></path></svg></a><nav aria-label="Main" data-orientation="horizontal" class="hidden items-center lg:flex"><ul class="flex flex-wrap gap-x-1 md:flex-nowrap"><li><div data-headlessui-state=""><button class="font-semibold text-primary hover:text-contrast focus-visible:ring-0 ui-open:text-orange" type="button" aria-expanded="false" data-headlessui-state="">Platform<span class="ml-sm inline-block ui-open:rotate-180">▾</span></button></div><span hidden="" style="position:fixed;top:1px;left:1px;width:1px;height:0;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border-width:0;display:none"></span></li><li class="text-decoration" role="presentation">|</li><li><div data-headlessui-state=""><button class="font-semibold text-primary hover:text-contrast focus-visible:ring-0 ui-open:text-orange" type="button" aria-expanded="false" data-headlessui-state="">Resources<span class="ml-sm inline-block ui-open:rotate-180">▾</span></button></div><span hidden="" style="position:fixed;top:1px;left:1px;width:1px;height:0;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border-width:0;display:none"></span></li><li class="text-decoration" role="presentation">|</li><li><a class="font-semibold text-primary hover:text-contrast" href="/docs">Documentation</a></li><li class="text-decoration" role="presentation">|</li><li><a class="font-semibold text-primary hover:text-contrast" href="/pricing" data-discover="true">Pricing</a></li><li class="text-decoration" role="presentation">|</li><li><a class="font-semibold text-primary hover:text-contrast" href="/migrate" data-discover="true">Migrate</a></li></ul></nav></div></div><details class="lg:hidden"><summary>Navigation</summary><nav class="dashed-box mt-1 p-3"><ul class="flex flex-wrap gap-x-1 md:flex-nowrap"><li><div data-headlessui-state=""><button class="font-semibold text-primary hover:text-contrast focus-visible:ring-0 ui-open:text-orange" type="button" aria-expanded="false" data-headlessui-state="">Platform<span class="ml-sm inline-block ui-open:rotate-180">▾</span></button></div><span hidden="" style="position:fixed;top:1px;left:1px;width:1px;height:0;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border-width:0;display:none"></span></li><li class="text-decoration" role="presentation">|</li><li><div data-headlessui-state=""><button class="font-semibold text-primary hover:text-contrast focus-visible:ring-0 ui-open:text-orange" type="button" aria-expanded="false" data-headlessui-state="">Resources<span class="ml-sm inline-block ui-open:rotate-180">▾</span></button></div><span hidden="" style="position:fixed;top:1px;left:1px;width:1px;height:0;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border-width:0;display:none"></span></li><li class="text-decoration" role="presentation">|</li><li><a class="font-semibold text-primary hover:text-contrast" href="/docs">Documentation</a></li><li class="text-decoration" role="presentation">|</li><li><a class="font-semibold text-primary hover:text-contrast" href="/pricing" data-discover="true">Pricing</a></li><li class="text-decoration" role="presentation">|</li><li><a class="font-semibold text-primary hover:text-contrast" href="/migrate" data-discover="true">Migrate</a></li></ul></nav></details></div></header><main class="container mb-6 flex max-w-7xl flex-1 flex-col px-3 sm:px-5 lg:px-12"><section class=""><p class="block"><a class="pr-sm text-primary hover:text-contrast" href="/blog" data-discover="true">Blog</a><span class="px-sm text-decoration">|</span><a class="px-sm text-blue hover:bg-blue-100 dark:hover:bg-blue-900" href="/blog/category/engineering" data-discover="true">Engineering</a></p><div class="flex lg:flex-row-reverse lg:gap-x-6"><div class="lg:sticky lg:top-2 lg:self-start"><button class="absolute right-0 bg-gray-100 px-sm md:block lg:hidden dark:bg-gray-800 -mt-9 hidden"><span class="inline">Table of contents «</span><span class="hidden">Close »</span></button><aside class="tree-nav w-full shrink-0 space-y-3 lg:w-36 hidden lg:block"><div><h4 class="text-secondary">Table of contents</h4><ul><li><a class="font-semibold text-primary hover:text-blue" href="/blog/state-of-online-schema-migrations-in-mysql#inplace-aka-innodb-online-ddl" data-discover="true">INPLACE, aka InnoDB Online DDL</a><ul><li><a class="text-primary hover:text-blue" href="/blog/state-of-online-schema-migrations-in-mysql#inplace-conclusions" data-discover="true">INPLACE conclusions</a></li></ul></li><li><a class="font-semibold text-primary hover:text-blue" href="/blog/state-of-online-schema-migrations-in-mysql#instant-schema-changes" data-discover="true">INSTANT schema changes</a><ul><li><a class="text-primary hover:text-blue" href="/blog/state-of-online-schema-migrations-in-mysql#instant-risks" data-discover="true">INSTANT risks</a></li><li><a class="text-primary hover:text-blue" href="/blog/state-of-online-schema-migrations-in-mysql#revertibility" data-discover="true">Revertibility</a></li><li><a class="text-primary hover:text-blue" href="/blog/state-of-online-schema-migrations-in-mysql#instant-conclusions" data-discover="true">INSTANT conclusions</a></li></ul></li><li><a class="font-semibold text-primary hover:text-blue" href="/blog/state-of-online-schema-migrations-in-mysql#solutions-external-to-mysql" data-discover="true">Solutions external to MySQL</a><ul><li><a class="text-primary hover:text-blue" href="/blog/state-of-online-schema-migrations-in-mysql#note-on-partitioning" data-discover="true">Note on partitioning</a></li><li><a class="text-primary hover:text-blue" href="/blog/state-of-online-schema-migrations-in-mysql#3rd-party-conclusions" data-discover="true">3rd party conclusions</a></li></ul></li></ul><div class="mb-3 mt-6 border bg-blue-50 p-3 font-semibold text-contrast dark:bg-blue-900"><p>PlanetScale, the fastest cloud Postgres, from $5/month.</p><p><a href="https://app.planetscale.com/new">Start now</a></p></div><p>Get the <a href="/blog/feed.atom">RSS feed</a></p></div></aside></div><article class="min-w-0 flex-grow"><h1>The State of Online Schema Migrations in MySQL</h1><p class="text-secondary"><a class="text-contrast no-underline" href="/blog/author/shlomi" data-discover="true">Shlomi Noach</a> |<!-- --> <time dateTime="2024-07-23">July 23, 2024</time></p><div class="blog-post-body"><p>How do you run non-blocking schema changes in MySQL? This is an eternal question. With a plethora of 3rd party solutions and with recent advancements in MySQL, it&#x27;s difficult to track which solution is preferable for a given schema migration. In this post, we provide a high level overview of the state of MySQL online schema migrations in 2024. We limit the discussion to <code>ALTER TABLE</code> statements, as other DDL statements are <em>typically</em> fast (<code>DROP TABLE</code> is somewhat of an exception, but out of scope of this post).</p><p>We&#x27;ll first examine the native MySQL options: <code>INPLACE</code> and <code>INSTANT</code>. For reference, see <a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-operations.html">Online DDL Operations</a> MySQL 8.0 documentation.</p><h2 id="inplace-aka-innodb-online-ddl"><a href="#inplace-aka-innodb-online-ddl"><code>INPLACE</code>, aka InnoDB Online DDL</a></h2><p>This is MySQL&#x27;s first take on non-blocking schema changes. <em>Some</em> types of <code>ALTER TABLE</code> (see above link for exhaustive list of supported changes) are eligible to run with <code>ALGORITHM=INPLACE</code>. An <code>INPLACE</code> schema change is technically non-blocking, with quite a few caveats:</p><ul><li>On the server where the query is submitted, normally the primary database server, DML queries (<code>SELECT</code>, <code>INSERT</code>, <code>UPDATE</code>, <code>DELETE</code>, ...) are non-blocking and may proceed to execute. Other DDL statements will block, and that&#x27;s expected.</li><li>The operation is resource greedy: the MySQL server will use as much CPU and disk I/O to complete the change as it can. This can and will impact performance on busy servers.</li><li>It requires extra disk space, up to as much disk space as the original table.</li><li>It is uninterruptible. The only way to abort is to kill the query aggressively. This then leads to a further massive cleanup operation, consuming more disk I/O.</li><li>On replica servers, the operation is NOT non-blocking. Meaning if the <code>ALTER TABLE</code> took 3 hours on the primary server, then from the moment it completes you can expect replication to stall while applying that same change for the next 3 hours or so, creating a massive 3 hour lag.</li></ul><p>The replication issue is a deal breaker for most. One way around it is to run the <code>ALTER TABLE</code> on the primary with <code>SQL_LOG_BIN=0</code> so that it does not replicate. Then, run it similarly individually on each replica. This technique works, but can be the cause for inconsistencies. Did you track all servers? What if you subsequently restore (or bootstrap) a server from backup, where the change never took place? How do you track that? Moreover, this technique will take <code>n</code> times longer to complete, as you need to run the change individually for each server. You may parallelize some of the work, but probably not all of it.</p><h3 id="inplace-conclusions"><a href="#inplace-conclusions"><code>INPLACE</code> conclusions</a></h3><p>For these reasons we find that <code>INPLACE</code> is not a good option for non-blocking changes.</p><h2 id="instant-schema-changes"><a href="#instant-schema-changes"><code>INSTANT</code> schema changes</a></h2><p>Instant schema changes are almost a holy grail in the world of databases, and where it works, it&#x27;s the next best thing after pizza (pending any bugs). MySQL offers support for <em>some</em> schema changes to run with <code>ALGORITHM=INSTANT</code>. <a href="https://dev.mysql.com/blog-archive/mysql-8-0-innodb-now-supports-instant-add-column/">Originally contributed to MySQL by Tencent</a> six years ago, <code>INSTANT</code> DDL only supported a single type of change: <code>ADD COLUMN</code>. Later on MySQL added support for more changes, like expanding an <code>enum</code> column, or adding and dropping <code>VIRTUAL</code> columns. Recently (one year ago), MySQL <code>8.0.29</code> added support for arbitrary <code>ADD COLUMN</code> and <code>DROP COLUMN</code> support.</p><p><code>INSTANT</code> truly runs instantly. It does not need to copy a table, does not need extra disk space, does not hammer the CPU. There&#x27;s nothing to interrupt because the operation terminates before you&#x27;ve blinked. It also runs instantly on the replicas.</p><p>It sounds perfect! And it mostly is, where supported, and with a bit of nuance. Consider again the documentation for supported operations. Looking closely, you can see these types of supported changes:</p><ul><li>Changing a column default value.</li><li>Adding/removing <code>VIRTUAL</code> columns.</li><li>Modify an <code>enum</code> definition.</li><li>And more.</li></ul><p>What&#x27;s shared to these changes is that they&#x27;re all metadata changes. They do not affect existing rows, do not modify the data, do not restructure the table, do not affect indexes. <code>ADD COLUMN</code> &amp; <code>DROP COLUMN</code> are the only supported changes that actually affect table data or how the data is structured. As another caveat, you cannot <code>DROP COLUMN</code> using <code>INSTANT</code> DDL if that column participates in an index.</p><p>So you <em>can</em> change a column&#x27;s default value from <code>0</code> to <code>1</code>, but you <em>cannot</em> make a nullable column non-nullable. You <em>can</em> add and drop <code>GENERATED VIRTUAL</code> columns, but <em>cannot</em> add and drop <code>GENERATED STORED</code> columns. You <em>can</em> modify an <code>enum</code> definition, but you <em>cannot</em> modify a column&#x27;s type from <code>int</code> to <code>bigint</code>.</p><p>The list of unsupported changes includes:</p><ul><li>Changing a column&#x27;s data type.</li><li>Adding a column with non-literal default value.</li><li>Adding indexes.</li><li>Modifying a <code>PRIMARY KEY</code> definition.</li><li>Adding/removing foreign keys.</li><li>Changing a table&#x27;s character set.</li><li>Making partitioning changes.</li></ul><p>Which is to say, there&#x27;s a long way to go before <code>INSTANT</code> DDL can satisfy the common needs of schema changes. Where possible, <code>INSTANT</code> DDL is wonderful, and in many situations is the preferable and recommended way to go.</p><h3 id="instant-risks"><a href="#instant-risks"><code>INSTANT</code> risks</a></h3><p><code>INSTANT</code> first appears to be risk-free. In most situations, it is! Even if you make a mistake, it can be corrected with a counter-<code>INSTANT</code> operation. That&#x27;s true for most changes, except when data is destroyed. At this time, the one destructive statement support by <code>INSTANT</code> DDL is <code>DROP COLUMN</code> (for &quot;real&quot;, non <code>VIRTUAL</code> columns).</p><p>Dropping a column has two main risks to it:</p><ol><li>The obvious risk of losing important data, if executed prematurely or accidentally.</li><li>The risk of breaking existing queries.</li></ol><p>Losing data can obviously be a massive incident, the cause for outage and for long hours or days of recovery. But why is this an <code>INSTANT</code> risk in particular? It&#x27;s the same damage whether <code>INSTANT</code> or not, right?</p><p>The answer is with the human behavior of always choosing <code>INSTANT</code> where possible. You&#x27;re an instant away from destroying your data, and with no barriers to hold you back, nor a mechanism (short of backups and delayed replicas) to take you back to safety. We&#x27;ll discuss this shortly as we introduce the concept of Revertibility (specifically in <a href="https://vitess.io">Vitess</a>).</p><p>How about breaking existing queries? Maybe the data is truly expendable, or safely aggregated elsewhere, but perhaps a bunch of <code>SELECT</code> or <code>INSERT</code> queries still reference the column?</p><p>MySQL offers <a href="https://dev.mysql.com/doc/refman/8.0/en/invisible-columns.html">invisible columns</a> as a means to emulate how your table might look like without a given column. However, it is limited. It only affects queries that do not explicitly use the column name, such as <code>SELECT * FROM my_table ....</code> or <code>INSERT INTO my_table VALUE (...)</code>. But any <code>SELECT the_column FROM my_table</code> query still has full access to columns. In today&#x27;s world, <code>SELECT *</code> and blind <code>INSERT</code> queries are not as common. Frameworks, tooling, and modern engineering paradigms all tend to be explicit and fully qualified. Invisible columns does not help here.</p><p>If dropping the column did cause queries to break, you will then need to either fix all the queries, or attempt to re-introduce the column. Let&#x27;s now discuss revertibility.</p><h3 id="revertibility"><a href="#revertibility">Revertibility</a></h3><p>What are your options for undoing a change? For switching back to the previous schema? Let&#x27;s illustrate using two simple examples.</p><p>Say your change was to <code>ALTER TABLE my_table ADD COLUMN name ...</code>. This looks harmless, and yet can cause downtime. <code>name</code> can be a common column <em>name</em>. Queries selecting <code>name</code> in a multi-table statement, such as <code>SELECT name, value FROM my_table JOIN another_table USING ...</code>, could fail due to the new ambiguity of <code>name</code> column.</p><p>The anti-change for <code>ADD COLUMN</code> is a <code>DROP COLUMN</code>, and since both are supported by <code>INSTANT</code> DDL, chances are you&#x27;ll be able to recover quickly and relatively safely.</p><p>What if your change was a <code>DROP COLUMN</code>? Lost data aside, what is the anti-change you&#x27;d apply to restore the previous schema? Not only data was lost, but also metadata. What was the column type? Length? Was it nullable? That information cannot be inferred unless you <em>have</em> the previous schema. In all likelihood, you use version control to manage your schema and are thus able to extract the previous definition. It is worth pointing out, though, that crafting the anti-change of a schema migration is nontrivial.</p><h3 id="instant-conclusions"><a href="#instant-conclusions"><code>INSTANT</code> conclusions</a></h3><p>Where possible, <code>INSTANT</code> is often the best approach for making online schema changes. However, it is too limited at this time and does not support the majority of common schema changes. It does not provide revertibility in case of data destruction. The MySQL team does not publish concrete plans for <code>INSTANT</code> DDL support in future versions of MySQL.</p><h2 id="solutions-external-to-mysql"><a href="#solutions-external-to-mysql">Solutions external to MySQL</a></h2><p>A number of 3rd party tools is available today for running online schema changes for MySQL. We will focus on <a href="https://vitess.io/docs/user-guides/schema-changes/managed-online-schema-changes/">Vitess</a>, the technology behind PlanetScale&#x27;s <a href="/docs/vitess/schema-changes">non-blocking schema changes</a>. Other 3rd party tools include <a href="https://github.com/github/gh-ost">gh-ost</a>, <a href="https://www.percona.com/doc/percona-toolkit/3.0/pt-online-schema-change.html">pt-online-schema-change</a>, recent newcomer <a href="https://github.com/cashapp/spirit">spirit</a>, and others.</p><p>These tools all share a <a href="/docs/vitess/schema-changes/how-online-schema-change-tools-work">similar basic design</a>, but <a href="/docs/vitess/schema-changes/online-schema-change-tools-comparison">operate differently</a>. The major characteristics share to all are:</p><ul><li>They mimic an <code>ALTER TABLE</code> by creating a <em>shadow</em> table with the new schema and slowly copying over data.</li><li>They can and often will take longer time to complete as compared with a native MySQL <code>ALTER TABLE</code>.</li><li>They require extra disk space, about as much as the existing table (less if you consider fragmentation, more if you&#x27;re adding bloated indexes, etc.)</li><li>They cause binary log bloating (essentially the entire table content goes through the binary logs).</li><li>They respect production workload, and will pause or throttle as needed so as to give way to production traffic (hence they&#x27;re likely to run longer).</li><li>They operate in small batches of changes, hence are able to keep replication lag to a minimum (and throttle based on lag).</li><li>They are interruptible: the operation can be aborted at no immediate cost (cleanup can be done at a later stage).</li><li>They are capable of handling almost every single kind of schema change.<ul><li>Most have foreign key limitations.</li><li>Some partitioning options are not recommended, or are plain incorrect to run using these tools.</li></ul></li></ul><p>To put it out of the way: if your table has a <code>color enum(&#x27;red&#x27;,&#x27;green&#x27;,&#x27;blue&#x27;)</code> column, and you want to add a new enum value, making it <code>color enum(&#x27;red&#x27;,&#x27;green&#x27;,&#x27;blue&#x27;,&#x27;orange&#x27;)</code>, you&#x27;re better off using <code>INSTANT</code> DDL. There are a handful such cases, that are supported by <code>INSTANT</code> DDL as mentioned above, and where it just doesn&#x27;t make sense to spend hours of migration.</p><p>However, for the (still vast) majority of changes, these are still the go-to solutions. First, of course, we&#x27;ve already established that neither <code>INSTANT</code> nor <code>INPLACE</code> cover all types of changes (they cover a minority of possible changes). But this also leads to an emerging behavior: maintaining two different techniques in your flow/automation creates more complexity. If you already have to use one of the 3rd party solutions, you may as well use it all the time.</p><p>Both <code>vitess</code> and <code>spirit</code> go an extra mile and can auto detect when a migration can be fulfilled using <code>INSTANT</code> DDL, which means you don&#x27;t need to think about it or be aware of which particular version supports which changes.</p><p><code>vitess</code> further <a href="https://vitess.io/docs/user-guides/schema-changes/revertible-migrations/">supports revertibility</a> as first class citizen, able to not only revert back to the original schema, but also to preserve the would-be lost data, while still accounting for any newly added, updated, or removed data since the change.</p><h3 id="note-on-partitioning"><a href="#note-on-partitioning">Note on partitioning</a></h3><p>Partitioning is a strange beast, and implemented in MySQL by creating a &quot;small&quot; table per partition. As such, operations on partitions are really operations on sets of tables. Some partitioning related changes should <em>only</em> be served by MySQL. Such is a <code>DROP PARTITION</code> statement for e.g. <code>RANGE</code> partitioned table. Some other partitioning changes are <em>better</em> served by MySQL, and some are best served by online schema change tools.</p><h3 id="3rd-party-conclusions"><a href="#3rd-party-conclusions">3rd party conclusions</a></h3><p>Most use cases are best served (or only well served) by 3rd party online schema change tools, and those are still the way to go for the foreseeable future.</p></div></article></div></section></main><footer class="mb-6 mt-10 px-3 sm:px-5 container max-w-7xl"><nav class="grid grid-cols-1 text-left sm:grid-cols-2 lg:grid-cols-5 lg:mx-7"><div class="dashed-box dashed-box-x-t sm:dashed-box-l-t lg:dashed-box-y-l p-3"><h2 class="font-semibold">Company</h2><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/about" data-discover="true">About</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/brand" data-discover="true">Brand</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/blog" data-discover="true">Blog</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/changelog" data-discover="true">Changelog</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/careers" data-discover="true">Careers</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/events" data-discover="true">Events</a></div><div class="dashed-box dashed-box-x-t lg:dashed-box-y-l p-3"><h2 class="font-semibold">Product</h2><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/case-studies" data-discover="true">Case studies</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/enterprise" data-discover="true">Enterprise</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/pricing" data-discover="true">Pricing</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/benchmarks" data-discover="true">Benchmarks</a></div><div class="dashed-box dashed-box-x-t sm:dashed-box-l-t lg:dashed-box-y-l p-3"><h2 class="font-semibold">Resources</h2><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/docs">Documentation</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/migrate" data-discover="true">Migrate</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="https://support.planetscale.com/hc/en-us" rel="nofollow noopener noreferrer" target="_blank">Support</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="https://planetscalestatus.com" rel="nofollow noopener noreferrer" target="_blank">Status</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="https://trust.planetscale.com" rel="nofollow noopener noreferrer" target="_blank">Trust Center</a></div><div class="dashed-box dashed-box-x-t lg:dashed-box-y-l p-3"><h2 class="font-semibold">Courses</h2><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/learn/courses/mysql-for-developers" data-discover="true">MySQL for Developers</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/learn/courses/database-scaling" data-discover="true">Database Scaling</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/learn/courses/vitess" data-discover="true">Learn Vitess</a></div><div class="dashed-box p-3 sm:col-span-2 lg:col-span-1"><h2 class="font-semibold text-primary hover:text-contrast">Open source</h2><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/vitess" data-discover="true">Vitess</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="https://vitess.io/slack" rel="nofollow noopener noreferrer" target="_blank">Vitess community</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="https://github.com/planetscale" rel="me nofollow noopener noreferrer" target="_blank">GitHub</a></div></nav><div class="dashed-box dashed-box-x-b p-3 lg:mx-7"><p class="mb-3 md:mb-0"><a class="text-primary" rel="nofollow" href="/legal/privacy" data-discover="true">Privacy</a><span class="text-decoration" role="presentation"> <!-- -->|<!-- --> </span><a class="text-primary" rel="nofollow" href="/legal/siteterms" data-discover="true">Terms</a><span class="text-decoration" role="presentation"> <!-- -->|<!-- --> </span><a class="text-primary" rel="nofollow" href="/legal/cookies" data-discover="true">Cookies</a><span class="text-decoration" role="presentation"> <!-- -->|<!-- --> </span><a class="text-primary" rel="nofollow" href="/legal/patents" data-discover="true">Patents</a><span class="text-decoration" role="presentation"> <!-- -->|<!-- --> </span><a class="text-primary" rel="nofollow" href="/legal/privacy#privacy-rights-and-choices" data-discover="true">Do Not Share My Personal Information</a></p><p class="text-secondary">© <!-- -->2026<!-- --> PlanetScale, Inc. All rights reserved.</p></div><p class="mb-0 mt-3 break-normal lg:mx-7"><a class="text-primary" href="https://github.com/planetscale" rel="me nofollow noopener noreferrer" target="_blank">GitHub</a><span class="text-decoration" role="presentation"> <!-- -->|<!-- --> </span><a aria-label="X (formerly Twitter)" class="text-primary" href="https://twitter.com/planetscale" rel="me nofollow noopener noreferrer" target="_blank">X</a><span class="text-decoration" role="presentation"> <!-- -->|<!-- --> </span><a aria-label="LinkedIn" class="text-primary" href="https://www.linkedin.com/company/planetscale" target="_blank" rel="noreferrer">LinkedIn</a><span class="text-decoration" role="presentation"> <!-- -->|<!-- --> </span><a class="text-primary" href="https://www.youtube.com/planetscale" rel="me nofollow noopener noreferrer" target="_blank">YouTube</a><span class="text-decoration" role="presentation"> <!-- -->|<!-- --> </span><a aria-label="Discord" class="text-primary" href="https://pscale.link/community" rel="nofollow noopener noreferrer" target="_blank">Discord</a><span class="text-decoration" role="presentation"> <!-- -->|<!-- --> </span><a class="text-primary" href="https://www.facebook.com/planetscaledata" rel="me nofollow noopener noreferrer" target="_blank">Facebook</a></p></footer><script nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0=">((storageKey2, restoreKey) => {
if (!window.history.state || !window.history.state.key) {
let key2 = Math.random().toString(32).slice(2);
window.history.replaceState({ key: key2 }, "");
}
try {
let storedY = JSON.parse(sessionStorage.getItem(storageKey2) || "{}")[restoreKey || window.history.state.key];
if (typeof storedY === "number") window.scrollTo(0, storedY);
} catch (error2) {
console.error(error2);
sessionStorage.removeItem(storageKey2);
}
})("react-router-scroll-positions", null)</script><script nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0=">window.__reactRouterContext = {"basename":"/","future":{"unstable_enableNodeReadableStream":false,"unstable_optimizeDeps":true},"routeDiscovery":{"mode":"lazy","manifestPath":"/__manifest"},"ssr":true,"isSpaMode":false};window.__reactRouterContext.stream = new ReadableStream({start(controller){window.__reactRouterContext.streamController = controller;}}).pipeThrough(new TextEncoderStream());</script><script nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0=" type="module" async="">;
import * as route0 from "/assets/root-DbOv4-98.js";
import * as route1 from "/assets/blog-pXH7ptHJ.js";
import * as route2 from "/assets/blog._slug-Ch_92qsH.js";
window.__reactRouterManifest = {
"entry": {
"module": "/assets/entry.client-3vubyXrk.js",
"imports": [
"/assets/jsx-runtime-DwfQwkRq.js",
"/assets/components-_bNmAApg.js",
"/assets/_.well-known_.mcp.server-card_.json_-Sx7XeH3e.js",
"/assets/index-mKTXLmHu.js",
"/assets/errorBoundaries-DhW4jVYt.js"
],
"css": []
},
"routes": {
"root": {
"id": "root",
"path": "",
"hasAction": false,
"hasLoader": true,
"hasClientAction": false,
"hasClientLoader": false,
"hasClientMiddleware": false,
"hasDefaultExport": true,
"hasErrorBoundary": true,
"module": "/assets/root-DbOv4-98.js",
"imports": [
"/assets/jsx-runtime-DwfQwkRq.js",
"/assets/components-_bNmAApg.js",
"/assets/_.well-known_.mcp.server-card_.json_-Sx7XeH3e.js",
"/assets/index-mKTXLmHu.js",
"/assets/errorBoundaries-DhW4jVYt.js",
"/assets/lib-Dg89tQ22.js",
"/assets/analytics.client-DM6E8o1h.js",
"/assets/SiteHeader-C2U5gvDH.js",
"/assets/current-9yDxj94E.js",
"/assets/clsx-eT0YPcGk.js",
"/assets/bugs-38ilEoW0.js",
"/assets/keyboard-D-uXZORL.js",
"/assets/use-tab-direction-dKm-S3Ck.js"
],
"css": []
},
"routes/blog": {
"id": "routes/blog",
"parentId": "root",
"path": "blog",
"hasAction": false,
"hasLoader": false,
"hasClientAction": false,
"hasClientLoader": false,
"hasClientMiddleware": false,
"hasDefaultExport": false,
"hasErrorBoundary": false,
"module": "/assets/blog-pXH7ptHJ.js",
"imports": [
"/assets/_.well-known_.mcp.server-card_.json_-Sx7XeH3e.js"
],
"css": []
},
"routes/blog.$slug": {
"id": "routes/blog.$slug",
"parentId": "routes/blog",
"path": ":slug",
"hasAction": false,
"hasLoader": true,
"hasClientAction": false,
"hasClientLoader": false,
"hasClientMiddleware": false,
"hasDefaultExport": true,
"hasErrorBoundary": false,
"module": "/assets/blog._slug-Ch_92qsH.js",
"imports": [
"/assets/components-_bNmAApg.js",
"/assets/lib-Dg89tQ22.js",
"/assets/jsx-runtime-DwfQwkRq.js",
"/assets/ContentImage-Dh6VEOUl.js",
"/assets/clsx-eT0YPcGk.js",
"/assets/BlogCategoryLink-DmQyn0gp.js",
"/assets/_.well-known_.mcp.server-card_.json_-Sx7XeH3e.js",
"/assets/Details-BSB_b6hI.js",
"/assets/Skittle-CDFOPRjH.js",
"/assets/SiteFooter-B2Gq9u2j.js",
"/assets/SiteHeader-C2U5gvDH.js",
"/assets/Vimeo-00PQJDli.js",
"/assets/YouTube-CMfaljVr.js",
"/assets/date-CJTFH3uT.js",
"/assets/errorBoundaries-DhW4jVYt.js",
"/assets/keyboard-D-uXZORL.js",
"/assets/use-tab-direction-dKm-S3Ck.js",
"/assets/index-mKTXLmHu.js",
"/assets/use-inert-others-BMJ6-xOX.js",
"/assets/description-Cf6FZmDe.js",
"/assets/use-is-mounted-uQsUZyP9.js",
"/assets/types-DvonrUFF.js",
"/assets/current-9yDxj94E.js",
"/assets/analytics.client-DM6E8o1h.js",
"/assets/bugs-38ilEoW0.js"
],
"css": []
},
"routes/_index": {
"id": "routes/_index",
"parentId": "root",
"index": true,
"hasAction": false,
"hasLoader": true,
"hasClientAction": false,
"hasClientLoader": false,
"hasClientMiddleware": false,
"hasDefaultExport": true,
"hasErrorBoundary": false,
"module": "/assets/_index-BfA6EnlR.js",
"imports": [
"/assets/components-_bNmAApg.js",
"/assets/lib-Dg89tQ22.js",
"/assets/jsx-runtime-DwfQwkRq.js",
"/assets/Logo-Gm9TLYAs.js",
"/assets/SiteFooter-B2Gq9u2j.js",
"/assets/SiteHeader-C2U5gvDH.js",
"/assets/_.well-known_.mcp.server-card_.json_-Sx7XeH3e.js",
"/assets/bugs-38ilEoW0.js",
"/assets/keyboard-D-uXZORL.js",
"/assets/use-is-mounted-uQsUZyP9.js",
"/assets/use-tab-direction-dKm-S3Ck.js",
"/assets/errorBoundaries-DhW4jVYt.js",
"/assets/clsx-eT0YPcGk.js",
"/assets/current-9yDxj94E.js",
"/assets/analytics.client-DM6E8o1h.js",
"/assets/index-mKTXLmHu.js"
],
"css": []
},
"routes/blog._index": {
"id": "routes/blog._index",
"parentId": "routes/blog",
"index": true,
"hasAction": false,
"hasLoader": true,
"hasClientAction": false,
"hasClientLoader": false,
"hasClientMiddleware": false,
"hasDefaultExport": true,
"hasErrorBoundary": false,
"module": "/assets/blog._index-DcvTTuDd.js",
"imports": [
"/assets/components-_bNmAApg.js",
"/assets/jsx-runtime-DwfQwkRq.js",
"/assets/social-Cd2AtOZM.js",
"/assets/BlogCategoryLink-DmQyn0gp.js",
"/assets/BlogPostLink-DC1SPKBJ.js",
"/assets/BlogCategoryNav-CB7TJ3IB.js",
"/assets/Paginator-xlPA_JNt.js",
"/assets/SiteFooter-B2Gq9u2j.js",
"/assets/SiteHeader-C2U5gvDH.js",
"/assets/date-CJTFH3uT.js",
"/assets/_.well-known_.mcp.server-card_.json_-Sx7XeH3e.js",
"/assets/lib-Dg89tQ22.js",
"/assets/errorBoundaries-DhW4jVYt.js",
"/assets/clsx-eT0YPcGk.js",
"/assets/types-DvonrUFF.js",
"/assets/enumerator-2YLGh-nT.js",
"/assets/current-9yDxj94E.js",
"/assets/analytics.client-DM6E8o1h.js",
"/assets/bugs-38ilEoW0.js",
"/assets/keyboard-D-uXZORL.js",
"/assets/use-tab-direction-dKm-S3Ck.js",
"/assets/index-mKTXLmHu.js"
],
"css": []
}
},
"url": "/assets/manifest-e17deb94.js",
"version": "e17deb94"
};
window.__reactRouterRouteModules = {"root":route0,"routes/blog":route1,"routes/blog.$slug":route2};
import("/assets/entry.client-3vubyXrk.js");</script><script type="application/ld+json" nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0=">{"@context":"https://schema.org","@type":"Organization","name":"PlanetScale, Inc.","url":"https://planetscale.com","sameAs":["https://twitter.com/PlanetScale","https://www.facebook.com/planetscaledata/","https://www.instagram.com/planetscale/"],"address":{"@type":"PostalAddress","streetAddress":"WeWork c/o PlanetScale, 535 Mission Street, 14th Floor","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94105","addressCountry":"US"}}</script><!--$--><script nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0=">window.__reactRouterContext.streamController.enqueue("[{\"_1\":2,\"_3\":-5,\"_4\":-5},\"loaderData\",{\"_5\":6,\"_7\":8},\"actionData\",\"errors\",\"root\",{\"_1115\":1116},\"routes/blog.$slug\",{\"_9\":10,\"_5\":11},\"blog\",{\"_12\":13,\"_14\":15,\"_16\":-7,\"_17\":18,\"_19\":20,\"_21\":22,\"_23\":24,\"_25\":26,\"_27\":28,\"_29\":30,\"_31\":32},\"https://planetscale.com\",\"body\",[84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129],\"body_text\",\"How do you run non-blocking schema changes in MySQL? This is an eternal question. With a plethora of 3rd party solutions and with recent advancements in MySQL, it's difficult to track which solution is preferable for a given schema migration. In this post, we provide a high level overview of the state of MySQL online schema migrations in 2024. We limit the discussion to ALTER TABLE statements, as other DDL statements are typically fast (DROP TABLE is somewhat of an exception, but out of scope of this post).\\nWe'll first examine the native MySQL options: INPLACE and INSTANT. For reference, see Online DDL Operations MySQL 8.0 documentation.\\nINPLACE, aka InnoDB Online DDL\\nThis is MySQL's first take on non-blocking schema changes. Some types of ALTER TABLE (see above link for exhaustive list of supported changes) are eligible to run with ALGORITHM=INPLACE. An INPLACE schema change is technically non-blocking, with quite a few caveats:\\nOn the server where the query is submitted, normally the primary database server, DML queries (SELECT, INSERT, UPDATE, DELETE, ...) are non-blocking and may proceed to execute. Other DDL statements will block, and that's expected.\\nThe operation is resource greedy: the MySQL server will use as much CPU and disk I/O to complete the change as it can. This can and will impact performance on busy servers.\\nIt requires extra disk space, up to as much disk space as the original table.\\nIt is uninterruptible. The only way to abort is to kill the query aggressively. This then leads to a further massive cleanup operation, consuming more disk I/O.\\nOn replica servers, the operation is NOT non-blocking. Meaning if the ALTER TABLE took 3 hours on the primary server, then from the moment it completes you can expect replication to stall while applying that same change for the next 3 hours or so, creating a massive 3 hour lag.\\nThe replication issue is a deal breaker for most. One way around it is to run the ALTER TABLE on the primary with SQL_LOG_BIN=0 so that it does not replicate. Then, run it similarly individually on each replica. This technique works, but can be the cause for inconsistencies. Did you track all servers? What if you subsequently restore (or bootstrap) a server from backup, where the change never took place? How do you track that? Moreover, this technique will take n times longer to complete, as you need to run the change individually for each server. You may parallelize some of the work, but probably not all of it.\\nINPLACE conclusions\\nFor these reasons we find that INPLACE is not a good option for non-blocking changes.\\nINSTANT schema changes\\nInstant schema changes are almost a holy grail in the world of databases, and where it works, it's the next best thing after pizza (pending any bugs). MySQL offers support for some schema changes to run with ALGORITHM=INSTANT. Originally contributed to MySQL by Tencent six years ago, INSTANT DDL only supported a single type of change: ADD COLUMN. Later on MySQL added support for more changes, like expanding an enum column, or adding and dropping VIRTUAL columns. Recently (one year ago), MySQL 8.0.29 added support for arbitrary ADD COLUMN and DROP COLUMN support.\\nINSTANT truly runs instantly. It does not need to copy a table, does not need extra disk space, does not hammer the CPU. There's nothing to interrupt because the operation terminates before you've blinked. It also runs instantly on the replicas.\\nIt sounds perfect! And it mostly is, where supported, and with a bit of nuance. Consider again the documentation for supported operations. Looking closely, you can see these types of supported changes:\\nChanging a column default value.\\nAdding/removing VIRTUAL columns.\\nModify an enum definition.\\nAnd more.\\nWhat's shared to these changes is that they're all metadata changes. They do not affect existing rows, do not modify the data, do not restructure the table, do not affect indexes. ADD COLUMN \u0026 DROP COLUMN are the only supported changes that actually affect table data or how the data is structured. As another caveat, you cannot DROP COLUMN using INSTANT DDL if that column participates in an index.\\nSo you can change a column's default value from 0 to 1, but you cannot make a nullable column non-nullable. You can add and drop GENERATED VIRTUAL columns, but cannot add and drop GENERATED STORED columns. You can modify an enum definition, but you cannot modify a column's type from int to bigint.\\nThe list of unsupported changes includes:\\nChanging a column's data type.\\nAdding a column with non-literal default value.\\nAdding indexes.\\nModifying a PRIMARY KEY definition.\\nAdding/removing foreign keys.\\nChanging a table's character set.\\nMaking partitioning changes.\\nWhich is to say, there's a long way to go before INSTANT DDL can satisfy the common needs of schema changes. Where possible, INSTANT DDL is wonderful, and in many situations is the preferable and recommended way to go.\\nINSTANT risks\\nINSTANT first appears to be risk-free. In most situations, it is! Even if you make a mistake, it can be corrected with a counter-INSTANT operation. That's true for most changes, except when data is destroyed. At this time, the one destructive statement support by INSTANT DDL is DROP COLUMN (for \\\"real\\\", non VIRTUAL columns).\\nDropping a column has two main risks to it:\\nThe obvious risk of losing important data, if executed prematurely or accidentally.\\nThe risk of breaking existing queries.\\nLosing data can obviously be a massive incident, the cause for outage and for long hours or days of recovery. But why is this an INSTANT risk in particular? It's the same damage whether INSTANT or not, right?\\nThe answer is with the human behavior of always choosing INSTANT where possible. You're an instant away from destroying your data, and with no barriers to hold you back, nor a mechanism (short of backups and delayed replicas) to take you back to safety. We'll discuss this shortly as we introduce the concept of Revertibility (specifically in Vitess).\\nHow about breaking existing queries? Maybe the data is truly expendable, or safely aggregated elsewhere, but perhaps a bunch of SELECT or INSERT queries still reference the column?\\nMySQL offers invisible columns as a means to emulate how your table might look like without a given column. However, it is limited. It only affects queries that do not explicitly use the column name, such as SELECT * FROM my_table .... or INSERT INTO my_table VALUE (...). But any SELECT the_column FROM my_table query still has full access to columns. In today's world, SELECT * and blind INSERT queries are not as common. Frameworks, tooling, and modern engineering paradigms all tend to be explicit and fully qualified. Invisible columns does not help here.\\nIf dropping the column did cause queries to break, you will then need to either fix all the queries, or attempt to re-introduce the column. Let's now discuss revertibility.\\nRevertibility\\nWhat are your options for undoing a change? For switching back to the previous schema? Let's illustrate using two simple examples.\\nSay your change was to ALTER TABLE my_table ADD COLUMN name .... This looks harmless, and yet can cause downtime. name can be a common column name. Queries selecting name in a multi-table statement, such as SELECT name, value FROM my_table JOIN another_table USING ..., could fail due to the new ambiguity of name column.\\nThe anti-change for ADD COLUMN is a DROP COLUMN, and since both are supported by INSTANT DDL, chances are you'll be able to recover quickly and relatively safely.\\nWhat if your change was a DROP COLUMN? Lost data aside, what is the anti-change you'd apply to restore the previous schema? Not only data was lost, but also metadata. What was the column type? Length? Was it nullable? That information cannot be inferred unless you have the previous schema. In all likelihood, you use version control to manage your schema and are thus able to extract the previous definition. It is worth pointing out, though, that crafting the anti-change of a schema migration is nontrivial.\\nINSTANT conclusions\\nWhere possible, INSTANT is often the best approach for making online schema changes. However, it is too limited at this time and does not support the majority of common schema changes. It does not provide revertibility in case of data destruction. The MySQL team does not publish concrete plans for INSTANT DDL support in future versions of MySQL.\\nSolutions external to MySQL\\nA number of 3rd party tools is available today for running online schema changes for MySQL. We will focus on Vitess, the technology behind PlanetScale's non-blocking schema changes. Other 3rd party tools include gh-ost, pt-online-schema-change, recent newcomer spirit, and others.\\nThese tools all share a similar basic design, but operate differently. The major characteristics share to all are:\\nThey mimic an ALTER TABLE by creating a shadow table with the new schema and slowly copying over data.\\nThey can and often will take longer time to complete as compared with a native MySQL ALTER TABLE.\\nThey require extra disk space, about as much as the existing table (less if you consider fragmentation, more if you're adding bloated indexes, etc.)\\nThey cause binary log bloating (essentially the entire table content goes through the binary logs).\\nThey respect production workload, and will pause or throttle as needed so as to give way to production traffic (hence they're likely to run longer).\\nThey operate in small batches of changes, hence are able to keep replication lag to a minimum (and throttle based on lag).\\nThey are interruptible: the operation can be aborted at no immediate cost (cleanup can be done at a later stage).\\nThey are capable of handling almost every single kind of schema change.\\nMost have foreign key limitations.\\nSome partitioning options are not recommended, or are plain incorrect to run using these tools.\\nTo put it out of the way: if your table has a color enum('red','green','blue') column, and you want to add a new enum value, making it color enum('red','green','blue','orange'), you're better off using INSTANT DDL. There are a handful such cases, that are supported by INSTANT DDL as mentioned above, and where it just doesn't make sense to spend hours of migration.\\nHowever, for the (still vast) majority of changes, these are still the go-to solutions. First, of course, we've already established that neither INSTANT nor INPLACE cover all types of changes (they cover a minority of possible changes). But this also leads to an emerging behavior: maintaining two different techniques in your flow/automation creates more complexity. If you already have to use one of the 3rd party solutions, you may as well use it all the time.\\nBoth vitess and spirit go an extra mile and can auto detect when a migration can be fulfilled using INSTANT DDL, which means you don't need to think about it or be aware of which particular version supports which changes.\\nvitess further supports revertibility as first class citizen, able to not only revert back to the original schema, but also to preserve the would-be lost data, while still accounting for any newly added, updated, or removed data since the change.\\nNote on partitioning\\nPartitioning is a strange beast, and implemented in MySQL by creating a \\\"small\\\" table per partition. As such, operations on partitions are really operations on sets of tables. Some partitioning related changes should only be served by MySQL. Such is a DROP PARTITION statement for e.g. RANGE partitioned table. Some other partitioning changes are better served by MySQL, and some are best served by online schema change tools.\\n3rd party conclusions\\nMost use cases are best served (or only well served) by 3rd party online schema change tools, and those are still the way to go for the foreseeable future.\",\"aside\",\"toc\",[43,44,45],\"title\",\"The State of Online Schema Migrations in MySQL\",\"authors\",[39],\"categories\",[38],\"excerpt\",\"Learn about the options for running non-blocking schema changes natively to MySQL, using Vitess, or other tools\",\"createdAt\",\"2024-07-23\",\"slug\",\"state-of-online-schema-migrations-in-mysql\",\"meta\",{\"_33\":34,\"_35\":26,\"_36\":37,\"_19\":20},\"canonical\",\"https://planetscale.com/blog/state-of-online-schema-migrations-in-mysql\",\"description\",\"image\",\"/assets/state-of-online-schema-migrations-in-mysql-social-CrLOQ2AR.jpg\",\"engineering\",{\"_29\":40,\"_41\":42},\"shlomi\",\"name\",\"Shlomi Noach\",{\"_46\":77,\"_48\":78,\"_50\":51,\"_19\":79},{\"_46\":62,\"_48\":63,\"_50\":51,\"_19\":64},{\"_46\":47,\"_48\":49,\"_50\":51,\"_19\":52},\"children\",[53,54],\"id\",\"solutions-external-to-mysql\",\"level\",2,\"Solutions external to MySQL\",{\"_46\":59,\"_48\":60,\"_50\":57,\"_19\":61},{\"_46\":55,\"_48\":56,\"_50\":57,\"_19\":58},[],\"3rd-party-conclusions\",3,\"3rd party conclusions\",[],\"note-on-partitioning\",\"Note on partitioning\",[65,66,67],\"instant-schema-changes\",\"INSTANT schema changes\",{\"_46\":74,\"_48\":75,\"_50\":57,\"_19\":76},{\"_46\":71,\"_48\":72,\"_50\":57,\"_19\":73},{\"_46\":68,\"_48\":69,\"_50\":57,\"_19\":70},[],\"instant-conclusions\",\"INSTANT conclusions\",[],\"revertibility\",\"Revertibility\",[],\"instant-risks\",\"INSTANT risks\",[80],\"inplace-aka-innodb-online-ddl\",\"INPLACE, aka InnoDB Online DDL\",{\"_46\":81,\"_48\":82,\"_50\":57,\"_19\":83},[],\"inplace-conclusions\",\"INPLACE conclusions\",[\"SingleFetchClassInstance\",1094],[\"SingleFetchClassInstance\",1074],[\"SingleFetchClassInstance\",1061],[\"SingleFetchClassInstance\",1035],[\"SingleFetchClassInstance\",983],[\"SingleFetchClassInstance\",962],[\"SingleFetchClassInstance\",950],[\"SingleFetchClassInstance\",941],[\"SingleFetchClassInstance\",928],[\"SingleFetchClassInstance\",870],[\"SingleFetchClassInstance\",862],[\"SingleFetchClassInstance\",858],[\"SingleFetchClassInstance\",826],[\"SingleFetchClassInstance\",802],[\"SingleFetchClassInstance\",727],[\"SingleFetchClassInstance\",723],[\"SingleFetchClassInstance\",679],[\"SingleFetchClassInstance\",665],[\"SingleFetchClassInstance\",652],[\"SingleFetchClassInstance\",623],[\"SingleFetchClassInstance\",619],[\"SingleFetchClassInstance\",605],[\"SingleFetchClassInstance\",591],[\"SingleFetchClassInstance\",576],[\"SingleFetchClassInstance\",562],[\"SingleFetchClassInstance\",521],[\"SingleFetchClassInstance\",517],[\"SingleFetchClassInstance\",509],[\"SingleFetchClassInstance\",505],[\"SingleFetchClassInstance\",469],[\"SingleFetchClassInstance\",449],[\"SingleFetchClassInstance\",433],[\"SingleFetchClassInstance\",420],[\"SingleFetchClassInstance\",406],[\"SingleFetchClassInstance\",397],[\"SingleFetchClassInstance\",359],[\"SingleFetchClassInstance\",341],[\"SingleFetchClassInstance\",265],[\"SingleFetchClassInstance\",239],[\"SingleFetchClassInstance\",224],[\"SingleFetchClassInstance\",203],[\"SingleFetchClassInstance\",187],[\"SingleFetchClassInstance\",179],[\"SingleFetchClassInstance\",149],[\"SingleFetchClassInstance\",138],[\"SingleFetchClassInstance\",130],{\"_131\":132,\"_41\":133,\"_134\":135,\"_46\":136},\"$$mdtype\",\"Tag\",\"p\",\"attributes\",{},[137],\"Most use cases are best served (or only well served) by 3rd party online schema change tools, and those are still the way to go for the foreseeable future.\",{\"_131\":132,\"_41\":139,\"_134\":140,\"_46\":141},\"h3\",{\"_48\":56},[142],[\"SingleFetchClassInstance\",143],{\"_131\":132,\"_41\":144,\"_134\":145,\"_46\":146},\"a\",{\"_147\":148},[58],\"href\",\"#3rd-party-conclusions\",{\"_131\":132,\"_41\":133,\"_134\":150,\"_46\":151},{},[152,153,154,155,156,157,158,159,160],\"Partitioning is a strange beast, and implemented in MySQL by creating a \\\"small\\\" table per partition. As such, operations on partitions are really operations on sets of tables. Some partitioning related changes should \",[\"SingleFetchClassInstance\",175],\" be served by MySQL. Such is a \",[\"SingleFetchClassInstance\",171],\" statement for e.g. \",[\"SingleFetchClassInstance\",166],\" partitioned table. Some other partitioning changes are \",[\"SingleFetchClassInstance\",161],\" served by MySQL, and some are best served by online schema change tools.\",{\"_131\":132,\"_41\":162,\"_134\":163,\"_46\":164},\"em\",{},[165],\"better\",{\"_131\":132,\"_41\":167,\"_134\":168,\"_46\":169},\"code\",{},[170],\"RANGE\",{\"_131\":132,\"_41\":167,\"_134\":172,\"_46\":173},{},[174],\"DROP PARTITION\",{\"_131\":132,\"_41\":162,\"_134\":176,\"_46\":177},{},[178],\"only\",{\"_131\":132,\"_41\":139,\"_134\":180,\"_46\":181},{\"_48\":60},[182],[\"SingleFetchClassInstance\",183],{\"_131\":132,\"_41\":144,\"_134\":184,\"_46\":185},{\"_147\":186},[61],\"#note-on-partitioning\",{\"_131\":132,\"_41\":133,\"_134\":188,\"_46\":189},{},[190,191,192,193],[\"SingleFetchClassInstance\",199],\" further \",[\"SingleFetchClassInstance\",194],\" as first class citizen, able to not only revert back to the original schema, but also to preserve the would-be lost data, while still accounting for any newly added, updated, or removed data since the change.\",{\"_131\":132,\"_41\":144,\"_134\":195,\"_46\":196},{\"_147\":198},[197],\"supports revertibility\",\"https://vitess.io/docs/user-guides/schema-changes/revertible-migrations/\",{\"_131\":132,\"_41\":167,\"_134\":200,\"_46\":201},{},[202],\"vitess\",{\"_131\":132,\"_41\":133,\"_134\":204,\"_46\":205},{},[206,207,208,209,210,211,212],\"Both \",[\"SingleFetchClassInstance\",221],\" and \",[\"SingleFetchClassInstance\",217],\" go an extra mile and can auto detect when a migration can be fulfilled using \",[\"SingleFetchClassInstance\",213],\" DDL, which means you don't need to think about it or be aware of which particular version supports which changes.\",{\"_131\":132,\"_41\":167,\"_134\":214,\"_46\":215},{},[216],\"INSTANT\",{\"_131\":132,\"_41\":167,\"_134\":218,\"_46\":219},{},[220],\"spirit\",{\"_131\":132,\"_41\":167,\"_134\":222,\"_46\":223},{},[202],{\"_131\":132,\"_41\":133,\"_134\":225,\"_46\":226},{},[227,228,229,230,231],\"However, for the (still vast) majority of changes, these are still the go-to solutions. First, of course, we've already established that neither \",[\"SingleFetchClassInstance\",236],\" nor \",[\"SingleFetchClassInstance\",232],\" cover all types of changes (they cover a minority of possible changes). But this also leads to an emerging behavior: maintaining two different techniques in your flow/automation creates more complexity. If you already have to use one of the 3rd party solutions, you may as well use it all the time.\",{\"_131\":132,\"_41\":167,\"_134\":233,\"_46\":234},{},[235],\"INPLACE\",{\"_131\":132,\"_41\":167,\"_134\":237,\"_46\":238},{},[216],{\"_131\":132,\"_41\":133,\"_134\":240,\"_46\":241},{},[242,243,244,245,246,247,248,249,250],\"To put it out of the way: if your table has a \",[\"SingleFetchClassInstance\",261],\" column, and you want to add a new enum value, making it \",[\"SingleFetchClassInstance\",257],\", you're better off using \",[\"SingleFetchClassInstance\",254],\" DDL. There are a handful such cases, that are supported by \",[\"SingleFetchClassInstance\",251],\" DDL as mentioned above, and where it just doesn't make sense to spend hours of migration.\",{\"_131\":132,\"_41\":167,\"_134\":252,\"_46\":253},{},[216],{\"_131\":132,\"_41\":167,\"_134\":255,\"_46\":256},{},[216],{\"_131\":132,\"_41\":167,\"_134\":258,\"_46\":259},{},[260],\"color enum('red','green','blue','orange')\",{\"_131\":132,\"_41\":167,\"_134\":262,\"_46\":263},{},[264],\"color enum('red','green','blue')\",{\"_131\":132,\"_41\":266,\"_134\":267,\"_46\":268},\"ul\",{},[269,270,271,272,273,274,275,276],[\"SingleFetchClassInstance\",326],[\"SingleFetchClassInstance\",316],[\"SingleFetchClassInstance\",312],[\"SingleFetchClassInstance\",308],[\"SingleFetchClassInstance\",304],[\"SingleFetchClassInstance\",300],[\"SingleFetchClassInstance\",296],[\"SingleFetchClassInstance\",277],{\"_131\":132,\"_41\":278,\"_134\":279,\"_46\":280},\"li\",{},[281,282],\"They are capable of handling almost every single kind of schema change.\",[\"SingleFetchClassInstance\",283],{\"_131\":132,\"_41\":266,\"_134\":284,\"_46\":285},{},[286,287],[\"SingleFetchClassInstance\",292],[\"SingleFetchClassInstance\",288],{\"_131\":132,\"_41\":278,\"_134\":289,\"_46\":290},{},[291],\"Some partitioning options are not recommended, or are plain incorrect to run using these tools.\",{\"_131\":132,\"_41\":278,\"_134\":293,\"_46\":294},{},[295],\"Most have foreign key limitations.\",{\"_131\":132,\"_41\":278,\"_134\":297,\"_46\":298},{},[299],\"They are interruptible: the operation can be aborted at no immediate cost (cleanup can be done at a later stage).\",{\"_131\":132,\"_41\":278,\"_134\":301,\"_46\":302},{},[303],\"They operate in small batches of changes, hence are able to keep replication lag to a minimum (and throttle based on lag).\",{\"_131\":132,\"_41\":278,\"_134\":305,\"_46\":306},{},[307],\"They respect production workload, and will pause or throttle as needed so as to give way to production traffic (hence they're likely to run longer).\",{\"_131\":132,\"_41\":278,\"_134\":309,\"_46\":310},{},[311],\"They cause binary log bloating (essentially the entire table content goes through the binary logs).\",{\"_131\":132,\"_41\":278,\"_134\":313,\"_46\":314},{},[315],\"They require extra disk space, about as much as the existing table (less if you consider fragmentation, more if you're adding bloated indexes, etc.)\",{\"_131\":132,\"_41\":278,\"_134\":317,\"_46\":318},{},[319,320,321],\"They can and often will take longer time to complete as compared with a native MySQL \",[\"SingleFetchClassInstance\",322],\".\",{\"_131\":132,\"_41\":167,\"_134\":323,\"_46\":324},{},[325],\"ALTER TABLE\",{\"_131\":132,\"_41\":278,\"_134\":327,\"_46\":328},{},[329,330,331,332,333],\"They mimic an \",[\"SingleFetchClassInstance\",338],\" by creating a \",[\"SingleFetchClassInstance\",334],\" table with the new schema and slowly copying over data.\",{\"_131\":132,\"_41\":162,\"_134\":335,\"_46\":336},{},[337],\"shadow\",{\"_131\":132,\"_41\":167,\"_134\":339,\"_46\":340},{},[325],{\"_131\":132,\"_41\":133,\"_134\":342,\"_46\":343},{},[344,345,346,347,348],\"These tools all share a \",[\"SingleFetchClassInstance\",354],\", but \",[\"SingleFetchClassInstance\",349],\". The major characteristics share to all are:\",{\"_131\":132,\"_41\":144,\"_134\":350,\"_46\":351},{\"_147\":353},[352],\"operate differently\",\"/docs/vitess/schema-changes/online-schema-change-tools-comparison\",{\"_131\":132,\"_41\":144,\"_134\":355,\"_46\":356},{\"_147\":358},[357],\"similar basic design\",\"/docs/vitess/schema-changes/how-online-schema-change-tools-work\",{\"_131\":132,\"_41\":133,\"_134\":360,\"_46\":361},{},[362,363,364,365,366,367,368,369,370,371,372],\"A number of 3rd party tools is available today for running online schema changes for MySQL. We will focus on \",[\"SingleFetchClassInstance\",392],\", the technology behind PlanetScale's \",[\"SingleFetchClassInstance\",387],\". Other 3rd party tools include \",[\"SingleFetchClassInstance\",382],\", \",[\"SingleFetchClassInstance\",377],\", recent newcomer \",[\"SingleFetchClassInstance\",373],\", and others.\",{\"_131\":132,\"_41\":144,\"_134\":374,\"_46\":375},{\"_147\":376},[220],\"https://github.com/cashapp/spirit\",{\"_131\":132,\"_41\":144,\"_134\":378,\"_46\":379},{\"_147\":381},[380],\"pt-online-schema-change\",\"https://www.percona.com/doc/percona-toolkit/3.0/pt-online-schema-change.html\",{\"_131\":132,\"_41\":144,\"_134\":383,\"_46\":384},{\"_147\":386},[385],\"gh-ost\",\"https://github.com/github/gh-ost\",{\"_131\":132,\"_41\":144,\"_134\":388,\"_46\":389},{\"_147\":391},[390],\"non-blocking schema changes\",\"/docs/vitess/schema-changes\",{\"_131\":132,\"_41\":144,\"_134\":393,\"_46\":394},{\"_147\":396},[395],\"Vitess\",\"https://vitess.io/docs/user-guides/schema-changes/managed-online-schema-changes/\",{\"_131\":132,\"_41\":398,\"_134\":399,\"_46\":400},\"h2\",{\"_48\":49},[401],[\"SingleFetchClassInstance\",402],{\"_131\":132,\"_41\":144,\"_134\":403,\"_46\":404},{\"_147\":405},[52],\"#solutions-external-to-mysql\",{\"_131\":132,\"_41\":133,\"_134\":407,\"_46\":408},{},[409,410,411,412,413],\"Where possible, \",[\"SingleFetchClassInstance\",417],\" is often the best approach for making online schema changes. However, it is too limited at this time and does not support the majority of common schema changes. It does not provide revertibility in case of data destruction. The MySQL team does not publish concrete plans for \",[\"SingleFetchClassInstance\",414],\" DDL support in future versions of MySQL.\",{\"_131\":132,\"_41\":167,\"_134\":415,\"_46\":416},{},[216],{\"_131\":132,\"_41\":167,\"_134\":418,\"_46\":419},{},[216],{\"_131\":132,\"_41\":139,\"_134\":421,\"_46\":422},{\"_48\":69},[423],[\"SingleFetchClassInstance\",424],{\"_131\":132,\"_41\":144,\"_134\":425,\"_46\":426},{\"_147\":432},[427,428],[\"SingleFetchClassInstance\",429],\" conclusions\",{\"_131\":132,\"_41\":167,\"_134\":430,\"_46\":431},{},[216],\"#instant-conclusions\",{\"_131\":132,\"_41\":133,\"_134\":434,\"_46\":435},{},[436,437,438,439,440],\"What if your change was a \",[\"SingleFetchClassInstance\",445],\"? Lost data aside, what is the anti-change you'd apply to restore the previous schema? Not only data was lost, but also metadata. What was the column type? Length? Was it nullable? That information cannot be inferred unless you \",[\"SingleFetchClassInstance\",441],\" the previous schema. In all likelihood, you use version control to manage your schema and are thus able to extract the previous definition. It is worth pointing out, though, that crafting the anti-change of a schema migration is nontrivial.\",{\"_131\":132,\"_41\":162,\"_134\":442,\"_46\":443},{},[444],\"have\",{\"_131\":132,\"_41\":167,\"_134\":446,\"_46\":447},{},[448],\"DROP COLUMN\",{\"_131\":132,\"_41\":133,\"_134\":450,\"_46\":451},{},[452,453,454,455,456,457,458],\"The anti-change for \",[\"SingleFetchClassInstance\",465],\" is a \",[\"SingleFetchClassInstance\",462],\", and since both are supported by \",[\"SingleFetchClassInstance\",459],\" DDL, chances are you'll be able to recover quickly and relatively safely.\",{\"_131\":132,\"_41\":167,\"_134\":460,\"_46\":461},{},[216],{\"_131\":132,\"_41\":167,\"_134\":463,\"_46\":464},{},[448],{\"_131\":132,\"_41\":167,\"_134\":466,\"_46\":467},{},[468],\"ADD COLUMN\",{\"_131\":132,\"_41\":133,\"_134\":470,\"_46\":471},{},[472,473,474,475,476,477,478,479,480,481,482,483,484],\"Say your change was to \",[\"SingleFetchClassInstance\",501],\". This looks harmless, and yet can cause downtime. \",[\"SingleFetchClassInstance\",498],\" can be a common column \",[\"SingleFetchClassInstance\",495],\". Queries selecting \",[\"SingleFetchClassInstance\",492],\" in a multi-table statement, such as \",[\"SingleFetchClassInstance\",488],\", could fail due to the new ambiguity of \",[\"SingleFetchClassInstance\",485],\" column.\",{\"_131\":132,\"_41\":167,\"_134\":486,\"_46\":487},{},[41],{\"_131\":132,\"_41\":167,\"_134\":489,\"_46\":490},{},[491],\"SELECT name, value FROM my_table JOIN another_table USING ...\",{\"_131\":132,\"_41\":167,\"_134\":493,\"_46\":494},{},[41],{\"_131\":132,\"_41\":162,\"_134\":496,\"_46\":497},{},[41],{\"_131\":132,\"_41\":167,\"_134\":499,\"_46\":500},{},[41],{\"_131\":132,\"_41\":167,\"_134\":502,\"_46\":503},{},[504],\"ALTER TABLE my_table ADD COLUMN name ...\",{\"_131\":132,\"_41\":133,\"_134\":506,\"_46\":507},{},[508],\"What are your options for undoing a change? For switching back to the previous schema? Let's illustrate using two simple examples.\",{\"_131\":132,\"_41\":139,\"_134\":510,\"_46\":511},{\"_48\":72},[512],[\"SingleFetchClassInstance\",513],{\"_131\":132,\"_41\":144,\"_134\":514,\"_46\":515},{\"_147\":516},[73],\"#revertibility\",{\"_131\":132,\"_41\":133,\"_134\":518,\"_46\":519},{},[520],\"If dropping the column did cause queries to break, you will then need to either fix all the queries, or attempt to re-introduce the column. Let's now discuss revertibility.\",{\"_131\":132,\"_41\":133,\"_134\":522,\"_46\":523},{},[524,525,526,527,528,529,530,531,532,533,534,535,536],\"MySQL offers \",[\"SingleFetchClassInstance\",557],\" as a means to emulate how your table might look like without a given column. However, it is limited. It only affects queries that do not explicitly use the column name, such as \",[\"SingleFetchClassInstance\",553],\" or \",[\"SingleFetchClassInstance\",549],\". But any \",[\"SingleFetchClassInstance\",545],\" query still has full access to columns. In today's world, \",[\"SingleFetchClassInstance\",541],\" and blind \",[\"SingleFetchClassInstance\",537],\" queries are not as common. Frameworks, tooling, and modern engineering paradigms all tend to be explicit and fully qualified. Invisible columns does not help here.\",{\"_131\":132,\"_41\":167,\"_134\":538,\"_46\":539},{},[540],\"INSERT\",{\"_131\":132,\"_41\":167,\"_134\":542,\"_46\":543},{},[544],\"SELECT *\",{\"_131\":132,\"_41\":167,\"_134\":546,\"_46\":547},{},[548],\"SELECT the_column FROM my_table\",{\"_131\":132,\"_41\":167,\"_134\":550,\"_46\":551},{},[552],\"INSERT INTO my_table VALUE (...)\",{\"_131\":132,\"_41\":167,\"_134\":554,\"_46\":555},{},[556],\"SELECT * FROM my_table ....\",{\"_131\":132,\"_41\":144,\"_134\":558,\"_46\":559},{\"_147\":561},[560],\"invisible columns\",\"https://dev.mysql.com/doc/refman/8.0/en/invisible-columns.html\",{\"_131\":132,\"_41\":133,\"_134\":563,\"_46\":564},{},[565,566,528,567,568],\"How about breaking existing queries? Maybe the data is truly expendable, or safely aggregated elsewhere, but perhaps a bunch of \",[\"SingleFetchClassInstance\",572],[\"SingleFetchClassInstance\",569],\" queries still reference the column?\",{\"_131\":132,\"_41\":167,\"_134\":570,\"_46\":571},{},[540],{\"_131\":132,\"_41\":167,\"_134\":573,\"_46\":574},{},[575],\"SELECT\",{\"_131\":132,\"_41\":133,\"_134\":577,\"_46\":578},{},[579,580,581,582,583],\"The answer is with the human behavior of always choosing \",[\"SingleFetchClassInstance\",588],\" where possible. You're an instant away from destroying your data, and with no barriers to hold you back, nor a mechanism (short of backups and delayed replicas) to take you back to safety. We'll discuss this shortly as we introduce the concept of Revertibility (specifically in \",[\"SingleFetchClassInstance\",584],\").\",{\"_131\":132,\"_41\":144,\"_134\":585,\"_46\":586},{\"_147\":587},[395],\"https://vitess.io\",{\"_131\":132,\"_41\":167,\"_134\":589,\"_46\":590},{},[216],{\"_131\":132,\"_41\":133,\"_134\":592,\"_46\":593},{},[594,595,596,597,598],\"Losing data can obviously be a massive incident, the cause for outage and for long hours or days of recovery. But why is this an \",[\"SingleFetchClassInstance\",602],\" risk in particular? It's the same damage whether \",[\"SingleFetchClassInstance\",599],\" or not, right?\",{\"_131\":132,\"_41\":167,\"_134\":600,\"_46\":601},{},[216],{\"_131\":132,\"_41\":167,\"_134\":603,\"_46\":604},{},[216],{\"_131\":132,\"_41\":606,\"_134\":607,\"_46\":608},\"ol\",{},[609,610],[\"SingleFetchClassInstance\",615],[\"SingleFetchClassInstance\",611],{\"_131\":132,\"_41\":278,\"_134\":612,\"_46\":613},{},[614],\"The risk of breaking existing queries.\",{\"_131\":132,\"_41\":278,\"_134\":616,\"_46\":617},{},[618],\"The obvious risk of losing important data, if executed prematurely or accidentally.\",{\"_131\":132,\"_41\":133,\"_134\":620,\"_46\":621},{},[622],\"Dropping a column has two main risks to it:\",{\"_131\":132,\"_41\":133,\"_134\":624,\"_46\":625},{},[626,627,628,629,630,631,632,633,634,635],[\"SingleFetchClassInstance\",649],\" first appears to be risk-free. In most situations, it is! Even if you make a mistake, it can be corrected with a counter-\",[\"SingleFetchClassInstance\",646],\" operation. That's true for most changes, except when data is destroyed. At this time, the one destructive statement support by \",[\"SingleFetchClassInstance\",643],\" DDL is \",[\"SingleFetchClassInstance\",640],\" (for \\\"real\\\", non \",[\"SingleFetchClassInstance\",636],\" columns).\",{\"_131\":132,\"_41\":167,\"_134\":637,\"_46\":638},{},[639],\"VIRTUAL\",{\"_131\":132,\"_41\":167,\"_134\":641,\"_46\":642},{},[448],{\"_131\":132,\"_41\":167,\"_134\":644,\"_46\":645},{},[216],{\"_131\":132,\"_41\":167,\"_134\":647,\"_46\":648},{},[216],{\"_131\":132,\"_41\":167,\"_134\":650,\"_46\":651},{},[216],{\"_131\":132,\"_41\":139,\"_134\":653,\"_46\":654},{\"_48\":75},[655],[\"SingleFetchClassInstance\",656],{\"_131\":132,\"_41\":144,\"_134\":657,\"_46\":658},{\"_147\":664},[659,660],[\"SingleFetchClassInstance\",661],\" risks\",{\"_131\":132,\"_41\":167,\"_134\":662,\"_46\":663},{},[216],\"#instant-risks\",{\"_131\":132,\"_41\":133,\"_134\":666,\"_46\":667},{},[668,669,670,671,672],\"Which is to say, there's a long way to go before \",[\"SingleFetchClassInstance\",676],\" DDL can satisfy the common needs of schema changes. Where possible, \",[\"SingleFetchClassInstance\",673],\" DDL is wonderful, and in many situations is the preferable and recommended way to go.\",{\"_131\":132,\"_41\":167,\"_134\":674,\"_46\":675},{},[216],{\"_131\":132,\"_41\":167,\"_134\":677,\"_46\":678},{},[216],{\"_131\":132,\"_41\":266,\"_134\":680,\"_46\":681},{},[682,683,684,685,686,687,688],[\"SingleFetchClassInstance\",719],[\"SingleFetchClassInstance\",715],[\"SingleFetchClassInstance\",711],[\"SingleFetchClassInstance\",701],[\"SingleFetchClassInstance\",697],[\"SingleFetchClassInstance\",693],[\"SingleFetchClassInstance\",689],{\"_131\":132,\"_41\":278,\"_134\":690,\"_46\":691},{},[692],\"Making partitioning changes.\",{\"_131\":132,\"_41\":278,\"_134\":694,\"_46\":695},{},[696],\"Changing a table's character set.\",{\"_131\":132,\"_41\":278,\"_134\":698,\"_46\":699},{},[700],\"Adding/removing foreign keys.\",{\"_131\":132,\"_41\":278,\"_134\":702,\"_46\":703},{},[704,705,706],\"Modifying a \",[\"SingleFetchClassInstance\",707],\" definition.\",{\"_131\":132,\"_41\":167,\"_134\":708,\"_46\":709},{},[710],\"PRIMARY KEY\",{\"_131\":132,\"_41\":278,\"_134\":712,\"_46\":713},{},[714],\"Adding indexes.\",{\"_131\":132,\"_41\":278,\"_134\":716,\"_46\":717},{},[718],\"Adding a column with non-literal default value.\",{\"_131\":132,\"_41\":278,\"_134\":720,\"_46\":721},{},[722],\"Changing a column's data type.\",{\"_131\":132,\"_41\":133,\"_134\":724,\"_46\":725},{},[726],\"The list of unsupported changes includes:\",{\"_131\":132,\"_41\":133,\"_134\":728,\"_46\":729},{},[730,731,732,733,734,735,736,737,738,739,740,741,742,743,740,744,745,746,747,748,749,750,751,752,734,753,321],\"So you \",[\"SingleFetchClassInstance\",799],\" change a column's default value from \",[\"SingleFetchClassInstance\",795],\" to \",[\"SingleFetchClassInstance\",791],\", but you \",[\"SingleFetchClassInstance\",788],\" make a nullable column non-nullable. You \",[\"SingleFetchClassInstance\",785],\" add and drop \",[\"SingleFetchClassInstance\",781],\" columns, but \",[\"SingleFetchClassInstance\",778],[\"SingleFetchClassInstance\",774],\" columns. You \",[\"SingleFetchClassInstance\",770],\" modify an \",[\"SingleFetchClassInstance\",766],\" definition, but you \",[\"SingleFetchClassInstance\",762],\" modify a column's type from \",[\"SingleFetchClassInstance\",758],[\"SingleFetchClassInstance\",754],{\"_131\":132,\"_41\":167,\"_134\":755,\"_46\":756},{},[757],\"bigint\",{\"_131\":132,\"_41\":167,\"_134\":759,\"_46\":760},{},[761],\"int\",{\"_131\":132,\"_41\":162,\"_134\":763,\"_46\":764},{},[765],\"cannot\",{\"_131\":132,\"_41\":167,\"_134\":767,\"_46\":768},{},[769],\"enum\",{\"_131\":132,\"_41\":162,\"_134\":771,\"_46\":772},{},[773],\"can\",{\"_131\":132,\"_41\":167,\"_134\":775,\"_46\":776},{},[777],\"GENERATED STORED\",{\"_131\":132,\"_41\":162,\"_134\":779,\"_46\":780},{},[765],{\"_131\":132,\"_41\":167,\"_134\":782,\"_46\":783},{},[784],\"GENERATED VIRTUAL\",{\"_131\":132,\"_41\":162,\"_134\":786,\"_46\":787},{},[773],{\"_131\":132,\"_41\":162,\"_134\":789,\"_46\":790},{},[765],{\"_131\":132,\"_41\":167,\"_134\":792,\"_46\":793},{},[794],\"1\",{\"_131\":132,\"_41\":167,\"_134\":796,\"_46\":797},{},[798],\"0\",{\"_131\":132,\"_41\":162,\"_134\":800,\"_46\":801},{},[773],{\"_131\":132,\"_41\":133,\"_134\":803,\"_46\":804},{},[805,806,807,808,809,810,811,812,813],\"What's shared to these changes is that they're all metadata changes. They do not affect existing rows, do not modify the data, do not restructure the table, do not affect indexes. \",[\"SingleFetchClassInstance\",823],\" \u0026 \",[\"SingleFetchClassInstance\",820],\" are the only supported changes that actually affect table data or how the data is structured. As another caveat, you cannot \",[\"SingleFetchClassInstance\",817],\" using \",[\"SingleFetchClassInstance\",814],\" DDL if that column participates in an index.\",{\"_131\":132,\"_41\":167,\"_134\":815,\"_46\":816},{},[216],{\"_131\":132,\"_41\":167,\"_134\":818,\"_46\":819},{},[448],{\"_131\":132,\"_41\":167,\"_134\":821,\"_46\":822},{},[448],{\"_131\":132,\"_41\":167,\"_134\":824,\"_46\":825},{},[468],{\"_131\":132,\"_41\":266,\"_134\":827,\"_46\":828},{},[829,830,831,832],[\"SingleFetchClassInstance\",854],[\"SingleFetchClassInstance\",845],[\"SingleFetchClassInstance\",837],[\"SingleFetchClassInstance\",833],{\"_131\":132,\"_41\":278,\"_134\":834,\"_46\":835},{},[836],\"And more.\",{\"_131\":132,\"_41\":278,\"_134\":838,\"_46\":839},{},[840,841,706],\"Modify an \",[\"SingleFetchClassInstance\",842],{\"_131\":132,\"_41\":167,\"_134\":843,\"_46\":844},{},[769],{\"_131\":132,\"_41\":278,\"_134\":846,\"_46\":847},{},[848,849,850],\"Adding/removing \",[\"SingleFetchClassInstance\",851],\" columns.\",{\"_131\":132,\"_41\":167,\"_134\":852,\"_46\":853},{},[639],{\"_131\":132,\"_41\":278,\"_134\":855,\"_46\":856},{},[857],\"Changing a column default value.\",{\"_131\":132,\"_41\":133,\"_134\":859,\"_46\":860},{},[861],\"It sounds perfect! And it mostly is, where supported, and with a bit of nuance. Consider again the documentation for supported operations. Looking closely, you can see these types of supported changes:\",{\"_131\":132,\"_41\":133,\"_134\":863,\"_46\":864},{},[865,866],[\"SingleFetchClassInstance\",867],\" truly runs instantly. It does not need to copy a table, does not need extra disk space, does not hammer the CPU. There's nothing to interrupt because the operation terminates before you've blinked. It also runs instantly on the replicas.\",{\"_131\":132,\"_41\":167,\"_134\":868,\"_46\":869},{},[216],{\"_131\":132,\"_41\":133,\"_134\":871,\"_46\":872},{},[873,874,875,876,877,878,879,880,881,882,883,884,885,886,887,888,889,890,208,891,892],\"Instant schema changes are almost a holy grail in the world of databases, and where it works, it's the next best thing after pizza (pending any bugs). MySQL offers support for \",[\"SingleFetchClassInstance\",924],\" schema changes to run with \",[\"SingleFetchClassInstance\",920],\". \",[\"SingleFetchClassInstance\",915],\" six years ago, \",[\"SingleFetchClassInstance\",912],\" DDL only supported a single type of change: \",[\"SingleFetchClassInstance\",909],\". Later on MySQL added support for more changes, like expanding an \",[\"SingleFetchClassInstance\",906],\" column, or adding and dropping \",[\"SingleFetchClassInstance\",903],\" columns. Recently (one year ago), MySQL \",[\"SingleFetchClassInstance\",899],\" added support for arbitrary \",[\"SingleFetchClassInstance\",896],[\"SingleFetchClassInstance\",893],\" support.\",{\"_131\":132,\"_41\":167,\"_134\":894,\"_46\":895},{},[448],{\"_131\":132,\"_41\":167,\"_134\":897,\"_46\":898},{},[468],{\"_131\":132,\"_41\":167,\"_134\":900,\"_46\":901},{},[902],\"8.0.29\",{\"_131\":132,\"_41\":167,\"_134\":904,\"_46\":905},{},[639],{\"_131\":132,\"_41\":167,\"_134\":907,\"_46\":908},{},[769],{\"_131\":132,\"_41\":167,\"_134\":910,\"_46\":911},{},[468],{\"_131\":132,\"_41\":167,\"_134\":913,\"_46\":914},{},[216],{\"_131\":132,\"_41\":144,\"_134\":916,\"_46\":917},{\"_147\":919},[918],\"Originally contributed to MySQL by Tencent\",\"https://dev.mysql.com/blog-archive/mysql-8-0-innodb-now-supports-instant-add-column/\",{\"_131\":132,\"_41\":167,\"_134\":921,\"_46\":922},{},[923],\"ALGORITHM=INSTANT\",{\"_131\":132,\"_41\":162,\"_134\":925,\"_46\":926},{},[927],\"some\",{\"_131\":132,\"_41\":398,\"_134\":929,\"_46\":930},{\"_48\":63},[931],[\"SingleFetchClassInstance\",932],{\"_131\":132,\"_41\":144,\"_134\":933,\"_46\":934},{\"_147\":940},[935,936],[\"SingleFetchClassInstance\",937],\" schema changes\",{\"_131\":132,\"_41\":167,\"_134\":938,\"_46\":939},{},[216],\"#instant-schema-changes\",{\"_131\":132,\"_41\":133,\"_134\":942,\"_46\":943},{},[944,945,946],\"For these reasons we find that \",[\"SingleFetchClassInstance\",947],\" is not a good option for non-blocking changes.\",{\"_131\":132,\"_41\":167,\"_134\":948,\"_46\":949},{},[235],{\"_131\":132,\"_41\":139,\"_134\":951,\"_46\":952},{\"_48\":82},[953],[\"SingleFetchClassInstance\",954],{\"_131\":132,\"_41\":144,\"_134\":955,\"_46\":956},{\"_147\":961},[957,428],[\"SingleFetchClassInstance\",958],{\"_131\":132,\"_41\":167,\"_134\":959,\"_46\":960},{},[235],\"#inplace-conclusions\",{\"_131\":132,\"_41\":133,\"_134\":963,\"_46\":964},{},[965,966,967,968,969,970,971],\"The replication issue is a deal breaker for most. One way around it is to run the \",[\"SingleFetchClassInstance\",980],\" on the primary with \",[\"SingleFetchClassInstance\",976],\" so that it does not replicate. Then, run it similarly individually on each replica. This technique works, but can be the cause for inconsistencies. Did you track all servers? What if you subsequently restore (or bootstrap) a server from backup, where the change never took place? How do you track that? Moreover, this technique will take \",[\"SingleFetchClassInstance\",972],\" times longer to complete, as you need to run the change individually for each server. You may parallelize some of the work, but probably not all of it.\",{\"_131\":132,\"_41\":167,\"_134\":973,\"_46\":974},{},[975],\"n\",{\"_131\":132,\"_41\":167,\"_134\":977,\"_46\":978},{},[979],\"SQL_LOG_BIN=0\",{\"_131\":132,\"_41\":167,\"_134\":981,\"_46\":982},{},[325],{\"_131\":132,\"_41\":266,\"_134\":984,\"_46\":985},{},[986,987,988,989,990],[\"SingleFetchClassInstance\",1012],[\"SingleFetchClassInstance\",1008],[\"SingleFetchClassInstance\",1004],[\"SingleFetchClassInstance\",1000],[\"SingleFetchClassInstance\",991],{\"_131\":132,\"_41\":278,\"_134\":992,\"_46\":993},{},[994,995,996],\"On replica servers, the operation is NOT non-blocking. Meaning if the \",[\"SingleFetchClassInstance\",997],\" took 3 hours on the primary server, then from the moment it completes you can expect replication to stall while applying that same change for the next 3 hours or so, creating a massive 3 hour lag.\",{\"_131\":132,\"_41\":167,\"_134\":998,\"_46\":999},{},[325],{\"_131\":132,\"_41\":278,\"_134\":1001,\"_46\":1002},{},[1003],\"It is uninterruptible. The only way to abort is to kill the query aggressively. This then leads to a further massive cleanup operation, consuming more disk I/O.\",{\"_131\":132,\"_41\":278,\"_134\":1005,\"_46\":1006},{},[1007],\"It requires extra disk space, up to as much disk space as the original table.\",{\"_131\":132,\"_41\":278,\"_134\":1009,\"_46\":1010},{},[1011],\"The operation is resource greedy: the MySQL server will use as much CPU and disk I/O to complete the change as it can. This can and will impact performance on busy servers.\",{\"_131\":132,\"_41\":278,\"_134\":1013,\"_46\":1014},{},[1015,1016,368,1017,368,1018,368,1019,1020],\"On the server where the query is submitted, normally the primary database server, DML queries (\",[\"SingleFetchClassInstance\",1032],[\"SingleFetchClassInstance\",1029],[\"SingleFetchClassInstance\",1025],[\"SingleFetchClassInstance\",1021],\", ...) are non-blocking and may proceed to execute. Other DDL statements will block, and that's expected.\",{\"_131\":132,\"_41\":167,\"_134\":1022,\"_46\":1023},{},[1024],\"DELETE\",{\"_131\":132,\"_41\":167,\"_134\":1026,\"_46\":1027},{},[1028],\"UPDATE\",{\"_131\":132,\"_41\":167,\"_134\":1030,\"_46\":1031},{},[540],{\"_131\":132,\"_41\":167,\"_134\":1033,\"_46\":1034},{},[575],{\"_131\":132,\"_41\":133,\"_134\":1036,\"_46\":1037},{},[1038,1039,1040,1041,1042,1043,1044,1045,1046],\"This is MySQL's first take on non-blocking schema changes. \",[\"SingleFetchClassInstance\",1057],\" types of \",[\"SingleFetchClassInstance\",1054],\" (see above link for exhaustive list of supported changes) are eligible to run with \",[\"SingleFetchClassInstance\",1050],\". An \",[\"SingleFetchClassInstance\",1047],\" schema change is technically non-blocking, with quite a few caveats:\",{\"_131\":132,\"_41\":167,\"_134\":1048,\"_46\":1049},{},[235],{\"_131\":132,\"_41\":167,\"_134\":1051,\"_46\":1052},{},[1053],\"ALGORITHM=INPLACE\",{\"_131\":132,\"_41\":167,\"_134\":1055,\"_46\":1056},{},[325],{\"_131\":132,\"_41\":162,\"_134\":1058,\"_46\":1059},{},[1060],\"Some\",{\"_131\":132,\"_41\":398,\"_134\":1062,\"_46\":1063},{\"_48\":78},[1064],[\"SingleFetchClassInstance\",1065],{\"_131\":132,\"_41\":144,\"_134\":1066,\"_46\":1067},{\"_147\":1073},[1068,1069],[\"SingleFetchClassInstance\",1070],\", aka InnoDB Online DDL\",{\"_131\":132,\"_41\":167,\"_134\":1071,\"_46\":1072},{},[235],\"#inplace-aka-innodb-online-ddl\",{\"_131\":132,\"_41\":133,\"_134\":1075,\"_46\":1076},{},[1077,1078,208,1079,1080,1081,1082],\"We'll first examine the native MySQL options: \",[\"SingleFetchClassInstance\",1091],[\"SingleFetchClassInstance\",1088],\". For reference, see \",[\"SingleFetchClassInstance\",1083],\" MySQL 8.0 documentation.\",{\"_131\":132,\"_41\":144,\"_134\":1084,\"_46\":1085},{\"_147\":1087},[1086],\"Online DDL Operations\",\"https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-operations.html\",{\"_131\":132,\"_41\":167,\"_134\":1089,\"_46\":1090},{},[216],{\"_131\":132,\"_41\":167,\"_134\":1092,\"_46\":1093},{},[235],{\"_131\":132,\"_41\":133,\"_134\":1095,\"_46\":1096},{},[1097,1098,1099,1100,1101,1102,1103],\"How do you run non-blocking schema changes in MySQL? This is an eternal question. With a plethora of 3rd party solutions and with recent advancements in MySQL, it's difficult to track which solution is preferable for a given schema migration. In this post, we provide a high level overview of the state of MySQL online schema migrations in 2024. We limit the discussion to \",[\"SingleFetchClassInstance\",1112],\" statements, as other DDL statements are \",[\"SingleFetchClassInstance\",1108],\" fast (\",[\"SingleFetchClassInstance\",1104],\" is somewhat of an exception, but out of scope of this post).\",{\"_131\":132,\"_41\":167,\"_134\":1105,\"_46\":1106},{},[1107],\"DROP TABLE\",{\"_131\":132,\"_41\":162,\"_134\":1109,\"_46\":1110},{},[1111],\"typically\",{\"_131\":132,\"_41\":167,\"_134\":1113,\"_46\":1114},{},[325],\"current\",{\"_1117\":1118,\"_1119\":1120,\"_1121\":1118},\"development\",false,\"env\",{\"_1122\":1123,\"_1124\":1125,\"_1126\":1127,\"_1128\":1129,\"_1130\":1131},\"userSignedIn\",\"IMAGE_CDN\",\"https://planetscale-images.imgix.net\",\"IMAGE_CDN_ENABLED\",\"true\",\"INTERNAL_API\",\"https://api.planetscale.com\",\"RELEASE\",\"117b8aaf-965c-42bc-b013-5f72770de4d9\",\"SENTRY_DSN\",\"https://bd81903b44804e22a06bdc0c1a91b303@o499952.ingest.us.sentry.io/4504531942572032\"]\n");</script><!--$--><script nonce="tXkHEZUQ7yEdW8fxAT/8naJhqzuNpzQFP5OW7G14UV0=">window.__reactRouterContext.streamController.close();</script><!--/$--><!--/$--></body></html>