11

if I have a query like the following:

INSERT INTO table (col1,col2,col3) VALUES
('col1_value_1', 'col2_value_1', 'col3_value_1'),
('col1_value_2', 'col2_value_2', 'col3_value_2'),
('col1_value_3', 'col2_value_3', 'col3_value_3');

Suppose that I have a table where the last id PRIMARY_KEY AUTO_INCREMENT value is 56, then will this insert query always create 3 records with ids 57, 58, 59. Is this operation atomic?

Or, if another query writes on the same table, could the ids not increment always by 1?

Thanks for the attention!

EDIT: Please read the following because maybe I wasn't so clear.

Of course AUTO_INCREMENT increments by one safely, I know that.

The point is:

Let's say I have the following table called table:

 ___________________________________
| id | col1       | col2             |
-------------------------------------
|  1 | "some val" | "some other val" |
|  2 | "some val" | "some other val" |
|  3 | "some val" | "some other val" |
|  4 | "some val" | "some other val" |
|  5 | "some val" | "some other val" |
|  6 | "some val" | "some other val" |
|____________________________________|

If I know run the query:

INSERT INTO table (col1,col2) VALUES
('some val', 'some other val'),
('some val', 'some other val'),
('some val', 'some other val')

I will end up with the following table:

 ___________________________________
| id | col1       | col2             |
-------------------------------------
|  1 | "some val" | "some other val" |
|  2 | "some val" | "some other val" |
|  3 | "some val" | "some other val" |
|  4 | "some val" | "some other val" |
|  5 | "some val" | "some other val" |
|  6 | "some val" | "some other val" |
|  7 | "some val" | "some other val" |
|  8 | "some val" | "some other val" |
|  9 | "some val" | "some other val" |
|____________________________________|

Nothing to say here. But if me and another guy run the same query at the same time, are these queries atomic?, meaning that we will always end up with:

1)

 ___________________________________
| id | col1       | col2             |
-------------------------------------
|  1 | "some val" | "some other val" |
|  2 | "some val" | "some other val" |
|  3 | "some val" | "some other val" |
|  4 | "some val" | "some other val" |
|  5 | "some val" | "some other val" |
|  6 | "some val" | "some other val" |
|  7 | "some val" | "some other val" |<-- My 1st inserted record
|  8 | "some val" | "some other val" |<-- My 2nd inserted record
|  9 | "some val" | "some other val" |<-- My 3rd inserted record
| 10 | "some val" | "some other val" |<-- Another guy's 1st inserted record
| 11 | "some val" | "some other val" |<-- Another guy's 2nd inserted record
| 12 | "some val" | "some other val" |<-- Another guy's 3rd inserted record
|____________________________________|

Or with:

2)

 ___________________________________
| id | col1       | col2             |
-------------------------------------
|  1 | "some val" | "some other val" |
|  2 | "some val" | "some other val" |
|  3 | "some val" | "some other val" |
|  4 | "some val" | "some other val" |
|  5 | "some val" | "some other val" |
|  6 | "some val" | "some other val" |
|  7 | "some val" | "some other val" |<-- Another guy's 1st inserted record
|  8 | "some val" | "some other val" |<-- Another guy's 2nd inserted record
|  9 | "some val" | "some other val" |<-- Another guy's 3rd inserted record
| 10 | "some val" | "some other val" |<-- My 1st inserted record
| 11 | "some val" | "some other val" |<-- My 2nd inserted record
| 12 | "some val" | "some other val" |<-- My 3rd inserted record
|____________________________________|

Depending on which query of the two MySQL schedules first.

Or could the following abnormalities arise too?:

3)

 ___________________________________
| id | col1       | col2             |
-------------------------------------
|  1 | "some val" | "some other val" |
|  2 | "some val" | "some other val" |
|  3 | "some val" | "some other val" |
|  4 | "some val" | "some other val" |
|  5 | "some val" | "some other val" |
|  6 | "some val" | "some other val" |
|  7 | "some val" | "some other val" |<-- My 1st inserted record
|  8 | "some val" | "some other val" |<-- My 2nd inserted record
|  9 | "some val" | "some other val" |<-- Another guy's 1st inserted record - WTF???
| 10 | "some val" | "some other val" |<-- My 3rd inserted record 
| 11 | "some val" | "some other val" |<-- Another guy's 2nd inserted record
| 12 | "some val" | "some other val" |<-- Another guy's 3rd inserted record
|____________________________________|

