<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shahab Gohar</title>
    <description>The latest articles on DEV Community by Shahab Gohar (@shahab_gohar_21c3bd70019a).</description>
    <link>https://dev.to/shahab_gohar_21c3bd70019a</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4046313%2F7312eacb-645f-437a-a6a4-a5e24951b128.jpg</url>
      <title>DEV Community: Shahab Gohar</title>
      <link>https://dev.to/shahab_gohar_21c3bd70019a</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shahab_gohar_21c3bd70019a"/>
    <language>en</language>
    <item>
      <title>Moving a 136 GB MySQL table to another disk at 2 a.m. (the safe way, not a symlink)</title>
      <dc:creator>Shahab Gohar</dc:creator>
      <pubDate>Sat, 25 Jul 2026 05:41:30 +0000</pubDate>
      <link>https://dev.to/shahab_gohar_21c3bd70019a/moving-a-136-gb-mysql-table-to-another-disk-at-2-am-the-safe-way-not-a-symlink-1jnm</link>
      <guid>https://dev.to/shahab_gohar_21c3bd70019a/moving-a-136-gb-mysql-table-to-another-disk-at-2-am-the-safe-way-not-a-symlink-1jnm</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Heads up:&lt;/strong&gt; the database, table, and column names in this post are dummies. I've swapped the real production names for generic ones (&lt;code&gt;app_prod&lt;/code&gt;, &lt;code&gt;call_transcripts&lt;/code&gt;, &lt;code&gt;call_records&lt;/code&gt;, and so on) so nothing internal leaks. Every command, number, and step is exactly what I ran.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It was 2 a.m. and the production root disk was sitting at &lt;strong&gt;94%&lt;/strong&gt;. Twenty-two gigabytes free on a 338 GB volume, and still creeping up. In a few hours an army of voice agents, dozens of them, each running its own script, was scheduled to start dialing, and every call they place writes to the same database. There wasn't even enough free space to take a backup before touching anything, and the disk was going to fill on its own long before I could resize it.&lt;/p&gt;

&lt;p&gt;One thing saved the night. A second, nearly empty data volume was already mounted on the box, roughly 294 GB free, doing basically nothing. So the plan was easy to say and a little scary to run: get the biggest table off the root disk and onto that volume, without deleting a single row, and without the shortcut that would've quietly blown up a week later.&lt;/p&gt;

&lt;p&gt;Here's exactly how it went.&lt;/p&gt;

&lt;h2&gt;
  
  
  First: what was actually eating the disk
&lt;/h2&gt;

&lt;p&gt;Before moving anything I wanted to know precisely where the space had gone. One database, &lt;code&gt;app_prod&lt;/code&gt;, was 264 GB of the 303 GB in use, and inside it two InnoDB tables did nearly all the damage:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Table&lt;/th&gt;
&lt;th&gt;On-disk &lt;code&gt;.ibd&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;Rows&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;call_transcripts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;136 GB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~4.7 M&lt;/td&gt;
&lt;td&gt;one TEXT transcript blob per call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;call_records&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;112 GB&lt;/td&gt;
&lt;td&gt;~33 M&lt;/td&gt;
&lt;td&gt;index-heavy, most of it is indexes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;call_transcripts&lt;/code&gt; alone was about &lt;strong&gt;40% of the entire root disk&lt;/strong&gt;. It ingests roughly 35 to 40 GB a day and gets trimmed back to a three-day window by a nightly retention job, so it's huge but bounded. That made it the obvious thing to relocate: move just its &lt;code&gt;.ibd&lt;/code&gt; file onto the spare volume and the root disk breathes again, no data touched.&lt;/p&gt;

&lt;p&gt;The only question was how to do it without corrupting anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap: don't symlink the &lt;code&gt;.ibd&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The first idea everyone has is to move the file and drop a symlink in its place. Don't. Symlinking an individual InnoDB &lt;code&gt;.ibd&lt;/code&gt; file is unsupported and genuinely unsafe.&lt;/p&gt;

&lt;p&gt;InnoDB rebuilds the tablespace file during a bunch of ordinary operations, &lt;code&gt;OPTIMIZE&lt;/code&gt;, some &lt;code&gt;ALTER&lt;/code&gt;s, &lt;code&gt;TRUNCATE&lt;/code&gt;. When it does, it deletes your symlink and writes a fresh, real file back in the original location, silently refilling the disk you were trying to save, usually at the worst possible moment. Crash recovery and most backup tools don't expect a symlinked tablespace either. MySQL tolerates symlinking a whole database directory, but never a single tablespace file. It's the kind of fix that works in the demo and detonates in production.&lt;/p&gt;

&lt;p&gt;There's a proper mechanism for this, and it's been around since MySQL 8.0: &lt;code&gt;innodb_directories&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The right way: &lt;code&gt;innodb_directories&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;InnoDB doesn't identify a tablespace by its path. It identifies it by a &lt;code&gt;space_id&lt;/code&gt; baked into the file header. At startup it scans the data directory plus any directories listed in &lt;code&gt;innodb_directories&lt;/code&gt;, matches each &lt;code&gt;.ibd&lt;/code&gt; to its tablespace by that &lt;code&gt;space_id&lt;/code&gt;, and rewrites the recorded path in the data dictionary. Because the dictionary is updated, the move survives restarts and later DDL. This is the behavior documented in the MySQL 8.0 manual for moving tablespace files while the server is offline.&lt;/p&gt;

