<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Mysql on Major Hayden</title><link>https://major.io/tags/mysql/</link><description>Recent content in Mysql on Major Hayden</description><generator>Hugo</generator><language>en</language><managingEditor>major@mhtx.net (Major Hayden)</managingEditor><webMaster>major@mhtx.net (Major Hayden)</webMaster><copyright>All content licensed [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)</copyright><lastBuildDate>Thu, 09 Jul 2026 20:41:49 +0000</lastBuildDate><atom:link href="https://major.io/tags/mysql/index.xml" rel="self" type="application/rss+xml"/><item><title>Fixing a ghost database migration failure</title><link>https://major.io/p/ghost-db-migration-failure/</link><pubDate>Thu, 31 Aug 2023 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/ghost-db-migration-failure/</guid><description>&lt;p&gt;I love learning about the &lt;em&gt;behind the scenes&lt;/em&gt; aspects of just about everything.
I do &lt;a href="https://w5wut.com"&gt;ham radio&lt;/a&gt;, I self-host lots of my personal infrastructure, and I&amp;rsquo;ve been learning more about the math behind the stock market for the last year or two.&lt;/p&gt;
&lt;p&gt;That led me to start a blog on &lt;a href="https://unsplash.com/photos/l6mLi-iKUW0"&gt;Ghost&lt;/a&gt; to share my findings with others.
I started &lt;a href="https://thetanerd.com"&gt;Theta Nerd&lt;/a&gt;&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; earlier this summer.&lt;/p&gt;
&lt;p&gt;My deployment looked great when I started!
Everything was automatically updated with &lt;a href="https://major.io/p/watchtower/"&gt;watchtower&lt;/a&gt; and running with &lt;a href="https://major.io/p/docker-compose-on-coreos/"&gt;docker-compose on Fedora CoreOS&lt;/a&gt;. &lt;em&gt;(Click these links to read the posts on both topics!)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;However, I woke up one morning to my monitoring going off and my site was down. 😱&lt;/p&gt;
&lt;h1 id="why-is-the-site-down"&gt;Why is the site down?&lt;/h1&gt;
&lt;p&gt;Anyone who has worked in IT knows this sinking feeling.
Something is down, you don&amp;rsquo;t know why, and you suspect the worst possible scenarios.&lt;/p&gt;
&lt;p&gt;The instance hosting the blog was online and responsive, so I started digging into the logs with &lt;code&gt;docker-compose logs&lt;/code&gt;.
I suddenly found a wall of text in the logs for the Ghost container:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[2023-08-03 11:10:16] INFO Adding members.email_disabled column
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[2023-08-03 11:10:16] INFO Setting email_disabled to true for all members that have their email on the suppression list
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[2023-08-03 11:10:16] INFO Setting nullable: stripe_products.product_id
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[2023-08-03 11:10:16] INFO Adding table: donation_payment_events
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[2023-08-03 11:10:16] INFO Rolling back: alter table `donation_payment_events` add constraint `donation_payment_events_member_id_foreign` foreign key (`member_id`) references `members` (`id`) on delete SET NULL - Referencing column &amp;#39;member_id&amp;#39; and referenced column &amp;#39;id&amp;#39; in foreign key constraint &amp;#39;donation_payment_events_member_id_foreign&amp;#39; are incompatible..
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[2023-08-03 11:10:16] INFO Dropping table: donation_payment_events
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[2023-08-03 11:10:16] INFO Dropping nullable: stripe_products.product_id with foreign keys disabled
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[2023-08-03 11:10:16] INFO Setting email_disabled to false for all members
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[2023-08-03 11:10:16] INFO Removing members.email_disabled column
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[2023-08-03 11:10:16] INFO Rollback was successful.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[2023-08-03 11:10:16] ERROR alter table `donation_payment_events` add constraint `donation_payment_events_member_id_foreign` foreign key (`member_id`) references `members` (`id`) on delete SET NULL - Referencing column &amp;#39;member_id&amp;#39; and referenced column &amp;#39;id&amp;#39; in foreign key constraint &amp;#39;donation_payment_events_member_id_foreign&amp;#39; are incompatible.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;alter table `donation_payment_events` add constraint `donation_payment_events_member_id_foreign` foreign key (`member_id`) references `members` (`id`) on delete SET NULL - Referencing column &amp;#39;member_id&amp;#39; and referenced column &amp;#39;id&amp;#39; in foreign key constraint &amp;#39;donation_payment_events_member_id_foreign&amp;#39; are incompatible.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&amp;#34;config&amp;#34;:{&amp;#34;transaction&amp;#34;:false},&amp;#34;name&amp;#34;:&amp;#34;2023-07-27-11-47-49-create-donation-events.js&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;#34;Error occurred while executing the following migration: 2023-07-27-11-47-49-create-donation-events.js&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Error ID:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 300
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Error Code: 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ER_FK_INCOMPATIBLE_COLUMNS
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;----------------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Error: alter table `donation_payment_events` add constraint `donation_payment_events_member_id_foreign` foreign key (`member_id`) references `members` (`id`) on delete SET NULL - Referencing column &amp;#39;member_id&amp;#39; and referenced column &amp;#39;id&amp;#39; in foreign key constraint &amp;#39;donation_payment_events_member_id_foreign&amp;#39; are incompatible.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; at /var/lib/ghost/versions/5.57.2/node_modules/knex-migrator/lib/index.js:1032:19
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; at Packet.asError (/var/lib/ghost/versions/5.57.2/node_modules/mysql2/lib/packets/packet.js:728:17)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; at Query.execute (/var/lib/ghost/versions/5.57.2/node_modules/mysql2/lib/commands/command.js:29:26)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; at Connection.handlePacket (/var/lib/ghost/versions/5.57.2/node_modules/mysql2/lib/connection.js:478:34)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; at PacketParser.onPacket (/var/lib/ghost/versions/5.57.2/node_modules/mysql2/lib/connection.js:97:12)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; at PacketParser.executeStart (/var/lib/ghost/versions/5.57.2/node_modules/mysql2/lib/packet_parser.js:75:16)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; at Socket.&amp;lt;anonymous&amp;gt; (/var/lib/ghost/versions/5.57.2/node_modules/mysql2/lib/connection.js:104:25)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; at Socket.emit (node:events:513:28)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; at addChunk (node:internal/streams/readable:315:12)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; at readableAddChunk (node:internal/streams/readable:289:9)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; at Socket.Readable.push (node:internal/streams/readable:228:10)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; at TCP.onStreamRead (node:internal/stream_base_commons:190:23)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ah, so a failed database migration in the upgrade to 5.57.2 is the culprit! 👏&lt;/p&gt;
&lt;p&gt;I brought the site back online quickly by changing the container version for Ghost back to the previous version (5.55.2).&lt;/p&gt;
&lt;h1 id="why-did-the-database-migration-fail"&gt;Why did the database migration fail?&lt;/h1&gt;
&lt;p&gt;The error message from above boils down to this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Error: alter table `donation_payment_events` add constraint 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;`donation_payment_events_member_id_foreign` foreign key (`member_id`)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;references `members` (`id`) on delete SET NULL - Referencing column
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;#39;member_id&amp;#39; and referenced column &amp;#39;id&amp;#39; in foreign key constraint 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;#39;donation_payment_events_member_id_foreign&amp;#39; are incompatible.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Adjusting the &lt;code&gt;donation_payment_events.member_id&lt;/code&gt; column to be a foreign key of &lt;code&gt;members.id&lt;/code&gt; is failing because they are incompatible types.
However, as I examined both tables, both were regular &lt;code&gt;varchar(24)&lt;/code&gt; columns without anything special attached to them:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mysql&amp;gt; describe members;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+------------------------------+---------------+------+-----+---------+-------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Field | Type | Null | Key | Default | Extra |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+------------------------------+---------------+------+-----+---------+-------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| id | varchar(24) | NO | PRI | NULL | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| uuid | varchar(36) | YES | UNI | NULL | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| email | varchar(191) | NO | UNI | NULL | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| status | varchar(50) | NO | | free | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| name | varchar(191) | YES | | NULL | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| expertise | varchar(191) | YES | | NULL | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| note | varchar(2000) | YES | | NULL | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| geolocation | varchar(2000) | YES | | NULL | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| enable_comment_notifications | tinyint(1) | NO | | 1 | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| email_count | int unsigned | NO | | 0 | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| email_opened_count | int unsigned | NO | | 0 | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| email_open_rate | int unsigned | YES | MUL | NULL | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| last_seen_at | datetime | YES | | NULL | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| last_commented_at | datetime | YES | | NULL | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| created_at | datetime | NO | | NULL | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| created_by | varchar(24) | NO | | NULL | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| updated_at | datetime | YES | | NULL | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| updated_by | varchar(24) | YES | | NULL | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+------------------------------+---------------+------+-----+---------+-------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;18 rows in set (0.00 sec)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="going-upstream"&gt;Going upstream&lt;/h1&gt;
&lt;p&gt;I went to Ghost&amp;rsquo;s GitHub repository and &lt;a href="https://github.com/TryGhost/Ghost/issues/17584"&gt;opened an issue&lt;/a&gt; with as much data as I can find.&lt;/p&gt;
&lt;p&gt;One of the &lt;a href="https://github.com/TryGhost/Ghost/issues/17584#issuecomment-1671134556"&gt;first replies&lt;/a&gt; mentioned something about database collations.
Long story short, collations describe how databases handle sorting and comparing data for different languages.
Comparing some languages to other languages can be particularly challenging and this can lead to problems.&lt;/p&gt;
&lt;p&gt;I made a switch from MariaDB to MySQL recently for the blog.
Could that be related?&lt;/p&gt;
&lt;h1 id="more-searching"&gt;More searching&lt;/h1&gt;
&lt;p&gt;I figured that I wasn&amp;rsquo;t the first one to stumble into this problem, and sure enough &amp;ndash; I wasn&amp;rsquo;t!
There&amp;rsquo;s a &lt;a href="https://dnsmichi.at/2022/06/01/ghost-v5-upgrade-with-mysql-8-collation-migration-in-docker-compose/"&gt;great blog post&lt;/a&gt; about a broken migration from MySQL 5 to 8 with Ghost.&lt;/p&gt;
&lt;p&gt;In short, it required several steps to fix it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Stop the Ghost container&lt;/li&gt;
&lt;li&gt;Back up the database first (always a good idea)&lt;/li&gt;
&lt;li&gt;Do a quick find/replace on the dumped database to change the collations&lt;/li&gt;
&lt;li&gt;Drop the &lt;code&gt;ghost&lt;/code&gt; database from the database 😱&lt;/li&gt;
&lt;li&gt;Import the database back into MySQL&lt;/li&gt;
&lt;li&gt;Start Ghost again&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Dropping databases always makes me pause, but that&amp;rsquo;s what backups are for! 😉&lt;/p&gt;
&lt;h1 id="how-i-fixed-it"&gt;How I fixed it&lt;/h1&gt;
&lt;p&gt;In my case, my MySQL container is called &lt;code&gt;ghostmysql&lt;/code&gt; and my Ghost database is &lt;code&gt;ghostdb&lt;/code&gt;.
Then I made a backup of the database using &lt;code&gt;mysqldump&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;sudo docker-compose exec ghostmysql mysqldump \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; -u root -psuper-secret-password ghostdb &amp;gt; backup-ghost-db.sql
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, I copied the SQL file to another directory &lt;em&gt;just in case&lt;/em&gt; I accidentally deleted this backup with an errant command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;cp backup-ghost-db.sql ../
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then I made a copy of the SQL file in the current directory and ran the find and replace on that copy.
This changes the collations from the wrong one, &lt;code&gt;utf8mb4_general_ci&lt;/code&gt;, to the right one, &lt;code&gt;utf8mb4_0900_ai_ci&lt;/code&gt;&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;cp backup-ghost-db.sql backup-ghost-db-new.sql
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;sed -i &amp;#39;s/utf8mb4_general_ci/utf8mb4_0900_ai_ci/g&amp;#39; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; backup-ghost-db-new.sql
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now I have the collations right for importing the database back into MySQL.
But first, I have to drop the existing database.
&lt;strong&gt;This is a good time to double check your backups!&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;sudo docker-compose exec ghostmysql mysql -u root \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; -psuper-secret-password
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;mysql&amp;gt; DROP DATABASE ghostdb;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we can import the modified backup:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;cat backup-ghost-db-new.sql | sudo docker-compose exec -T \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; ghostmysql mysql -u root -psuper-secret-password ghostdb
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Start all the containers:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;sudo docker-compose up -d
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ghost was back online with the older version and everything looked good!
I updated my &lt;code&gt;docker-compose.yaml&lt;/code&gt; back to use &lt;code&gt;latest&lt;/code&gt; for the Ghost version and ran &lt;code&gt;sudo docker-compose up -d&lt;/code&gt; once more.&lt;/p&gt;
&lt;p&gt;Within seconds, the new container image was in place and the container was running!
Both migrations completed in seconds and the blog was back online with the newest version. 🎉&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Theta is one of many &lt;a href="https://en.wikipedia.org/wiki/Greeks_(finance)"&gt;financial Greeks&lt;/a&gt; that measure certain aspects of options contracts in the market.
It&amp;rsquo;s also a &lt;a href="https://en.wikipedia.org/wiki/Theta"&gt;letter in the Greek alphabet&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;The default collation in MySQL 8 is &lt;code&gt;utf8mb4_0900_ai_ci&lt;/code&gt;.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Survive the Google Reader exodus with Tiny Tiny RSS</title><link>https://major.io/p/survive-the-google-reader-exodus-with-tiny-tiny-rss/</link><pubDate>Sun, 17 Mar 2013 21:27:38 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/survive-the-google-reader-exodus-with-tiny-tiny-rss/</guid><description>&lt;p&gt;&lt;img alt="1" loading="lazy" src="https://major.io/wp-content/uploads/2013/03/tinytinyrss.png"&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s no secret that &lt;a href="http://en.wikipedia.org/wiki/Google_Reader"&gt;Google Reader&lt;/a&gt; is a popular way to keep up with your RSS feeds, but it&amp;rsquo;s &lt;a href="http://www.newyorker.com/online/blogs/books/2013/03/farewell-dear-reader.html"&gt;getting shelved later this year&lt;/a&gt;. Most folks &lt;a href="http://news.cnet.com/8301-1023_3-57574777-93/feedly-adds-500k-new-users-on-google-decision-to-kill-reader/"&gt;suggested Feedly as a replacement&lt;/a&gt; but I found the UI quite clunky in a browser and on Android devices.&lt;/p&gt;
&lt;p&gt;Then someone suggested &lt;a href="http://tt-rss.org/redmine/projects/tt-rss/wiki"&gt;Tiny Tiny RSS&lt;/a&gt;. I couldn&amp;rsquo;t learn more about it on the day Google Reader&amp;rsquo;s shutdown was announced because the site was slammed. In a nutshell, Tiny Tiny RSS is a well-written web UI for managing feeds and a handy API for using it with mobile applications. The backend code is written in PHP and it supports MySQL and Postgres.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s also an &lt;a href="https://play.google.com/store/apps/details?id=org.fox.ttrss&amp;amp;hl=en"&gt;Android application&lt;/a&gt; that gives you a seven day trial once you install it. The &lt;a href="https://play.google.com/store/apps/details?id=org.fox.ttrss.key"&gt;pro key costs $1.99&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The installation took me a few minutes and then I was off to the races. I&amp;rsquo;d recommend implementing SSL for accessing your installation (unless you like passing credentials in the clear) and enable keepalive connections in Apache. The UI in the application drags down a ton of javascript as it works and enabling keepalives will keep your page load times low.&lt;/p&gt;
&lt;p&gt;If you want to get your Google Reader feeds moved over in bulk, just export them from Google Reader:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Click the settings cog at the top right of Google Reader and choose Reader Settings&lt;/li&gt;
&lt;li&gt;Choose Import/Export from the menu&lt;/li&gt;
&lt;li&gt;Press Export, head over to Google Takeout and download your zip file&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Unzip the file and find the .xml file. Open up a browser, access Tiny Tiny RSS and do this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Click Actions &amp;gt; Preferences&lt;/li&gt;
&lt;li&gt;Click the Feeds tab&lt;/li&gt;
&lt;li&gt;Click the OPML button at the bottom&lt;/li&gt;
&lt;li&gt;Import the xml file that was in the zip file from Google&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;From there, just &lt;a href="http://tt-rss.org/redmine/projects/tt-rss/wiki/UpdatingFeeds"&gt;choose a method for updating feeds&lt;/a&gt; and you should be all set!&lt;/p&gt;</description></item><item><title>mysql-json-bridge: a simple JSON API for MySQL</title><link>https://major.io/p/mysql-json-bridge-a-simple-json-api-for-mysql/</link><pubDate>Thu, 29 Mar 2012 02:34:53 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/mysql-json-bridge-a-simple-json-api-for-mysql/</guid><description>&lt;p&gt;My quest to get better at &lt;a href="http://python.org"&gt;Python&lt;/a&gt; led me to create a new project on GitHub. It&amp;rsquo;s called &lt;a href="https://github.com/rackerhacker/mysql-json-bridge"&gt;mysql-json-bridge&lt;/a&gt; and it&amp;rsquo;s ready for you to use.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why do we need a JSON API for MySQL?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The real need sprang from a situation I was facing daily at &lt;a href="http://rackspace.com/"&gt;Rackspace&lt;/a&gt;. We have a lot of production and pre-production environments which are in flux but we need a way to query data from various MySQL servers for multiple purposes. Some folks need data in ruby or python scripts while others need to drag in data with .NET and Java. Wrestling with the various adapters and all of the user privileges on disparate database servers behind different firewalls on different networks was less than enjoyable.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s where this bridge comes in.&lt;/p&gt;
&lt;p&gt;The bridge essentially gives anyone the ability to talk to multiple database servers across different environments by talking to a single endpoint with easily configurable security and encryption. As long as the remote user can make an HTTP POST and parse some JSON, they can query data from multiple MySQL endpoints.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How does it work?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It all starts with a simple HTTP POST. I&amp;rsquo;ve become a big fan of the Python &lt;a href="http://python-requests.org"&gt;requests&lt;/a&gt; module. If you&amp;rsquo;re using it, this is all you need to submit a query:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;import requests
payload = {&amp;#39;sql&amp;#39;: &amp;#39;SELECT * FROM some_tables WHERE some_column=some_value&amp;#39;}
url = &amp;#34;http://localhost:5000/my_environment/my_database&amp;#34;
r = requests.post(url, data=payload)
print r.text
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The bridge takes your query and feeds it into the corresponding MySQL server. When the results come back, they&amp;rsquo;re converted to JSON and returned via the same HTTP connection.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What technology does it use?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://flask.pocoo.org/"&gt;Flask&lt;/a&gt; does the heavy lifting for the HTTP requests and &lt;a href="https://github.com/facebook/tornado/blob/master/tornado/database.py"&gt;Facebook&amp;rsquo;s Tornado database class&lt;/a&gt; wraps the &lt;a href="http://mysql-python.sourceforge.net/"&gt;MySQLdb&lt;/a&gt; module in something a little more user friendly. Other than those modules, &lt;a href="http://pyyaml.org/"&gt;PyYAML&lt;/a&gt; and &lt;a href="http://python-requests.org"&gt;requests&lt;/a&gt; are the only other modules not provided by the standard Python libraries.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is it fast?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Yes. I haven&amp;rsquo;t done any detailed benchmarks on it yet, but the overhead is quite low even with a lot of concurrency. The biggest slowdowns come from network latency between you and the bridge or between the bridge and the database server. Keep in mind that gigantic result sets will take a longer time to transfer across the network and get transformed into JSON.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I found a bug. I have an idea for an improvement. You&amp;rsquo;re terrible at Python.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;All feedback (and every pull request) is welcome. I&amp;rsquo;m still getting the hang of Python (hey, I&amp;rsquo;ve only been writing in it seriously for a few weeks!) and I&amp;rsquo;m always eager to learn a new or better way to accomplish something. Feel free to create an issue in GitHub or submit a pull request with a patch.&lt;/p&gt;</description></item><item><title>Monitor MySQL restore progress with pv</title><link>https://major.io/p/monitor-mysql-restore-progress-with-pv/</link><pubDate>Wed, 24 Nov 2010 16:43:28 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/monitor-mysql-restore-progress-with-pv/</guid><description>&lt;p&gt;The &lt;a href="http://linux.die.net/man/1/pv"&gt;pv&lt;/a&gt; command is one that I really enjoy using but it&amp;rsquo;s also one that I often forget about. You can&amp;rsquo;t get a much more concise definition of what pv does than this one:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;pv allows a user to see the progress of data through a pipeline, by giving information such as time elapsed, percentage completed (with progress bar), current throughput rate, total data transferred, and ETA.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The usage certainly isn&amp;rsquo;t complicated:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;To use it, insert it in a pipeline between two processes, with the appropriate options. Its standard input will be passed through to its standard output and progress will be shown on standard error.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A great application of pv is when you&amp;rsquo;re restoring large amounts of data into MySQL, especially if you&amp;rsquo;re restoring data under duress due to an accidentally-dropped table or database. (Who hasn&amp;rsquo;t been there before?) The standard way of restoring data is something we&amp;rsquo;re all familiar with:&lt;/p&gt;
&lt;pre lang="html"&gt;# mysql my_database &amp;lt; database_backup.sql&lt;/pre&gt;
&lt;p&gt;The downside of this method is that you have no idea how quickly your restore is working or when it might be done. You could always open another terminal to monitor the tables and databases as they&amp;rsquo;re created, but that can be hard to follow.&lt;/p&gt;
&lt;p&gt;Toss in pv and that problem is solved:&lt;/p&gt;
&lt;pre lang="html"&gt;# pv database_backup.sql | mysql my_database
96.8MB 0:00:17 [5.51MB/s] [==&gt; ] 11% ETA 0:02:10
&lt;/pre&gt;
&lt;p&gt;When it comes to MySQL, your restore rate is going to be different based on some different factors, so the ETA might not be entirely accurate.&lt;/p&gt;</description></item><item><title>Private network interfaces: the forgotten security hole</title><link>https://major.io/p/private-network-interfaces-the-forgotten-security-hole/</link><pubDate>Tue, 02 Mar 2010 00:55:07 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/private-network-interfaces-the-forgotten-security-hole/</guid><description>&lt;p&gt;Regardless of the type of hosting you&amp;rsquo;re using - dedicated or cloud - it&amp;rsquo;s important to take network interface security seriously. Most often, threats from the internet are the only ones mentioned. However, if you share a private network with other customers, you have just as much risk on that interface.&lt;/p&gt;
&lt;p&gt;Many cloud providers allow you access to a private network environment where you can exchange data with other instances or other services offered by the provider. The convenience of this access comes with a price: other instances can access your instance on the private network just as easily as they could on the public interface.&lt;/p&gt;
&lt;p&gt;Here are some security tips for your private interfaces:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Disable the private interface&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This one is pretty simple. If you have only one instance or server, and you don&amp;rsquo;t need to communicate privately with any other instances, just disable the interface. Remember to configure your networking scripts to leave the interface disabled after reboots.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use packet filtering&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The actual mechanism will vary based on your operating system, but filtering packets is the one of the simplest ways to secure your private interface. You can take some different approaches with them, but I find the easiest method is to allow access from your other instances and reject all other traffic.&lt;/p&gt;
&lt;p&gt;For additional security, you can limit access based on ports as well as source IP addresses. This could prevent an attacker from having easy access to your other instances if they&amp;rsquo;re able to break into one of them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Configure your daemons to listen on the appropriate interfaces&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If there are services that don&amp;rsquo;t need to be listening on the private network, don&amp;rsquo;t allow them to listen on your private interface. For example, MySQL might need to listen on the private interface so the web server can talk to it, but apache won&amp;rsquo;t need to listen on the private interface. This reduces the profile of your instance on the private network and makes it a less likely target for attack.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use hosts.allow and hosts.deny&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Many new systems administrators forget about how handy tcpwrappers can be for limiting access. If your firewall is down in error, host.allow and hosts.deny could be an extra layer of protection. It&amp;rsquo;s important to ensure that the daemons you are attempting to control are build with tcpwrappers support. Daemons like sshd support it, but apache and MySQL do not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Encrypt all traffic on the private network&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Just because it&amp;rsquo;s called a &amp;ldquo;private&amp;rdquo; network doesn&amp;rsquo;t mean that your traffic can traverse the network privately. You should always err on the side of caution and encrypt all traffic traversing the private network. You can use ssh tunnels, stunnel, or the built-in SSL features found in most daemons.&lt;/p&gt;
&lt;p&gt;This also brings up an important point: &lt;strong&gt;you should know how your provider&amp;rsquo;s private network works&lt;/strong&gt;. Are there safeguards to prevent sniffing? Could someone else possibly ARP spoof your instance&amp;rsquo;s private IP addresses? Is your private network&amp;rsquo;s subnet shared among many customers?&lt;/p&gt;
&lt;p&gt;With all of that said, it&amp;rsquo;s also very important to have proper change control policies so that administrators working after you are fully aware of the security measures in place and why they are important. This will ensure that all of the administrators on your instances will understand the security of the system and they should be able to make sensible adjustments later for future functionality.&lt;/p&gt;</description></item><item><title>MySQL: The total number of locks exceeds the lock table size</title><link>https://major.io/p/mysql-the-total-number-of-locks-exceeds-the-lock-table-size-2/</link><pubDate>Tue, 16 Feb 2010 18:00:29 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/mysql-the-total-number-of-locks-exceeds-the-lock-table-size-2/</guid><description>&lt;p&gt;If you&amp;rsquo;re running an operation on a large number of rows within a table that uses the InnoDB storage engine, you might see this error:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ERROR 1206 (HY000): The total number of locks exceeds the lock table size&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;MySQL is trying to tell you that it doesn&amp;rsquo;t have enough room to store all of the row locks that it would need to execute your query. The only way to fix it for sure is to adjust &lt;code&gt;innodb_buffer_pool_size&lt;/code&gt; and restart MySQL. By default, this is set to only 8MB, which is too small for anyone who is using InnoDB to do anything.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you need a temporary workaround,&lt;/strong&gt; reduce the amount of rows you&amp;rsquo;re manipulating in one query. For example, if you need to delete a million rows from a table, try to delete the records in chunks of 50,000 or 100,000 rows. If you&amp;rsquo;re inserting many rows, try to insert portions of the data at a single time.&lt;/p&gt;
&lt;p&gt;Further reading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://bugs.mysql.com/bug.php?id=15667"&gt;MySQL Bug #15667 - The total number of locks exceeds the lock table size&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://mrothouse.wordpress.com/2006/10/20/mysql-error-1206/"&gt;MySQL Error 1206 » Mike R&amp;rsquo;s Blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>MySQL: The total number of locks exceeds the lock table size</title><link>https://major.io/p/mysql-the-total-number-of-locks-exceeds-the-lock-table-size/</link><pubDate>Fri, 29 Jan 2010 13:12:21 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/mysql-the-total-number-of-locks-exceeds-the-lock-table-size/</guid><description>&lt;p&gt;This problem has cropped up for me a few times, but I&amp;rsquo;ve always forgotten to make a post about it. If you&amp;rsquo;re working with a large InnoDB table and you&amp;rsquo;re updating, inserting, or deleting a large volume of rows, you may stumble upon this error:&lt;/p&gt;
&lt;pre lang="html"&gt;ERROR 1206 (HY000): The total number of locks exceeds the lock table size&lt;/pre&gt;
&lt;p&gt;InnoDB stores its lock tables in the main buffer pool. This means that the number of locks you can have at the same time is limited by the &lt;code&gt;innodb_buffer_pool_size&lt;/code&gt; variable that was set when MySQL was started. By default, MySQL leaves this at 8MB, which is pretty useless if you&amp;rsquo;re doing anything with InnoDB on your server.&lt;/p&gt;
&lt;p&gt;Luckily, the fix for this issue is very easy: adjust &lt;code&gt;innodb_buffer_pool_size&lt;/code&gt; to a more reasonable value. However, that fix does require a restart of the MySQL daemon. There&amp;rsquo;s simply no way to adjust this variable on the fly (with the current stable MySQL versions as of this post&amp;rsquo;s writing).&lt;/p&gt;
&lt;p&gt;Before you adjust the variable, make sure that your server can handle the additional memory usage. The &lt;code&gt;innodb_buffer_pool_size&lt;/code&gt; variable is a server wide variable, not a per-thread variable, so it&amp;rsquo;s shared between all of the connections to the MySQL server (like the query cache). If you set it to something like 1GB, MySQL won&amp;rsquo;t use all of that up front. As MySQL finds more things to put in the buffer, the memory usage will gradually increase until it reaches 1GB. At that point, the oldest and least used data begins to get pruned when new data needs to be present.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So, you need a workaround without a MySQL restart?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re in a pinch, and you need a workaround, break up your statements into chunks. If you need to delete a million rows, try deleting 5-10% of those rows per transaction. This may allow you to sneak under the lock table size limitations and clear out some data without restarting MySQL.&lt;/p&gt;
&lt;p&gt;To learn more about InnoDB&amp;rsquo;s parameters, visit the &lt;a href="http://dev.mysql.com/doc/refman/5.0/en/innodb-parameters.html"&gt;MySQL documentation&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>PHPMyAdmin 3.x hides the table indexes</title><link>https://major.io/p/phpmyadmin-3x-hides-the-table-indexes/</link><pubDate>Sat, 04 Apr 2009 00:51:48 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/phpmyadmin-3x-hides-the-table-indexes/</guid><description>&lt;p&gt;Users of PHPMyAdmin 3.x may find that the table indexes are automatically hidden at the bottom of the page. I find this to be a huge annoyance since table indexes are tremendously important to the structure of the table.&lt;/p&gt;
&lt;p&gt;If you don&amp;rsquo;t want to downgrade to PHPMyAdmin 2.x, just add the following line to the top of your config.inc.php file:&lt;/p&gt;
&lt;pre lang="php"&gt;$cfg['InitialSlidersState'] = 'open';&lt;/pre&gt;
&lt;p&gt;This will cause the indexes to be displayed when you click &lt;strong&gt;Structure&lt;/strong&gt; for a certain table. By default, they are hidden.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Sidenote:&lt;/strong&gt; Some of you might be thinking: “Hey, you&amp;rsquo;re a DBA, you should know MySQL queries without needing PHPMyAdmin.” You&amp;rsquo;re right. I do know how to get the job done without PHPMyAdmin, but I enjoy the way PHPMyAdmin allows me to visualize my table structures. Also, it&amp;rsquo;s a handy way to present data to others very quickly.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Importing Excel files into MySQL with PHP</title><link>https://major.io/p/importing-excel-files-into-mysql-with-php/</link><pubDate>Fri, 07 Nov 2008 19:42:45 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/importing-excel-files-into-mysql-with-php/</guid><description>&lt;p&gt;If you have Excel files that need to be imported into MySQL, you can import them easily with PHP. First, you will need to download some prerequisites:&lt;/p&gt;
&lt;p&gt;PHPExcelReader - &lt;a href="http://sourceforge.net/projects/phpexcelreader/"&gt;http://sourceforge.net/projects/phpexcelreader/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Spreadsheet_Excel_Writer - &lt;a href="http://pear.php.net/package/Spreadsheet_Excel_Writer"&gt;http://pear.php.net/package/Spreadsheet_Excel_Writer&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Once you&amp;rsquo;ve downloaded both items, upload them to your server. Your directory listing on your server should have two directories: &lt;code&gt;Excel&lt;/code&gt; &lt;em&gt;(from PHPExcelReader)&lt;/em&gt; and &lt;code&gt;Spreadsheet_Excel_Writer-x.x.x&lt;/code&gt; &lt;em&gt;(from Spreadsheet_Excel_Writer)&lt;/em&gt;. To work around a bug in PHPExcelReader, copy &lt;code&gt;oleread.inc&lt;/code&gt; from the &lt;code&gt;Excel&lt;/code&gt; directory into a new path:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Spreadsheet/Excel/Reader/OLERead.php&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The PHPExcelReader code will expect &lt;code&gt;OLERead.php&lt;/code&gt; to be in that specific location. Once that is complete, you&amp;rsquo;re ready to use the PHPExcelReader class. I made an example Excel spreadsheet like this:&lt;/p&gt;
&lt;pre&gt;Name Extension Email
----------------------------------------------------
Jon Smith 2001 jsmith@domain.com
Clint Jones 2002 cjones@domain.com
Frank Peterson 2003 fpeterson@domain.com&lt;/pre&gt;
&lt;p&gt;After that, I created a PHP script to pick up the data and insert it into the database, row by row:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;require_once &amp;#39;Excel/reader.php&amp;#39;;
$data = new Spreadsheet_Excel_Reader();
$data-&amp;gt;setOutputEncoding(&amp;#39;CP1251&amp;#39;);
$data-&amp;gt;read(&amp;#39;exceltestsheet.xls&amp;#39;);

$conn = mysql_connect(&amp;#34;hostname&amp;#34;,&amp;#34;username&amp;#34;,&amp;#34;password&amp;#34;);
mysql_select_db(&amp;#34;database&amp;#34;,$conn);

for ($x = 2; $x &amp;lt; = count($data-&amp;gt;sheets[0][&amp;#34;cells&amp;#34;]); $x++) {
 $name = $data-&amp;gt;sheets[0][&amp;#34;cells&amp;#34;][$x][1];
 $extension = $data-&amp;gt;sheets[0][&amp;#34;cells&amp;#34;][$x][2];
 $email = $data-&amp;gt;sheets[0][&amp;#34;cells&amp;#34;][$x][3];
 $sql = &amp;#34;INSERT INTO mytable (name,extension,email)
 VALUES (&amp;#39;$name&amp;#39;,$extension,&amp;#39;$email&amp;#39;)&amp;#34;;
 echo $sql.&amp;#34;\n&amp;#34;;
 mysql_query($sql);
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After the script ran, each row had been added to the database table successfully. If you have additional columns to insert, just repeat these lines, using an appropriate variable for each column:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sheets[0][&amp;#34;cells&amp;#34;][$row_number][$column_number];
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For more details, you can refer to a &lt;a href="http://devzone.zend.com/article/3336-Reading-and-Writing-Spreadsheets-with-PHP"&gt;post in Zend&amp;rsquo;s Developer Zone&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>MySQL: ERROR 1040: Too many connections</title><link>https://major.io/p/mysql-error-1040-too-many-connections/</link><pubDate>Tue, 24 Jun 2008 17:00:47 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/mysql-error-1040-too-many-connections/</guid><description>&lt;p&gt;If you run a fairly busy and/or badly configured MySQL server, you may receive something like this when attempting to connect:&lt;/p&gt;
&lt;pre lang="html"&gt;# mysql
ERROR 1040: Too many connections&lt;/pre&gt;
&lt;p&gt;MySQL is telling you that it is handling the maximum connections that you have configured it to handle. By default, MySQL will handle 100 connections simultaneously. This is very similar to the situation when Apache reaches the MaxClients setting. You won&amp;rsquo;t even be able to connect to MySQL to find out what is causing the connections to be used up, so you will be forced to restart the MySQL daemon to troubleshoot the issue.&lt;/p&gt;
&lt;p&gt;What causes MySQL to run out of connections? Here&amp;rsquo;s a list of reasons that may cause MySQL to run out of available connections, listed in order of what you should check:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bad MySQL configuration&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Verify that you have set MySQL&amp;rsquo;s buffers and caches to appropriate levels for the type of data you&amp;rsquo;re storing and the types of queries that you are running. One quick way to check this information is via &lt;a href="http://rackerhacker.com/mysqltuner/"&gt;MySQLTuner&lt;/a&gt;. The script will tell you how well your server is performing along with the corrections you should make. Running the script only takes a few moments and it doesn&amp;rsquo;t require a DBA to decipher the results.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Data storage techniques&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Remember that MySQL works best when moving vertically, not horizontally. If you have a table with 20 columns, breaking it into two tables with 10 columns each will improve performance. Even if you need to join the two tables together to get your data, it will still perform at a higher level. Also, use the right data types for the right data. If you&amp;rsquo;re storing an integer only, don&amp;rsquo;t use a CHAR or VARCHAR data type. If your integer will be small, then use something like a TINYINT or SMALLINT rather than INT. This means MySQL will use less memory, pull less data from the disk, and have higher performing joins.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Slow queries&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;These are generally pretty easy to fix. If you have queries that don&amp;rsquo;t use indexes, or if queries run slowly with indexes in place, you need to rethink how you&amp;rsquo;re pulling your data. Should your data be split into multiple tables? Are you pulling more data than you need? Keep these questions in mind, enable the slow query log, and re-work your queries to find where the bottlenecks occur.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Division of labor&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Most people who use MySQL have a dynamic site written in a scripting language, like PHP, Perl or Python. It&amp;rsquo;s obvious that your server will need to do some work to parse the scripts, send data back to the client, and communicate with MySQL. If you find that your server is overworked, consider moving MySQL to its own dedicated hardware. Among many other things, this will reduce your disk I/O, allow you to better utilize memory, and it will help you when you need to scale even further. Be sure to keep your MySQL server close to your web servers, however, as increased latency will only make your performance problem first.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Right hardware&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Do you have the right hardware for the job? Depending on your budget, you may need to make the move for hardware that gives you better I/O throughput and more useable cores. MySQL is a multi-threaded application, so it can utilize multiple cores to serve data quickly. Also, writing logs, reading tables, and adjusting indexes are disk-intensive tasks that need fast drives to perform well. When you look for a dedicated server for MySQL, be sure to choose multiple-core machines with low latency RAM, fast drives (SCSI/SAS), and a reliable network interface.&lt;/p&gt;
&lt;p&gt;By reviewing these bottlenecks, you can reduce the load on your MySQL server without increasing your maximum connections. Simply increasing the maximum connections &lt;strong&gt;is a very bad idea&lt;/strong&gt;. This can cause MySQL to consume unnecessary resources on your server and it may lead to an unstable system (crash!).&lt;/p&gt;</description></item><item><title>Remove backticks from MySQL dumps</title><link>https://major.io/p/remove-backticks-from-mysql-dumps/</link><pubDate>Wed, 18 Jun 2008 17:00:01 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/remove-backticks-from-mysql-dumps/</guid><description>&lt;p&gt;I found myself in a peculiar situation last week. I&amp;rsquo;d been asked to downgrade a server from MySQL 4.1 to MySQL 3.23. Believe me, I tried to advise against the request, but I didn&amp;rsquo;t succeed.&lt;/p&gt;
&lt;p&gt;I made a MySQL 3.23 compatible dump with &lt;code&gt;--compatible=mysql323&lt;/code&gt;, but the dump came out with backticks around the database names. This works with some 3.23 versions, but it doesn&amp;rsquo;t work with others. Apparently RHEL 3&amp;rsquo;s MySQL 3.23 is one of those versions where it simply won&amp;rsquo;t work.&lt;/p&gt;
&lt;p&gt;This sed line came in handy to strip the backticks from the &lt;code&gt;USE&lt;/code&gt; lines in the dump:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sed -e &amp;#34;s/^USE \`\(.*\)\`/USE \1/g&amp;#34;
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>MySQL: Can’t drop one or more of the requested users</title><link>https://major.io/p/mysql-cant-drop-one-or-more-of-the-requested-users/</link><pubDate>Wed, 11 Jun 2008 23:59:37 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/mysql-cant-drop-one-or-more-of-the-requested-users/</guid><description>&lt;p&gt;MySQL has quite a few cryptic error messages, and this one is one of the best:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;mysql&amp;gt; DROP USER &amp;#39;forums&amp;#39;@&amp;#39;db1.myserver.com&amp;#39;;
ERROR 1268 (HY000): Can&amp;#39;t drop one or more of the requested users
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Naturally, I was quite interested to know why MySQL wasn&amp;rsquo;t going to allow me to remove this user. There was nothing special about the user, but then again, this wasn&amp;rsquo;t a server that I personally managed, so I wasn&amp;rsquo;t sure what kind of configuration was in place.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s always a good idea to get your bearings, so I checked the current grants:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mysql&amp;gt; SHOW GRANTS FOR 'forums'@'db1.myserver.com';
+----------------------------------------------------------------------+
| Grants for forums@db1.myserver.com |
+----------------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'forums'@'db1.myserver.com' WITH GRANT OPTION |
+----------------------------------------------------------------------+
1 row in set (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The GRANT OPTION was causing my grief. It was the only privilege that the user had on the server. I revoked the privilege and attempted to drop the user yet again:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;mysql&amp;gt; REVOKE GRANT OPTION ON *.* FROM &amp;#39;forums&amp;#39;@&amp;#39;db1.myserver.com&amp;#39;;
Query OK, 0 rows affected (0.00 sec)
mysql&amp;gt; DROP USER &amp;#39;forums&amp;#39;@&amp;#39;db1.myserver.com&amp;#39;;
Query OK, 0 rows affected (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It&amp;rsquo;s key to remember that revoking the GRANT OPTION is a completely separate process. Revoking ALL PRIVILEGES doesn&amp;rsquo;t include GRANT OPTION, so be sure to specify it separately:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;mysql&amp;gt; REVOKE ALL PRIVILEGES, GRANT OPTION ON *.* FROM &amp;#39;user&amp;#39;@&amp;#39;host&amp;#39;;
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Backing up MySQL to Amazon’s S3</title><link>https://major.io/p/backing-up-mysql-to-amazons-s3/</link><pubDate>Fri, 06 Jun 2008 00:18:49 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/backing-up-mysql-to-amazons-s3/</guid><description>&lt;p&gt;I received an e-mail from &lt;a href="http://www.timlinden.com/"&gt;Tim Linden&lt;/a&gt; about a &lt;a href="http://www.timlinden.com/blog/server/backup-mysql-amazon-s3/"&gt;post he made in his blog&lt;/a&gt; about backing up MySQL data to &lt;a href="http://en.wikipedia.org/wiki/Amazon_S3"&gt;Amazon&amp;rsquo;s S3&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The article goes over installing the Net::Amazon::S3 Perl module via WHM (which is handy for the cPanel users). However, if you&amp;rsquo;re not a cPanel user, you can install it via CPAN:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# perl -MCPAN -e &amp;#39;install Net::Amazon::S3&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you&amp;rsquo;d rather install it through Webmin, go to the &amp;lsquo;Others&amp;rsquo; section, and click &amp;lsquo;Perl Modules&amp;rsquo;.&lt;/p&gt;
&lt;p&gt;Also, Tim mentions configuring a &lt;a href="http://www.rjonna.com/ext/s3fox.php"&gt;Firefox extension for accessing S3&lt;/a&gt; that works very well. However, I find myself using Safari most often, so I prefer to use &lt;a href="http://www.jungledisk.com/"&gt;Jungle Disk&lt;/a&gt; or &lt;a href="http://www.panic.com/transmit/"&gt;Transmit&lt;/a&gt; on my Mac.&lt;/p&gt;
&lt;p&gt;Overall, it&amp;rsquo;s a great post, and I&amp;rsquo;m glad Tim told me about it!&lt;/p&gt;</description></item><item><title>Importing MySQL dumps made on 64-bit servers</title><link>https://major.io/p/importing-mysql-dumps-made-on-64-bit-servers/</link><pubDate>Fri, 21 Mar 2008 17:51:56 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/importing-mysql-dumps-made-on-64-bit-servers/</guid><description>&lt;p&gt;It&amp;rsquo;s tough to find examples of dumps that can&amp;rsquo;t be properly reimported on other servers. However, if you have a 64-bit server, and you make a MySQL dump file from it, you may see this issue when importing the dump on a 32-bit MySQL server:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ERROR 1118 (42000) at line 1686: Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. You have to change some columns to TEXT or BLOBs
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You really don&amp;rsquo;t have any options in this situation. You&amp;rsquo;ll need to adjust your table on the 64-bit server for good and then make a new dump file, or you will just have to live with the fact that it can&amp;rsquo;t be imported into a 32-bit instance of MySQL.&lt;/p&gt;</description></item><item><title>Plesk and MySQL 5</title><link>https://major.io/p/plesk-and-mysql-5/</link><pubDate>Wed, 30 Jan 2008 18:29:18 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/plesk-and-mysql-5/</guid><description>&lt;p&gt;One of the questions I receive the most is: &amp;ldquo;What version of Plesk works with MySQL 5?&amp;rdquo; The minimum version of Plesk for MySQL 5 is &lt;strong&gt;8.1.0&lt;/strong&gt;. If you install MySQL 5 on a version prior to 8.1.0, you may be able to access then panel in the other 8.x versions, but your upgrades will fail miserably.&lt;/p&gt;
&lt;p&gt;In case you&amp;rsquo;re curious about a slightly older system, full MySQL 4 support was available in Plesk &lt;strong&gt;7.5.3&lt;/strong&gt;. However, MySQL 4 is supported on some distributions as far back as &lt;strong&gt;7.1&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Fedora Core 2&lt;/p&gt;
&lt;p&gt;Mandrake 10&lt;/p&gt;
&lt;p&gt;SuSE 9.0&lt;/p&gt;
&lt;p&gt;FreeBSD 5.2.1&lt;/p&gt;
&lt;p&gt;Check out SWSoft/Parallel&amp;rsquo;s site for more information about MySQL &lt;a href="http://kb.swsoft.com/en/305"&gt;4&lt;/a&gt; and &lt;a href="http://kb.swsoft.com/en/1792"&gt;5&lt;/a&gt; support.&lt;/p&gt;</description></item><item><title>MySQL Replication: Wrap-up</title><link>https://major.io/p/mysql-replication-wrap-up/</link><pubDate>Tue, 15 Jan 2008 18:20:02 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/mysql-replication-wrap-up/</guid><description>&lt;p&gt;After a couple of weeks, my MySQL replication series has come to a close. Here&amp;rsquo;s links to all of the topics that I covered:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://rackerhacker.com/2008/01/02/mysql-replication-performance/"&gt;Performance&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://rackerhacker.com/2008/01/03/mysql-replication-redundancy/"&gt;Redundancy&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://rackerhacker.com/2008/01/04/mysql-replication-backups-data-integrity/"&gt;Backups and Data Integrity&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://rackerhacker.com/2008/01/07/mysql-replication-horizontal-data-partitioning/"&gt;Horizontal Data Partitioning&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://rackerhacker.com/2008/01/08/mysql-replication-delayed-slaves/"&gt;Delayed Slaves&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://rackerhacker.com/2008/01/09/mysql-replication-breakdown/"&gt;Breakdown&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://rackerhacker.com/2008/01/10/mysql-replication-across-an-external-network/"&gt;Replication Across an External Network&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://rackerhacker.com/2008/01/11/mysql-replication-upgrading-the-mysql-server/"&gt;Upgrading the MySQL Server&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://rackerhacker.com/2008/01/14/mysql-replication-slave-performance/"&gt;Slave Performance&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If there&amp;rsquo;s any other questions that you have, please let me know and I&amp;rsquo;ll be happy to add some extra posts on your topic!&lt;/p&gt;</description></item><item><title>MySQL Replication: Slave Performance</title><link>https://major.io/p/mysql-replication-slave-performance/</link><pubDate>Mon, 14 Jan 2008 18:26:40 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/mysql-replication-slave-performance/</guid><description>&lt;p&gt;There&amp;rsquo;s a few final configuration options that may help the performance of your slave MySQL servers. If you&amp;rsquo;re not using certain storage engines, like InnoDB or Berkeley, then by all means, remove them from your configuration. For those two specifically, just add the following to your my.cnf on the slave server:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;skip-innodb&amp;lt;br /&amp;gt; skip-bdb&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;To reduce disk I/O on big MyISAM write operations, you can &lt;a href="http://dev.mysql.com/doc/refman/5.0/en/server-options.html#option_mysqld_delay-key-write"&gt;delay the flushing of indexes&lt;/a&gt; to the disk:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;delay_key_write = ALL&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You can also make all of your &lt;a href="http://dev.mysql.com/doc/refman/5.0/en/server-options.html#option_mysqld_low-priority-updates"&gt;write queries take a backseat&lt;/a&gt; to any reads:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;low-priority-updates&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Keep in mind, however, that the last two options will increase slave performance, but it &lt;a href="http://rackerhacker.com/2008/01/08/mysql-replication-delayed-slaves/"&gt;may cause them to lag behind the master&lt;/a&gt;. Depending on your application, this may not be acceptable.&lt;/p&gt;</description></item><item><title>MySQL Replication: Upgrading the MySQL server</title><link>https://major.io/p/mysql-replication-upgrading-the-mysql-server/</link><pubDate>Fri, 11 Jan 2008 23:44:54 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/mysql-replication-upgrading-the-mysql-server/</guid><description>&lt;p&gt;If you want to make a DBA nervous, just let them know that they need to upgrade MySQL servers that are replicating in a production environment. There&amp;rsquo;s multiple ways to get the job done, but here is the safest route:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;First,&lt;/strong&gt; make sure you have dumped all of your databases properly. Verify that your backups are correct and intact, and that you have multiple copies of them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Next,&lt;/strong&gt; upgrade the slave servers individually to the newest version. After upgrading the first one, make sure the slave server is operating properly. If it is working properly, then you can continue to upgrade the other slaves.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Once all of the slaves have been upgraded,&lt;/strong&gt; then you can upgrade the master. If a busy web application is sending write queries to the master, you may want to put up a temporary page that tells visitors that maintenance is being performed. Once all of the writes clear out, stop the master and upgrade it.&lt;/p&gt;
&lt;p&gt;After the master starts up, be sure that the slaves reconnect, and you might want to perform a test write query. Verify that the write is performed on the slaves as it was done on the master.&lt;/p&gt;</description></item><item><title>MySQL Replication: Across an external network</title><link>https://major.io/p/mysql-replication-across-an-external-network/</link><pubDate>Thu, 10 Jan 2008 18:51:39 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/mysql-replication-across-an-external-network/</guid><description>&lt;p&gt;While many people might find replicating over an external network to be an odd concept, it does have some uses. For example, if you need to replicate data for local access at certain locations, it may be helpful. Also, if you have a dedicated server, you can replicate to your home to run backups.&lt;/p&gt;
&lt;p&gt;First off, you&amp;rsquo;re going to need security for the connection. This is easily done with SSL. On the master, simply add the following lines to the &lt;code&gt;[mysqld]&lt;/code&gt; section and restart the master:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ssl-ca=cacert.pem&amp;lt;br /&amp;gt; ssl-cert=server-cert.pem&amp;lt;br /&amp;gt; ssl-key=server-key.pem&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;To have the slaves use SSL connections to the master server, simply add on &lt;code&gt;MASTER_SSL=1&lt;/code&gt; to the &lt;code&gt;CHANGE MASTER&lt;/code&gt; statement on the slave.&lt;/p&gt;
&lt;p&gt;Another aspect to consider is bandwidth usage. This may be a priority if your remote areas have slow downlinks, or if you are charged for your bandwidth usage. You can compress the MySQL traffic very easily. Simply add the following to the MySQL configuration file in the &lt;code&gt;[mysqld]&lt;/code&gt; section:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;slave_compressed_protocol = 1&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;With both of these changes,&lt;/strong&gt; keep in mind that there is a significant CPU overhead required to compress and/or encrypt data. Determine carefully what your application requires and test your configuration thoroughly.&lt;/p&gt;</description></item><item><title>MySQL Replication: Breakdown</title><link>https://major.io/p/mysql-replication-breakdown/</link><pubDate>Wed, 09 Jan 2008 18:24:03 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/mysql-replication-breakdown/</guid><description>&lt;p&gt;On some occasions, MySQL replication can break down if an statement comes from the master that makes no sense to the slave. For example, if an UPDATE statement arrives from the master server, but the table referenced by the UPDATE no longer exists, then the slave will halt replication and throw an error when &lt;code&gt;SHOW SLAVE STATUS;&lt;/code&gt; is run.&lt;/p&gt;
&lt;p&gt;The obvious question here is: how can the master and the slave have different data after replication has started? After all, you make a dump file prior to starting replication, so both servers contain the same information. Stray updates can be thrown into the mix from application errors or plain user errors. These kinds of things happen, even though we all try to avoid it.&lt;/p&gt;
&lt;p&gt;Don&amp;rsquo;t worry - this is almost always an easy fix. You have two main options:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fix the problem yourself.&lt;/strong&gt; If the master sent a query that the slave can&amp;rsquo;t run, fix it manually. For example, if the master wants to run an INSERT on a table that doesn&amp;rsquo;t exist, run a quick &lt;code&gt;SHOW CREATE TABLE;&lt;/code&gt; on the master and create the table manually on the slave. When the table is there, run a &lt;code&gt;START SLAVE;&lt;/code&gt; on the slave and you should be all set.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Skip an unnecessary query.&lt;/strong&gt; Let&amp;rsquo;s say that the master sent over a &lt;code&gt;DROP TABLE&lt;/code&gt; query but the table doesn&amp;rsquo;t exist on the master. It&amp;rsquo;s safe to say that the master won&amp;rsquo;t be sending any write queries to that table in the future, so the query can be skipped. To skip it, run the following statement:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mysql&amp;gt; SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 1;&amp;lt;br /&amp;gt; mysql&amp;gt; START SLAVE;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;In short, you&amp;rsquo;re telling MySQL to skip that unnecessary query and keep going with the ones after that. Of course, if you need to skip multiple queries, change the 1 to whatever number you need and then run &lt;code&gt;START SLAVE;&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>MySQL Replication: Delayed Slaves</title><link>https://major.io/p/mysql-replication-delayed-slaves/</link><pubDate>Wed, 09 Jan 2008 03:11:11 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/mysql-replication-delayed-slaves/</guid><description>&lt;p&gt;In a perfect world, slaves will contain the same data as the master at all times. The events should be picked up and executed by the slaves in milliseconds. However, in real world scenarios, replication will be held up for different reasons. Whether it&amp;rsquo;s table locks, disk I/O, network saturation, or CPU usage, slaves might become several seconds, minutes or even hours behind the master.&lt;/p&gt;
&lt;p&gt;In some situations, delays of less than 30 seconds may not be a big issue. Some applications, like social networking applications, would need to have the data match at all times. Lags would not be acceptable.&lt;/p&gt;
&lt;p&gt;For example, review this scenario. Let&amp;rsquo;s say you go to a site and create an account. That would send a write query to the master. Once you&amp;rsquo;ve finished the account creation, the page will depend on a read query. If the slave is behind the master, it won&amp;rsquo;t have any data about your new account, and the application will probably tell you that you don&amp;rsquo;t have an account. That would be pretty annoying for your application&amp;rsquo;s users.&lt;/p&gt;
&lt;p&gt;To check your current lag, simply run &lt;a href="http://dev.mysql.com/doc/refman/5.0/en/show-slave-status.html"&gt;&lt;code&gt;SHOW SLAVE STATUS;&lt;/code&gt;&lt;/a&gt; in MySQL, and review the number following &lt;code&gt;Seconds_Behind_Master&lt;/code&gt;. If everything is running well, it should be followed by 0. If &lt;code&gt;NULL&lt;/code&gt; is shown, then there is most likely an issue with replication, and you might want to check &lt;code&gt;Last_Error&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So, how can replication lags be corrected? Try these methods:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Review your queries.&lt;/strong&gt; When queries keep running in MySQL, the slave may be unable to keep up. Make sure that your read queries are as &lt;a href="http://dev.mysql.com/doc/refman/5.0/en/explain.html"&gt;optimized as possible&lt;/a&gt; so they complete quickly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Optimize your MySQL server variables.&lt;/strong&gt; Be sure to &lt;a href="http://rackerhacker.com/mysqltuner/"&gt;thoroughly review your MySQL configuration&lt;/a&gt; for any bottlenecks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Choose the right storage engines.&lt;/strong&gt; If you&amp;rsquo;re making a lot of updates to a table, &lt;a href="http://rackerhacker.com/2007/11/06/when-to-use-myisam-or-innodb/"&gt;consider using InnoDB&lt;/a&gt;. If your tables are not updated often, c&lt;a href="http://rackerhacker.com/2007/11/06/when-to-use-myisam-or-innodb/"&gt;onsider using MyISAM tables&lt;/a&gt; (or even &lt;a href="http://dev.mysql.com/doc/refman/5.0/en/myisampack.html"&gt;compressed MyISAM tables&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Upgrade your hardware.&lt;/strong&gt; Find your hardware bottleneck. If it&amp;rsquo;s the CPU, consider upgrading to a multi-core CPU, or a CPU with a higher clock speed. For I/O bottlenecks, consider a RAID solution with SAS drives. If you&amp;rsquo;re lucky enough to have a network bottleneck (lucky since it means you&amp;rsquo;re doing well with CPU and I/O), use a dedicated switch or upgrade to gigabit (or faster) hardware.&lt;/p&gt;</description></item><item><title>MySQL Replication: Horizontal Data Partitioning</title><link>https://major.io/p/mysql-replication-horizontal-data-partitioning/</link><pubDate>Mon, 07 Jan 2008 17:57:56 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/mysql-replication-horizontal-data-partitioning/</guid><description>&lt;p&gt;If you have a master with multiple slaves, you can get some performance and save money on hardware by splitting data horizontally among your servers. For example, if you have one high traffic database and two lower traffic databases, you can selectively split them among the slaves. With five slaves, set up three of the slaves to replicate your high traffic database, and the two other slaves can handle one each out of the two low traffic databases.&lt;/p&gt;
&lt;p&gt;This allows you to expand when you&amp;rsquo;re ready, and you can move your databases around to take advantage of idle servers. MySQL AB already has &lt;a href="http://dev.mysql.com/doc/refman/5.0/en/replication-solutions-partitioning.html"&gt;documentation on how to make this possible&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>MySQL Replication: Backups &amp; Data Integrity</title><link>https://major.io/p/mysql-replication-backups-data-integrity/</link><pubDate>Fri, 04 Jan 2008 19:39:12 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/mysql-replication-backups-data-integrity/</guid><description>&lt;p&gt;An often overlooked benefit of MySQL replication is the ability to make reliable backups without affecting the integrity of the MySQL data.&lt;/p&gt;
&lt;p&gt;With one MySQL server, backups have a huge impact on the server. If file-based backups are performed, you have to stop MySQL completely while the files are copied (unless you purchase expensive utilities that accomplish this while MySQL is running). If dumps are made with mysqldump, table locking and I/O operations will crush the performance of the server.&lt;/p&gt;
&lt;p&gt;You can get around these performance hits by running dumps in single transaction mode, or by restricting mysqldump to locking one table at a time. The performance gain comes at a price, however, as your backups are not a perfect snapshot. After one table is locked for a period of table, previously locked tables are actively changing and some tables might not match up.&lt;/p&gt;
&lt;p&gt;By having a slave available, you can perform a snapshot backup and lock all of the tables during the process. This provides an exact point-in-time backup with a very low effect on your MySQL servers&amp;rsquo; performance.&lt;/p&gt;</description></item><item><title>MySQL Replication: Redundancy</title><link>https://major.io/p/mysql-replication-redundancy/</link><pubDate>Fri, 04 Jan 2008 02:39:11 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/mysql-replication-redundancy/</guid><description>&lt;p&gt;Although performance is a much larger benefit of replication, it provides some redundancy for your application as well. Adding a slave server to a master allows you to perform read operations on either server, but you&amp;rsquo;re still bound to the master server for writes. In a group of multiple slaves with one master, you have your data available and online in multiple locations, which means that certain servers can fall out of replication without a large disaster.&lt;/p&gt;
&lt;p&gt;When disaster does occur, use the following recommendations as a guide.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If the master fails in a two-server replication environment,&lt;/strong&gt; you will be dead in the water with regards to write queries. You will need to convert the slave into a master. This can be done relatively quickly by following these steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Log into MySQL on the slave and run &lt;code&gt;STOP SLAVE; RESET SLAVE;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;log-bin&lt;/code&gt; to the slave&amp;rsquo;s /etc/my.cnf file and restart MySQL&lt;/li&gt;
&lt;li&gt;The slave server will now be running as a master&lt;/li&gt;
&lt;li&gt;Adjust your application to send reads and writes to the slave&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once the original master comes back online, &lt;a href="http://rackerhacker.com/2007/12/31/seven-step-mysql-replication/"&gt;set it up just like a new slave&lt;/a&gt;. You can skip some steps, such as setting the server-id, since that still should correspond to your overall configuration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If the master fails in a multiple-server replication environment,&lt;/strong&gt; you&amp;rsquo;re still in bad shape for writes. Follow the steps shown above, and then adjust the other slaves (with &lt;code&gt;CHANGE MASTER&lt;/code&gt;) so that they pull events from the new master instead.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If a slave fails in any replication environment,&lt;/strong&gt; adjust your application so that it no longer attempts to send reads to the failed slave. While you work to bring the failed slave back online, your queries will be distributed to the remaining servers.&lt;/p&gt;
&lt;p&gt;You can automate many of these operations by using applications like &lt;a href="http://www.linux-ha.org/"&gt;heartbeat&lt;/a&gt;, or by using load balancers to automatically route database traffic.&lt;/p&gt;</description></item><item><title>MySQL Replication: Performance</title><link>https://major.io/p/mysql-replication-performance/</link><pubDate>Wed, 02 Jan 2008 18:20:05 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/mysql-replication-performance/</guid><description>&lt;p&gt;MySQL replication can increase performance by allowing developers to spread queries over two servers. Queries that write data must be sent to the master at all times, but queries that read data can be sent to either server. This means that by adding a slave server to a database environment allows you to effectively double your read query performance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;However, there are some large caveats to consider here.&lt;/strong&gt; The actual web site code itself will need to be written in such a way that read and write queries can be diverted to different destinations. Depending on the size of the application and how it has been developed, the work requires to provide this functionality may be prohibitive for replication.&lt;/p&gt;
&lt;p&gt;Some load balancers can balance MySQL query traffic, and this can help if the code cannot balance the load internally. Open source applications like MySQL Proxy and pound can be used as well.&lt;/p&gt;
&lt;p&gt;Also, if the queries are not optimized, and the correct storage engines are not used, replication will not work well. If queries take an extended time to execute, the performance gains will be almost non existent. Also, if the wrong storage engines are used, and much of the rows or tables are locked, performance gains will be greatly limited. Some situations may actually cause replication to halt due to locking. When this occurs, the data on the slave becomes stale and SELECTs run against the master and slave will return different results.&lt;/p&gt;
&lt;p&gt;In short:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Replication can increase read performance&lt;/li&gt;
&lt;li&gt;It cannot fix issues caused by bad queries/storage engines&lt;/li&gt;
&lt;li&gt;Write queries can only be sent to the master&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>