Or something like this:

4)

 ___________________________________
| id | col1       | col2             |
-------------------------------------
|  1 | "some val" | "some other val" |
|  2 | "some val" | "some other val" |
|  3 | "some val" | "some other val" |
|  4 | "some val" | "some other val" |
|  5 | "some val" | "some other val" |
|  6 | "some val" | "some other val" |
|  7 | "some val" | "some other val" |<-- Another guy's 1st inserted record
|  8 | "some val" | "some other val" |<-- My 1st inserted record - WTF???
|  9 | "some val" | "some other val" |<-- Another guy's 2nd inserted record
| 10 | "some val" | "some other val" |<-- My 2nd inserted record - WTF^2???
| 11 | "some val" | "some other val" |<-- Another guy's 3rd inserted record
| 12 | "some val" | "some other val" |<-- My 3rd inserted record - WTF^3???
|____________________________________|

Or any other combination != 3) and 4)?

I consider 1) and 2) as atomic. Is it always guaranteed that I will always end up with 1) or 2) and never ever end up with 3) or 4) or any other combination? And if yes (I will always end up with 1) or 2)), both for MyISAM and InnoDB?

If I do SELECT LAST_INSERT_ID(); and e.g. I get 7, does it automatically mean that the rows with id 8 and 9 were also inserted by my query and not by the query of the other guy?

tonix
  • 6,671
  • 13
  • 75
  • 136
  • Yes, the id will always be incremented. – Jörn Buitink Dec 10 '15 at 11:34
  • @JörnBuitink Please, check my edit. – tonix Dec 10 '15 at 13:34
  • `LAST_INSERT_ID()` operates on connection basis. Some other guy and you will not be doing stuff in the same connection. Therefore, you are isolated and you will receive the last id of your own operations. – Mjh Dec 10 '15 at 13:46
  • 2
    @Mjh Yes, I know that me and the other user will receive the last id of our own operations and that it will be different. This is not the point. The point is: given my bulk `INSERT` query which inserts e.g. 10 records, I execute it and then I get the `LAST_INSERT_ID()`. If e.g. `LAST_INSERT_ID() = 71` does it **always** mean that the 10 records I have added have ids `71`, `72`, `73`, `74`, `75`, `76`, `77`, `78`, `79`, `80`? – tonix Dec 10 '15 at 14:07
  • It doesn't *have* to be but since it's a bulk insert, then yes. – Mjh Dec 10 '15 at 14:12
  • `It doesn't have to be`, what do you mean? – tonix Dec 10 '15 at 14:13
  • It means that it depends on the following scenario - InnoDB runs in autocommit mode = on, that means every query is its own transaction. You can execute 5 insert queries from your script, and each will be its own transaction. Another PHP process can serve "the other guy" and he can do the same. Now we have 10 queries and each is its own transaction, which means there is no guarantee that your 5 queries are served and saved in a sequence. If they were a bulk insert or in same transaction, then yes, otherwise - it doesn't have to be - mysql schedules handling concurrent requests. – Mjh Dec 10 '15 at 14:35
  • `If they were a bulk insert, ... then yes` So, the assumptions I made in the edit of my post about the `atomicity` of a bulk insert were right? `If they were a bulk insert, ... then yes` Is it true for MyISAM too? – tonix Dec 10 '15 at 14:40

5 Answers5

13

The answer is: well, it depends.

In case of myisam, the answer is a definite yes, since myisam sequences insert requests.

In case of innodb, however, the behaviour is configurable since mysql v5.1. before v5.1, then answer for InnoDB is also yes, after that it depends the on the innodb_autoinc_lock_mode setting. See mysql documentation on InnoDB auto_increment configuration for details.

To give you the highlights, there are 3 innodb_autoinc_lock_mode settings:

  1. traditional (0)
  2. consequtive (1) - default
  3. interleaved (2)

With innodb_autoinc_lock_mode set to 0 (“traditional”) or 1 (“consecutive”), the auto-increment values generated by any given statement will be consecutive, without gaps, because the table-level AUTO-INC lock is held until the end of the statement, and only one such statement can execute at a time.

