{"id":41,"date":"2014-03-24T20:39:00","date_gmt":"2014-03-24T19:39:00","guid":{"rendered":"http:\/\/188.166.119.205\/?p=41"},"modified":"2026-11-10T05:52:04","modified_gmt":"2026-11-10T05:52:04","slug":"how-to-remove-database-in-a-safe-way","status":"publish","type":"post","link":"https:\/\/banyek.net\/?p=41&lang=en","title":{"rendered":"How to remove database in a safe way"},"content":{"rendered":"<p>When you have to drop a large database, you\u2019ll encounter some problems, mainly replication lag. Now I\u2019ll show you how to avoid this.<\/p>\n<p>What can cause replication lag when you drop a database? First, it takes some disk I\/O to unlink the files, and secondly, MySQL will scan through the buffer pool to see if there are pages from that database or not. On a huge (or at least big) database this could take seconds or even minutes, what means your slaves will collect lag for seconds or (of course) even minutes.<\/p>\n<p><!--more--><\/p>\n<p>Every database dropping should be started with a backup of that database. I did it on a replica.<\/p>\n<pre><code>[root@db-secondary.bfc \/home\/banyek\/chomp]# mysqldump --set-gtid-purged=OFF --single-transaction chomp &gt; chomp.sql\n[root@db-secondary.bfc \/home\/banyek\/chomp]# ls\nchomp.sql\n[root@db-secondary.bfc \/home\/banyek\/chomp]# ls -alh\ntotal 823M\ndrwxr-xr-x 2 root   root   4.0K Mar 24 05:31 .\ndrwx------ 4 banyek banyek 4.0K Mar 24 05:31 ..\n-rw-r--r-- 1 root   root   823M Mar 24 05:32 chomp.sql\n[root@db-secondary.bfc \/home\/banyek\/chomp]# gzip chomp.sql\n[root@db-secondary.bfc \/home\/banyek\/chomp]# ls -lah\ntotal 202M\ndrwxr-xr-x 2 root   root   4.0K Mar 24 05:33 .\ndrwx------ 4 banyek banyek 4.0K Mar 24 05:31 ..\n-rw-r--r-- 1 root   root   202M Mar 24 05:32 chomp.sql.gz\n<\/code><\/pre>\n<p>So, your database is dumped, you can now safely remove it.<\/p>\n<p>The following steps should happen:<\/p>\n<ul>\n<li>Clean up data from tables<\/li>\n<li>Shrink the datafiles<\/li>\n<li>Drop tables<\/li>\n<li>Drop database<\/li>\n<\/ul>\n<p>###Clean up data from tables<\/p>\n<p>First, check what to deal with. How many tables we have, how big they are, etc.<\/p>\n<pre><code>[root@db-master.bfc \/home\/banyek]# ls -lh \/var\/lib\/mysql\/chomp\/\ntotal 2.8G\n-rw-r----- 1 mysql mysql 8.5K Jan 21 00:25 apikeys.frm\n-rw-r----- 1 mysql mysql 1.0M Jan 21 00:01 apikeys.ibd\n-rw-r----- 1 mysql mysql   65 Jan 21 00:25 db.opt\n-rw-r----- 1 mysql mysql  13K Jan 21 00:25 images.frm\n-rw-r----- 1 mysql mysql 2.8G Mar  4 14:22 images.ibd\n-rw-r----- 1 mysql mysql 8.4K Jan 21 00:25 keys.frm\n-rw-r----- 1 mysql mysql 1.0M Jan 21 00:01 keys.ibd\n-rw-r----- 1 mysql mysql 8.5K Jan 21 00:25 transform_lock.frm\n-rw-r----- 1 mysql mysql 8.0M Jan 21 00:01 transform_lock.ibd\n-rw-r----- 1 mysql mysql 8.5K Jan 21 00:25 videos.frm\n-rw-r----- 1 mysql mysql 9.0M Mar  4 13:14 videos.ibd\n\nMon Mar 24 05:34:26 2014root@localhost [chomp]&gt; select count(*) from images;\n+----------+\n| count(*) |\n+----------+\n|  8843673 |\n+----------+\n1 row in set (7.57 sec)\n\nMon Mar 24 06:31:58 2014root@localhost [chomp]&gt; select count(*) from apikeys\n    -&gt; ;\n+----------+\n| count(*) |\n+----------+\n|        3 |\n+----------+\n1 row in set (0.00 sec)\n\nMon Mar 24 06:32:16 2014root@localhost [chomp]&gt; select count(*) from `keys`;\n+----------+\n| count(*) |\n+----------+\n|        1 |\n+----------+\n1 row in set (0.00 sec)\n\nMon Mar 24 06:32:30 2014root@localhost [chomp]&gt; select count(*) from transform_lock;\n+----------+\n| count(*) |\n+----------+\n|        0 |\n+----------+\n1 row in set (0.00 sec)\n\nMon Mar 24 06:32:42 2014root@localhost [chomp]&gt; select count(*) from videos;\n+----------+\n| count(*) |\n+----------+\n|     8570 |\n+----------+\n1 row in set (0.02 sec)\n<\/code><\/pre>\n<p>You can see we have only one really big table, and that is \u2018images\u2019 with about 9 million rows. The \u2018videos\u2019 table is the second biggest, it is 1\/1000th of the images.<\/p>\n<p>So far, so good.<\/p>\n<p>I use the <em>pt-archiver<\/em> tool to clean up tables because it chunks up the data what needed to delete. That means it will run the delete command with a limit clause where \u2018limit\u2019 equals the chunk size. I have calculated earlier that our database can delete about 2000 row in a chunk with no replication lag, so I\u2019ll use the same chunk size for cleaning up images.<\/p>\n<p>Before I start the delete, I check how many chunks I have \u2013 just to help myself keep tracking the state of delete.<\/p>\n<pre><code>Mon Mar 24 05:39:23 2014root@localhost [chomp]&gt; select 8843673 \/ 2000 as chunks;\n+-----------+\n| chunks    |\n+-----------+\n| 4421.8365 |\n+-----------+\n1 row in set (0.00 sec)\n<\/code><\/pre>\n<p>Here is the snippet I use to track the state of delete:<\/p>\n<pre><code># SELECT CONCAT(100 - ROUND(($CHUNKS - (($MAXITEMS - COUNT(*)) \/ $CHUNKSIZE \/ $CHUNKS * 100),'%' as percent_done FROM $TABLE_TO_CLEAN_UP\n#\n# So, wrapped into a while loop, in the current way it will be like:\n\nwhile true; do clear ; mysql chomp -e \"select concat(100 - round((4421 - ((8843673 - count(*)) \/2000)) \/ 4421 * 100),'%')  as percent_done from images;\" ; sleep 30; done\n\n# It will produce a neat table like this - refreshed in every 30 seonds.\n# +--------------+\n# | percent_done |\n# +--------------+\n# | 61%          |\n# +--------------+\n<\/code><\/pre>\n<p>After the progress bar is set up, we can start the table cleanup job with pt-archiver<\/p>\n<pre><code>[root@db-master.bfc \/home\/banyek]# pt-archiver --statistics --no-check-charset --limit 2000 --txn-size 2000 --source D=chomp,t=images,u=user,p=password --purge --where \"1=1\"\n\nSource: D=chomp,p=...,t=images,u=user\nSELECT 8420052\nINSERT 0\nDELETE 8420052\nAction        Count       Time        Pct\ndeleting    8420052  1981.1935      72.48\nselect         4212    72.4313       2.65\ncommit         4211     9.3239       0.34\nother             0   670.3669      24.53\n\n# You can see the 'Count' row is a bit less than it was at the select count(*), but this is just because\n# I've ran the archiver two times.\n#\n#\n# After the cleanup is done at the table, repeat this with the others too.\n<\/code><\/pre>\n<p>###Shrink the datafiles<\/p>\n<p>After you have cleaned up the data from the tables themselves you can see that the data files are still there. (<a href=\"http:\/\/ops.kinja.com\/free-up-space-on-mysql-servers-512264425\">You can read more about this here<\/a>) So the next step is to shrink the datafiles. The easiest way to achieve this to run a simple alter like \u2018ALTER TABLE tablename engine=InnoDB\u2019 but we are still aware of replication lag, so run it via <em>pt-online-schema-change<\/em><\/p>\n<p><a href=\"http:\/\/ops.kinja.com\/free-up-space-on-mysql-servers-512264425\">Free up space on mysql servers<\/a><\/p>\n<p>First, I have to tell you, that the \u201cfragmentation\u201d is not the best word what I should\u2026<a href=\"http:\/\/ops.kinja.com\/free-up-space-on-mysql-servers-512264425\">Read more<\/a><\/p>\n<pre><code>root@db-master.bfc \/var\/log\/mysql]# pt-online-schema-change --execute --alter \"engine=InnoDB;\" h=localhost,u=user,p=password,D=chomp,t=images\nFound 31 slaves:\n[...]\nAltering new table...\nAltered `chomp`.`_images_new` OK.\n2014-03-24T06:38:39 Creating triggers...\n2014-03-24T06:38:39 Created triggers OK.\n2014-03-24T06:38:39 Copying approximately 1 rows...\n2014-03-24T06:38:39 Copied rows OK.\n2014-03-24T06:38:39 Swapping tables...\n2014-03-24T06:38:39 Swapped original and new tables OK.\n2014-03-24T06:38:39 Dropping old table...\n2014-03-24T06:38:40 Dropped old table `chomp`.`_images_old` OK.\n2014-03-24T06:38:40 Dropping triggers...\n2014-03-24T06:38:40 Dropped triggers OK.\nSuccessfully altered `chomp`.`images`.\n[root@db-master.bfc \/var\/lib\/mysql\/chomp]# ls -lah\ntotal 11M\ndrwx------  2 mysql mysql 4.0K Mar 24 06:38 .\ndrwxr-xr-x 16 mysql mysql 4.0K Mar  4 04:17 ..\n-rw-r-----  1 mysql mysql 8.5K Jan 21 00:25 apikeys.frm\n-rw-r-----  1 mysql mysql 1.0M Jan 21 00:01 apikeys.ibd\n-rw-r-----  1 mysql mysql   65 Jan 21 00:25 db.opt\n-rw-rw----  1 mysql mysql  13K Mar 24 06:38 images.frm\n-rw-rw----  1 mysql mysql 128K Mar 24 06:38 images.ibd\n-rw-r-----  1 mysql mysql 8.4K Jan 21 00:25 keys.frm\n-rw-r-----  1 mysql mysql 1.0M Jan 21 00:01 keys.ibd\n-rw-r-----  1 mysql mysql 8.5K Jan 21 00:25 transform_lock.frm\n-rw-r-----  1 mysql mysql 8.0M Jan 21 00:01 transform_lock.ibd\n-rw-rw----  1 mysql mysql 8.5K Mar 24 06:37 videos.frm\n-rw-rw----  1 mysql mysql  96K Mar 24 06:37 videos.ibd\n<\/code><\/pre>\n<p>###Drop tables<\/p>\n<p>Now we can drop the tables. YAY!<\/p>\n<p>Only we have to be aware of two things:<\/p>\n<ol>\n<li>use IF EXISTS clause, because if a table filtered out from replication it will break it.<\/li>\n<li>be aware of reserved words. (Check bottom: the \u2018keys\u2019 table can cause problems because the word is reserved. Just put backtick surround this. (And don\u2019t let the developers create tables with reserved words :D)<\/li>\n<\/ol>\n<pre><code>Mon Mar 24 06:42:26 2014root@localhost [chomp]&gt; drop table if exists apikeys;\nQuery OK, 0 rows affected (0.01 sec)\n\nMon Mar 24 06:43:10 2014root@localhost [chomp]&gt; drop table if exists images;\nQuery OK, 0 rows affected (0.00 sec)\n\nMon Mar 24 06:43:49 2014root@localhost [chomp]&gt; drop table if exists `keys`;\nQuery OK, 0 rows affected (0.00 sec)\n\nMon Mar 24 06:43:55 2014root@localhost [chomp]&gt; drop table if exists transform_lock;\nQuery OK, 0 rows affected (0.00 sec)\n\nMon Mar 24 06:44:06 2014root@localhost [chomp]&gt; drop table if exists videos;\nQuery OK, 0 rows affected (0.00 sec)\n\nMon Mar 24 06:44:14 2014root@localhost [chomp]&gt; show tables;\nEmpty set (0.00 sec)\n<\/code><\/pre>\n<p>###Drop database<\/p>\n<p>Finally, we have reached this point. We can now drop the database, and we\u2019re done (please don\u2019t forget the IF EXISTS clause!)<\/p>\n<pre><code>Mon Mar 24 06:44:17 2014root@localhost [chomp]&gt; use mysql\nReading table information for completion of table and column names\nYou can turn off this feature to get a quicker startup with -A\n\nDatabase changed\nMon Mar 24 06:44:20 2014root@localhost [mysql]&gt; drop database if exists chomp;\nQuery OK, 0 rows affected (0.04 sec)\n<\/code><\/pre>\n<p>###Conclusion<\/p>\n<p>Dropping a database is really an easy thing, just one command and everything done, but if your site has a lot of traffic, and your users didn\u2019t tolerate weird things, you have to be cautious. In this scenario, a simple \u2018drop database\u2019 had to take about a few seconds, with a few seconds lag, but building up good habits are mandatory \u2013 so I recommend to always clean up the database before dropping it.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>When you have to drop a large database, you\u2019ll encounter some problems, mainly replication lag. Now I\u2019ll show you how to avoid this. What can cause replication lag when you drop a database? First, it takes some disk I\/O to unlink the files, and secondly, MySQL will scan through the buffer pool to see if [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[134],"tags":[144,126],"class_list":["post-41","post","type-post","status-publish","format-standard","hentry","category-tech-en","tag-mysql-en","tag-oldblog-en"],"_links":{"self":[{"href":"https:\/\/banyek.net\/index.php?rest_route=\/wp\/v2\/posts\/41","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/banyek.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/banyek.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/banyek.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/banyek.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=41"}],"version-history":[{"count":1,"href":"https:\/\/banyek.net\/index.php?rest_route=\/wp\/v2\/posts\/41\/revisions"}],"predecessor-version":[{"id":436,"href":"https:\/\/banyek.net\/index.php?rest_route=\/wp\/v2\/posts\/41\/revisions\/436"}],"wp:attachment":[{"href":"https:\/\/banyek.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=41"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/banyek.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=41"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/banyek.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=41"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}