10

We're using MySQL with InnoDB storage engine and transactions a lot, and we've run into a problem: we need a nice way to emulate Oracle's SEQUENCEs in MySQL. The requirements are: - concurrency support - transaction safety - max performance (meaning minimizing locks and deadlocks)

We don't care if some of the values won't be used, i.e. gaps in sequence are ok. There is an easy way to archieve that by creating a separate InnoDB table with a counter, however this means it will take part in transaction and will introduce locks and waiting. I am thinking to try a MyISAM table with manual locks, any other ideas or best practices?

Michael Pliskin
  • 2,352
  • 4
  • 26
  • 42

4 Answers4

18

If auto-increment isn't good enough for your needs, you can create a atomic sequence mechanism with n named sequences like this:

Create a table to store your sequences:

CREATE TABLE sequence (
  seq_name varchar(20) unique not null,
  seq_current int unsigned not null
);

Assuming you have a row for 'foo' in the table you can atomically get the next sequence id like this:

UPDATE sequence SET seq_current = (@next := seq_current + 1) WHERE seq_name = 'foo';
SELECT @next;

No locks required. Both statements need to be executed in the same session, so that the local variable @next is actually defined when the select happens.

battey
  • 458
  • 4
  • 13
Arne Claassen
  • 14,088
  • 5
  • 67
  • 106
  • Thanks - that's more or less what we came up with after all - stored procedure that does nearly exactly that. – Michael Pliskin Feb 18 '10 at 20:57
  • 1
    In case that record does not exist, use this query. `INSERT INTO sequence (seq_name, seq_current) VALUES ('foo', (@next := 1)) ON DUPLICATE KEY UPDATE seq_current = (@next := seq_current + 1);` then call `SELECT @next;` – iwat Aug 07 '12 at 09:47
  • you may want to combine iwat's comment with qu1j0t3's answer: `INSERT INTO sequences2 (seq_name, seq_current) VALUES ('foo', LAST_INSERT_ID(1)) ON DUPLICATE KEY UPDATE seq_current = LAST_INSERT_ID(seq_current + 1);` then either call `SELECT LAST_INSERT_ID();`, or if you're using an application driver, use the built-in auto-increment call (e.g. `Statement#getGeneratedKeys` in jdbc) – Alden Jul 13 '15 at 22:39
10

The right way to do this is given in the MySQL manual:

UPDATE child_codes SET counter_field = LAST_INSERT_ID(counter_field + 1);
SELECT LAST_INSERT_ID();
sth
  • 222,467
  • 53
  • 283
  • 367
qu1j0t3
  • 720
  • 8
  • 15
  • 1
    I fail to see the logic here. `LAST_INSERT_ID` is only meaningful for auto-incrementing fields. But if it's already auto-incrementing, why the need to manually increment it? – devios1 Jun 24 '13 at 18:21
  • The update statement would have no effect on LAST_INSERT_ID if it weren't manually set. This can be used as a trick to get data out of an update statement without using a separate user-defined variable. – Peter Dolberg Mar 10 '14 at 18:06
  • This approach is correct and @devios comment is misleading. `LAST_INSERT_ID(expr)` has nothing to do with auto-incrementing. – baf Jun 09 '15 at 10:54
5

We are a high transaction gaming company and need these sort of solutions for our needs. One of the features of Oracle sequences was also the increment value that could also be set.

The solution uses DUPLICATE KEY.

CREATE TABLE sequences (
  id BIGINT DEFAULT 1,
  name CHAR(20),
  increment TINYINT,
  UNIQUE KEY(name)
);

To get the next index:

Abstract the following with a stored procedure or a function sp_seq_next_val(VARCHAR):

INSERT INTO sequences (name) VALUES ("user_id") ON DUPLICATE KEY UPDATE id = id + increment;<br/>
SELECT id FROM sequences WHERE name = "user_id";
Kalle Richter
  • 8,008
  • 26
  • 77
  • 177
kopos
  • 406
  • 6
  • 12
0

Won't the MySQL Identity column on the table handle this?

CREATE TABLE table_name ( id INTEGER AUTO_INCREMENT PRIMARY KEY )

Or are you looking to use it for something other than just inserting into another table?

If you're writing using a procedural language as well (instead of just SQL) then the other option would be to create a table containing a single integer (or long integer) value and a stored procedure which locked it, selected from it, incremented it and unlocked it before returning the value.

(Note - always increment before you return the value - it maximise the chance of not getting duplicates if there are errors - or wrap the whole thing in a transaction.)

You would then call this independently of your main insert / update (so it doesn't get caught in any transactions automatically created by the calling mechanism) and then pass it as a parameter to wherever you want to use it.

Because it's independent of the rest of the stuff you're doing it should be quick and avoid locking issues. Even if you did see an error caused by locking (unlikely unless you're overloading the database) you could just call it a second / third time.

Jon Hopkins
  • 2,243
  • 25
  • 28
  • Thanks, your second approach is probably what I had in mind more or less, and the details help a lot. Any comments on what storage engine is best for such a table? – Michael Pliskin Apr 30 '09 at 08:19
  • I don't really know MySQL that well so I can't really advise on that (I've done it on SQL Server). What I would say is that I don't think it's that complex so the best approach would potentially be to use whatever you're using for the rest of your stuff and save adding complexity. – Jon Hopkins Apr 30 '09 at 09:22