With innodb_autoinc_lock_mode set to 2 (“interleaved”), there may be gaps in the auto-increment values generated by “bulk inserts,” but only if there are concurrently executing “INSERT-like” statements.

For lock modes 1 or 2, gaps may occur between successive statements because for bulk inserts the exact number of auto-increment values required by each statement may not be known and overestimation is possible.

Further gaps can be experience in the auto_increment value, if a transactions has been rolled back. A bulk insert can only be rolled back as a whole.

UPDATE: As described above, you will get scenario 1) or 2), if you use

  • myisam table engine
  • or innodb pre mysql v5.1
  • or innodb with mysql v5.1 or newer and the innodb_autoinc_lock_mode is 0 or 1

There is no way of telling which gets inserted first.

You may get scenario 3) or 4) if you use

  • innodb with innodb_autoinc_lock_mode 2

Again, there is no way of telling how and why mysql mixes up the order of the records.

So, if your question is related to the fact that you insert 3 records with bulk insert and last_insert_id() returns the auto_increment value of the first inserted record only, and you want get the ids of the other 2 records by simple addition is that you may need to check mysql's configuration based on the table engine and mysql version used.

Shadow
  • 33,525
  • 10
  • 51
  • 64
0

If you define any column primary key aut_increment then it will automatically increase value start from 1, you don't need to define this column in insert query then it will automatically insert incremented value in primary key column.

Pramod Kumar
  • 76
  • 1
  • 5
0

auto_increment is safe in concurrent environment. It's job is to give unique values, no matter how many people you have connected and working on a table. You can control the offset for incrementing, by default it's 1.

Now what does this actually mean - it means that what's written in the table doesn't have to be incremented by 1. This is the famous "gap" problem.

Suppose that you and I are writing to your table at the same time. I wrote records 10, 11, 12 and you wrote 13, 14, 15. However, something bad could have happened (a deadlock, or transaction failed) and my results aren't persisted - the queries failed and the auto_increment got spent. In this scenario, your records (13, 14, 15) are written to the disk and my aren't.

This is normal behaviour. Your table doesn't have to contain numbers that are incremented by 1. It will contain unique numbers and that's the job of auto_increment.

