views:

148

answers:

3

We're very frustratingly getting deadlocks in MySQL. It isn't because of exceeding a lock timeout as the deadlocks happen instantly when they do happen. Here's the SQL code that is executing on 2 separate threads (with 2 separate connections from the connection pool) that produces a deadlock:

UPDATE Sequences SET Counter = LAST_INSERT_ID(Counter + 1) WHERE Sequence IS NULL

Sequences table has 2 columns: Sequence and Counter

The LAST_INSERT_ID allows us to retrieve this updated counter value as per MySQL's recommendation. That works perfect for us, but we get these deadlocks! Why are we getting them and how can we avoid them??

Thanks so much for any help with this.

EDIT: this is all in a transaction (required since I'm using Hibernate) and AUTO_INCREMENT doesn't make sense here. I should've been more clear. The Sequences table holds many sequences (in our case about 100 million of them). I need to increment a counter and retrieve that value. AUTO_INCREMENT plays no role in all of this, this has nothing to do with Ids or PRIMARY KEYs.

+2  A: 

Wrap your sql statements in a transaction. If you aren't using a transaction you will get a race condition on LAST_INSERT_ID.

But really, you should have counter fields auto_increment, so you let mysql handle this.

Your third solution is to use LOCK_TABLES, to lock the sequence table so no other process can access it concurrently. This is the probably the slowest solution unless you are using INNODB.

Byron Whitlock
This is in a transaction. I edited my question above to explain that AUTO_INCREMENT isn't relevant here, sorry for not being clear on that. Locking the Sequences table is going to likely be a serious performance problem for us.
at
Transactions cause *more* deadlocks... they are not a solution to resolving a deadlock.
zombat
A: 

Deadlocks are a normal part of any transactional database, and can occur at any time. Generally, you are supposed to write your application code to handle them, as there is no surefire way to guarantee that you will never get a deadlock. That being said, there are situations that increase the likelihood of deadlocks occurring, such as the use of large transactions, and there are things you can do to mitigate their occurrence.

First thing, you should read this manual page to get a better understanding of how you can avoid them.

Second, if all you're doing is updating a counter, you should really, really, really be using an AUTO_INCREMENT column for Counter rather than relying on a "select then update" process, which as you have seen is a race condition that can produce deadlocks. Essentially, the AUTO_INCREMENT property of your table column will act as a counter for you.

Finally, I'm going to assume that you have that update statement inside a transaction, as this would produce frequent deadlocks. If you want to see it in action, try the experiment listed here. That's exactly what's happening with your code... two threads are attempting to update the same records at the same time before one of them is committed. Instant deadlock.

Your best solution is to figure out how to do it without a transaction, and AUTO_INCREMENT will let you do that.

zombat
Interesting experiment linked to, thanks. But I'm not sure that's what is happening here. I'm updating only 1 row. I'll read the manual page. This is of course in a transaction. I edited my question above to explain that AUTO_INCREMENT isn't relevant here, sorry for not being clear on that.
at
@at - you're updating only one row, but it's the same row in both transactions, hence the deadlock. If two transactions hit at the same time, both will attempt to update the same row, as `LAST_INSERT_ID()` will be the same for each before one can commit.
zombat
Why wouldn't update statements that update the same row just execute one after the other? Why the deadlock?
at
A: 

No other SQL involved ? Seems a bit unlikely to me.

The 'where sequence is null' probably causes a full table scan, causing read locks to be acquired on every row/page/... .

This becomes a problem if (your particular engine does not use MVCC and) there were an INSERT that preceded your update within the same transaction. That INSERT would have acquired an exclusive lock on some resource (row/page/...), which will cause the acquisition of a read lock by any other thread to go waiting. So two connections can first do their insert, causing each of them to have an exclusive lock on some small portion of the table, and then they both try to do your update, requiring each of them to be able to acquire a read lock on the entire table.

Erwin Smout
There was no other SQL involved in the deadlocks that had anything to do with that table. The deadlocks all happen on that UPDATE statement above. It's interesting what you say about the NULL issue. There's also an UPDATE statement where the sequence name has a value, but the deadlocks don't happen there. I figured because of the nature of our app, we need the null sequence just so much more often. If NULL causes a full table scan, I could easily change this to look for something else like an empty string. I do have inserts in the same transaction, that causes deadlocks?
at
"There was no other SQL involved" and "I do have inserts in the same transaction" ??? Come on. Deadlocks occur between transactions, not at the statement level.And why did you write that "WHERE ... IS NULL" in the first place ? Don't you want to limit your update to just the row that was just inserted by that same transaction ?
Erwin Smout
You asked me if no other SQL was involved and then suggested a problem might be an insert statement within the same transaction. That's why I responded with those quoted phrases. "WHERE Sequence IS NULL" is in fact limiting my update to just the row where Sequence is null. I'm a little confused by your last question. An insert only happens because the sequence name didn't yet exist in the Sequences table.
at