&lt;p&gt;Two rules matter, and both bite if you skip them:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The directory has to stay in the config &lt;strong&gt;permanently&lt;/strong&gt;. InnoDB needs it at every startup to locate the file, and without it, recovery can't find the tablespace.&lt;/li&gt;
&lt;li&gt;A file-per-table &lt;code&gt;.ibd&lt;/code&gt; can only be moved into a directory &lt;strong&gt;named after its schema&lt;/strong&gt;. My table lives in the &lt;code&gt;app_prod&lt;/code&gt; schema, so the destination had to be &lt;code&gt;.../mysql-data/app_prod/&lt;/code&gt;, not just any folder. Miss that and InnoDB won't adopt the file.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For this move the specifics were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tablespace &lt;code&gt;app_prod/call_transcripts&lt;/code&gt;, &lt;code&gt;space_id = 348&lt;/code&gt; on this instance (yours will differ)&lt;/li&gt;
&lt;li&gt;Old path: &lt;code&gt;/var/lib/mysql/app_prod/call_transcripts.ibd&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;New path: &lt;code&gt;/mnt/data-volume/mysql-data/app_prod/call_transcripts.ibd&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The procedure
&lt;/h2&gt;

&lt;p&gt;Two phases. Everything I could prepare with the server running, I did first, so the actual downtime was just the file copy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prep, with zero downtime
&lt;/h3&gt;