Mjh
  • 2,904
  • 1
  • 17
  • 16
  • @tonix - does it matter what you actually end up with? I don't think it's relevant what the order or actual values are. What you end up with depends on whether all of your insert queries are executed in a transaction. Technically, you will end up with case `1` of your example. However, what's accurate to state is that you will end up with *records with unique identifiers*. Out of curiosity, why are you asking about the actual order of insertion? – Mjh Dec 10 '15 at 13:43
  • 1
    Well `PHP`'s `PDO` class has a method `lastInsertId()`. This method returns the MySQL's `LAST_INSERT_ID()` result. When I insert multiple rows using one bulk `INSERT` query, after that I call PDO's `lastInsertId()`. It will return the `id` of the first inserted row within my bulk `INSERT`. Now, I want somehow to know the `id`s of the next `n` rows I have added after the first within that bulk query too but `PDO` doesn't provide such a lastInsertId`s`() method. Suppose I know that I have added 3 rows and lastInsertId() = 7, can I safely just take the range between (7..9) which is `7, 8, 9? – tonix Dec 10 '15 at 13:54
  • Right, since you are using `PDO`, may I ask how come you are not using prepared statements instead of bulk insert? That's a much saner choice than using bulk inserts, and you can actually retrieve all generated ids that way. With bulk inserts, you can't get what you need - and there exist something that satisfies your needs. – Mjh Dec 10 '15 at 13:58
  • Well, I can use prepared statements with bulk inserts. Are you saying that I can't use prepared statements with bulk inserts? If yes, then I can assure you that you are wrong. – tonix Dec 10 '15 at 14:01
  • I didn't state that you can't use prepared statements with bulk inserts. It's just pointless and what you are after is insert_id of each query. Bulk insert doesn't let you have that. There are also various problems associated with bulk inserts - for example, you can easily exceed the value of `max_allowed_packet`. I don't see the point of bulk inserts in your use scenario, it appears that prepared statement which is executed in a loop does what you want. Wrapping it in a transaction would also give you the high insertion rate as well. – Mjh Dec 10 '15 at 14:07
  • I understand now what you mean. – tonix Dec 10 '15 at 14:12
0

mysql treat multi insertion query as a transaction or one query, all rows will be inserted or if it failed there is no rows will be inserted, so if you insert this query:

INSERT INTO table (col1,col2,col3) VALUES
('col1_value_1', 'col2_value_1', 'col3_value_1'),
('col1_value_2', 'col2_value_2', 'col3_value_2'),
('col1_value_3', 'col2_value_3', 'col3_value_3');

mysql will run this as a one query, if your id auto incremental it will take your ids 57,58,59. if the other user pass insert query in the same time, it will 2 probability if other user query take more time than your query your query will take 57,58,59 if your query take more time than the other user so your ids will start from the other user query end. so whatever the case the multi insert query when id is auto incremental will be sorted in the query.

Gouda Elalfy
  • 6,888
  • 1
  • 26
  • 38
  • Is it always like you are saying. Both for `MyISAM` and `InnoDB`? – tonix Dec 10 '15 at 13:58
  • I think this for InnoDB, but MyIsam lock the the insertion in the same time, so if you start to run your query. other user query will not done. – Gouda Elalfy Dec 10 '15 at 14:00
  • So, what you are saying definitely **always** applies that way to MyISAM and *abnormalities* like those I have posted can't arise. Do you confirm? – tonix Dec 10 '15 at 14:03
  • I test what I say for InnoDB and sure from that in InnoDB, and I know but not confirm that MyISAM lock queries in the same time as I read http://dba.stackexchange.com/questions/21075/way-to-prevent-queries-from-waiting-for-table-level-lock – Gouda Elalfy Dec 10 '15 at 14:08
-1

If you insert value in one statement scenario 3 and 4 don't appear even if you are using them innodb_autoinc_lock_mode = 2. From documentation https://dev.mysql.com/doc/refman/5.7/en/innodb-auto-increment-handling.html

innodb_autoinc_lock_mode = 2 In this lock mode, auto-increment values are guaranteed to be unique and monotonically increasing across all concurrently executing “INSERT-like” statements.

Tested

SilverRAT
  • 37
  • 7
  • Thank you for the insight! Does this work the same for MyISAM tables, too (the fact that scenario 3 and 4 don't appear)? And what's the purpose of the `innodb_autoinc_lock_mode = 2` flag then? – tonix Aug 05 '17 at 23:03
  • 1
    Tested my script for myisam and scenario scripts 3 and 4 haven't appeared. This is because inserts, even if they have multiple values, are one transaction and are not split in to single inserts. about Innodb_autoinc_lock_mode = 2 from documentation `In this lock mode, no “INSERT-like” statements use the table-level AUTO-INC lock, and multiple statements can execute at the same time. This is the fastest and most scalable lock mode, but it is not safe when using statement-based replication or recovery scenarios when SQL statements are replayed from the binary log` – SilverRAT Aug 07 '17 at 07:07
  • I am sorry, but I didn't understand this lock mode from the explanation taken from documentation, could you please explain further and make an example? Thanks! – tonix Aug 07 '17 at 14:31
  • Normally, only one insert can be executed on a table. Another can only be executed when the previous one is finished. START INSERT1, END INSERT1 , START INSERT2, END INSERT2, START INSERT3, END INSERT3 – SilverRAT Aug 09 '17 at 07:11
  • 1
    If you use innodb_autoinc_lock_mode = 2, the database does not wait and the previous INSERT is finished, it executes at the same time. START INSERT1, START INSERT2, START INSERT3, END INSERT2, END INSERT3, END INSERT1 This works much faster but it can be done if your binary log uses a statement because inserts can be made at different speeds. Primary Key on slave can accept different values. – SilverRAT Aug 09 '17 at 07:11
  • Thanks for the answer! – tonix Aug 13 '17 at 09:28
  • 2
    This answer is incorrect. In the interleaved mode (innodb_autoinc_lock_mode = 2). The linked MySQL documentation explicitly writes after the quoted paragraph: *" However, because multiple statements can be generating numbers at the same time (that is, allocation of numbers is interleaved across statements), the **values generated for the rows inserted by any given statement may not be consecutive**."* I have described all these in my answer months ago. – Shadow Aug 14 '17 at 10:41
  • Well, then I should mark yours as the right one, but thanks to @SilverRAT anyway! – tonix Aug 17 '17 at 08:49