Mysql – Concurrent MySQL updates hang with InnoDB (on Amazon RDS)

concurrencyinnodbMySQL

I'm running into a problem where multiple MySQL updates being performed at the same time will lock up and takes several minutes to finish. I'm using InnoDB, so I'm confused as to why this could be happening since each update is updating only 1 row. I'm also using a m2.4xlarge RDS instance (the largest they come).

Stuck updating

Here's what I'm doing: I have a table with about 100M rows in it, with "views" being a column (which is indexed), and I want to update the views on about 1M rows. On several different servers I have a loop like this where each server has it's own set of rows to be updated (pseudo code):

mysql("set autocommit=0");
mysql("start transaction");

foreach($rows as $row) {
    mysql("update table set views=views+1 where id=$row[id]");
}

mysql("commit");

This loops through all the rows that need to be updated. It works perfectly when the number of servers is small, like around 4, but when it grows to 10+ the updates start to hang in the "Updating" state all at once. Nothing says that it's waiting on a lock, it's just "Updating". This happens for about 5 minutes, where it will finally make the updates and continue through the loop and eventually happen again.

I'm not looking for alternative ways to do the updates. Having things like a tmp table and

update table,tmp_table set table.views = table.views+tmp_table.views where
  table.id = tmp_table.id

lock all the rows that are being updated until they all finish (which could be hours), which won't work for me. They MUST be in these awful loops.

I'm wondering why they could be getting stuck in the "Updating" state, and what I can do to prevent it.

tldr; Having 10+ "update" loops will eventually lock up all the updates being done, at the same time, for an unknown reason until they decide to finally make updates and continue through the loops, only for it to happen again seconds later.

SHOW VARIABLES: http://pastebin.com/NdmAeJrz

SHOW ENGINE INNODB STATUS: http://pastebin.com/Ubwu4F1h

Best Answer

I'm not looking for alternative ways to do the updates. Having things like a tmp table [will] lock all the rows that are being updated until they all finish (which could be hours), which won't work for me. They MUST be in these awful loops.

I disagree.

The strength of an RDBMS is in performing set operations like "update all these rows plz". Given this, your intuition should tell you that these "awful loops" are not the best way to go except under very rare circumstances.

Let's take a look at your current update logic and understand what it's doing.

First off, the set autocommit=0 line in your script is unnecessary. Because you explicitly open a transaction immediately after that with start transaction, autocommit automatically becomes disabled until you end the transaction with COMMIT or ROLLBACK.

Now for the meat of the logic: You've wrapped all these individual updates inside the loop in one big transaction. If your intention behind the iterative updates was to reduce locking and increase concurrency, the wrapped transaction defeats that intention. MySQL must maintain locks on every row it updates until the transaction commits so it can roll them all back at once if the transaction fails or is cancelled. Furthermore, instead of knowing in advance that it is about to lock this range of rows (which would enable MySQL to issue locks with the appropriate granularity) the engine is forced to issue a large number of row-level locks in rapid-fire. Given that you are updating 1 million rows, this is a massive burden on the engine.

I propose two solutions:

  1. Turn autocommit on and remove the transaction wrapper. MySQL will then be able to release every row lock right after it finishes updating the row. It is still forced to issue and release a massive number of locks in a short period of time, so I doubt this will be an appropriate fix for you. Furthermore, if some error occurs halfway through the loop, nothing will be rolled back since the work is not transaction-bound.

  2. Batch your updates in a temp table. You mentioned and then dismissed this solution, but I bet it will perform best. Have you already tried it? I would first test the full million-row update. If that takes too long then batch the work into progressively smaller chunks until you've found the sweet spot: the batches are big enough to get the total work done quickly, but no individual batch blocks other processes for too long. This is a common technique DBAs use when they have to modify a large number of rows during live operations. Remember, since your goal is to maximize your concurrency, keep autocommit on and don't wrap any of this work into a massive transaction so MySQL releases its locks as soon as possible.

    Notice that as the batches become progressively smaller, this solution eventually approximates the first one. That is why I am confident this solution will perform better: When the database engine can group its work into chunks, it flies.