&lt;p&gt;Create the destination on the data volume, owned by &lt;code&gt;mysql&lt;/code&gt;, using the schema-named subdirectory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /mnt/data-volume/mysql-data/app_prod
&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; mysql:mysql /mnt/data-volume/mysql-data
&lt;span class="nb"&gt;chmod &lt;/span&gt;750 /mnt/data-volume/mysql-data /mnt/data-volume/mysql-data/app_prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the gotcha almost no tutorial mentions. On Ubuntu, &lt;code&gt;mysqld&lt;/code&gt; runs under an &lt;strong&gt;AppArmor&lt;/strong&gt; profile. If the new path isn't whitelisted, MySQL is denied access and just fails to start, with an error that sends you hunting in the wrong place for an hour. Add the paths to &lt;code&gt;/etc/apparmor.d/local/usr.sbin.mysqld&lt;/code&gt; (which the main profile includes):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/mnt/data-volume/mysql-data/ r,
/mnt/data-volume/mysql-data/** rwk,
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apparmor_parser &lt;span class="nt"&gt;-r&lt;/span&gt; /etc/apparmor.d/usr.sbin.mysqld
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register the new directory in &lt;code&gt;/etc/mysql/mysql.conf.d/mysqld.cnf&lt;/code&gt; under &lt;code&gt;[mysqld]&lt;/code&gt;. This takes effect on the next restart and has to stay there for good:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[mysqld]&lt;/span&gt;
&lt;span class="py"&gt;innodb_directories&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/mnt/data-volume/mysql-data"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One more reboot-safety detail. The volume is mounted via &lt;code&gt;/etc/fstab&lt;/code&gt; with &lt;code&gt;nofail&lt;/code&gt;, which means on boot MySQL could start &lt;em&gt;before&lt;/em&gt; the mount is ready and then fail to find its tablespace. A small systemd drop-in at &lt;code&gt;/etc/systemd/system/mysql.service.d/require-datavolume.conf&lt;/code&gt; forces the ordering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;RequiresMountsFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/mnt/data-volume&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then &lt;code&gt;systemctl daemon-reload&lt;/code&gt; so the drop-in is picked up. Nothing so far has touched the running database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cutover, about 11 minutes of downtime
&lt;/h3&gt;

&lt;p&gt;Here's the honest cost: moving one table still means stopping the whole instance. The file can only be moved safely while MySQL is fully offline, so the app is down for the length of the copy. I did a clean shutdown first, so all dirty pages flush and there's no crash recovery to sit through on the way back up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clean shutdown so every dirty page is flushed and no crash recovery is needed&lt;/span&gt;
mysql &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SET GLOBAL innodb_fast_shutdown=0;"&lt;/span&gt;
systemctl stop mysql

&lt;span class="c"&gt;# mv copies to the target and unlinks the source ONLY after the copy fully&lt;/span&gt;
&lt;span class="c"&gt;# succeeds, so the original is never at risk mid-transfer. (~136 GB, ~11 min.)&lt;/span&gt;
&lt;span class="nb"&gt;mv&lt;/span&gt; /var/lib/mysql/app_prod/call_transcripts.ibd &lt;span class="se"&gt;\&lt;/span&gt;
   /mnt/data-volume/mysql-data/app_prod/call_transcripts.ibd

&lt;span class="nb"&gt;chown &lt;/span&gt;mysql:mysql /mnt/data-volume/mysql-data/app_prod/call_transcripts.ibd
&lt;span class="nb"&gt;chmod &lt;/span&gt;640         /mnt/data-volume/mysql-data/app_prod/call_transcripts.ibd

systemctl start mysql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The choice of &lt;code&gt;mv&lt;/code&gt; over &lt;code&gt;cp&lt;/code&gt; is deliberate and load-bearing. A cross-volume &lt;code&gt;mv&lt;/code&gt; copies to the target and only unlinks the source after the copy fully succeeds, so the original is never at risk mid-transfer. It also guarantees the file ends up in exactly one place, which is the whole game: if InnoDB's startup scan ever finds &lt;strong&gt;two&lt;/strong&gt; files with the same &lt;code&gt;space_id&lt;/code&gt;, it refuses to start. Never leave a copy behind in the old datadir.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verify
&lt;/h3&gt;

&lt;p&gt;After MySQL came back up, I confirmed the data dictionary points at the volume and the blob reads back intact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- The recorded path now points at the data volume&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;SPACE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PATH&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INNODB_TABLESPACES&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INNODB_DATAFILES&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SPACE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'app_prod/call_transcripts'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- returns: 348 | app_prod/call_transcripts | /mnt/data-volume/mysql-data/app_prod/call_transcripts.ibd&lt;/span&gt;

&lt;span class="c1"&gt;-- Table reads fine and the blob comes back intact&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;LENGTH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;call_transcripts&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The error log showed a clean start, "ready for connections", no tablespace warnings. The voice agents started on schedule.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Root &lt;code&gt;/&lt;/code&gt; used&lt;/td&gt;
&lt;td&gt;303 GB (94%)&lt;/td&gt;
&lt;td&gt;167 GB (52%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Root &lt;code&gt;/&lt;/code&gt; free&lt;/td&gt;
&lt;td&gt;22 GB&lt;/td&gt;
&lt;td&gt;157 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;call_transcripts.ibd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;on root disk&lt;/td&gt;
&lt;td&gt;on data volume&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;From 94% to 52% by moving a single file, database back up after ~11 minutes down, and not one row deleted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rollback, because it's 2 a.m. and you're human
&lt;/h2&gt;

&lt;p&gt;The reason this is safe to try on a bad night: the data is only ever in one place, so backing out is symmetrical. If MySQL fails to start after the move, stop it, move the file home, drop the config line, start again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl stop mysql
&lt;span class="nb"&gt;mv&lt;/span&gt; /mnt/data-volume/mysql-data/app_prod/call_transcripts.ibd &lt;span class="se"&gt;\&lt;/span&gt;
   /var/lib/mysql/app_prod/call_transcripts.ibd
&lt;span class="nb"&gt;chown &lt;/span&gt;mysql:mysql /var/lib/mysql/app_prod/call_transcripts.ibd
&lt;span class="c"&gt;# remove the innodb_directories line from mysqld.cnf, then:&lt;/span&gt;
systemctl start mysql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Lessons I wrote down afterward
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;It's headroom, not a cure.&lt;/strong&gt; The other 112 GB table and the ongoing 35 to 40 GB/day of churn still live on the root disk. It'll climb again. Keep watching &lt;code&gt;df -h /&lt;/code&gt; and plan the real capacity work instead of treating the move as the finish line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Index the retention delete.&lt;/strong&gt; The nightly job that trims the table to three days runs a delete keyed on &lt;code&gt;date_entered&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;call_transcripts&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;date_entered&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UTC_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That column had no index, so the delete was a full table scan across ~4.7 M rows every night. The fix is a safe online DDL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;call_transcripts&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_call_transcripts_date_entered&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date_entered&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Batching the delete in chunks (a loop of &lt;code&gt;DELETE ... LIMIT 5000&lt;/code&gt;) also keeps undo and redo from spiking on a big purge. And once a tablespace is relocated, be careful with casual &lt;code&gt;OPTIMIZE&lt;/code&gt; or &lt;code&gt;ALTER&lt;/code&gt; rebuilds on it until you've confirmed they respect &lt;code&gt;innodb_directories&lt;/code&gt;, the same rebuild behavior that makes symlinks unsafe is worth a second thought here too.&lt;/p&gt;




&lt;p&gt;If you'd rather skip the 2 a.m. narration and just keep the steps, there's a cleaner reference version on my site: &lt;strong&gt;&lt;a href="https://shahabgohar.dev/blogs/move-mysql-innodb-table-to-another-disk/" rel="noopener noreferrer"&gt;Moving a MySQL InnoDB table to another disk&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Day to day I build and run automation, CRM, and AI systems for small teams. Most of it is quiet, well-tested plumbing; the rest is nights like this one, and staying calm through those is half the job. If that's the kind of help you need, I'm at &lt;a href="https://shahabgohar.dev/" rel="noopener noreferrer"&gt;shahabgohar.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>mysql</category>
      <category>database</category>
      <category>devops</category>
      <category>sysadmin</category>
    </item>
  </channel>
</rss>
