If i have insert query for example:
INSERT INTO user(username) VALUES('admin');
And then get the id of the inserted record with
LAST_INSERT_ID();
Looks find but what happens if between the insert and LAST_INSERT_ID() another insert is executed.
How MySQL knows to return the correct id (for the first or second insert) since no parameter is passed to LAST_INSERT_ID();
Is it save to use this function?
Thanks
I'm supposing that you mean what happen if i'm connected to the MySQL server and executing an INSERT but others are also doing insert, like updating a table on a website while client are currently using it.
If you go take a look at the documentation https://dev.mysql.com/doc/refman/8.0/en/information-functions.html there is a point that answers your questions:
The ID that was generated is maintained in the server on a
per-connection basis. This means that the value returned by the
function to a given client is the first AUTO_INCREMENT value generated
for most recent statement affecting an AUTO_INCREMENT column by that
client. This value cannot be affected by other clients, even if they
generate AUTO_INCREMENT values of their own. This behavior ensures
that each client can retrieve its own ID without concern for the
activity of other clients, and without the need for locks or
transactions.
This should be the same in MariaDB.
As discussed in the comment, you are wondering if you can use this in a php PDO environment. If you mean to use this directly from the database, it's a no, you won't be able to have the last inserted ID because you won't have the same client connection as PDO. If you want to use it directly from PDO please use the specific PDO function: http://php.net/manual/fr/pdo.lastinsertid.php , this should allow to do what you want.
If you insert multiple rows into a table using a single INSERT query, the LAST_INSERT_ID function returns the last insert id of the first row.
i.e
If your table id has 3 as column value and you will insert 4 rows in a single query then LAST_INSERT_ID will give you 4 instead of 7
If you insert 4 rows in 4 different insert query then LAST_INSERT_ID will give you 7
last_insert_id( ) Or mysqli_insert_id( ) will always return the id of last or most recent query. And also be noted that id field must have AUTO_INCREMENT enabled.
It doesn't give you the freedom to choose any specific table. Or you can't have id which is generated from your previous query.
So, from this point it serves a very small purpose. Only the last id, it doesn't matter which table.
To get last id from any specific table this query would be helpful : "SELECT id FROM table_name ORDER BY id DESC LIMIT 1"
I want to insert multiple rows into a table, using a single INSERT statement. This is no problem, since SQL offers the option to provide multiple rows as parameter for a single INSERT statement. Now, those rows contain an ID field that is incremented automatically, i.e. its value is set by the database, not by my code.
As a result, I would like to get the ID values of the inserted rows. My basic question is: How do I do that for MariaDB / MySQL?
As it turns out, this is pretty simple, e.g. in PostgreSQL, as PostgreSQL has the RETURNING clause for INSERT which returns the desired values for one or even for multiple rows. This is exactly what I want and it works.
Unfortunately, neither MariaDB nor MySQL have PostgreSQL's RETURNING clause, so I need to fallback to something such as LAST_INSERT_ID(), but this only returns the ID of the single last inserted row, even if multiple rows were inserted using a single INSERT. How do I get all the ID values?
My code currently looks like this:
INSERT INTO mytable
(foo, bar)
VALUES
('fooA', 'barA'),
('fooB', 'barB');
SELECT LAST_INSERT_ID() AS id;
How can I solve this issue in a way that works even with concurrent writes?
(And no, it's not an option to change to a UUID field, or something like this; the auto-increment field is given, and can not be changed.)
MySQL & MariaDB have the LAST_INSERT_ID() function, and it returns the id generated by the most recent INSERT statement in your current session.
But when your INSERT statement inserts multiple rows, LAST_INSERT_ID() returns the first id in the set generated.
In such a batch of multiple rows, you can rely on the subsequent id's being consecutive. The MySQL JDBC driver depends on this, for example.
If the rows you insert include a mix of NULL and non-NULL values for the id column, you have a risk of messing up this assumption. The JDBC driver returns the wrong values for the set of generated id's.
As stated in the comments, you can capture the inserted IDs (SQL Server):
use tempdb
create table test (
id int identity(1,1) primary key,
t varchar(10) null
)
create table ids (
i int not null
)
insert test(t)
output inserted.id into ids
values (null), (null), (null)
select *
from test
select *
from ids
My INSERT... SELECT query inserts 2,546 records into a table which brings my ID range for all inserted records from 176,439 to 178,984. All records are successfully inserted (178984 - 176439 + 1 = 2,546). Now if launch my application and add a single new record, my next ID appears as 180534. I don't understand mysql didn't set it to 178985. Similar behavior happens if I repeat the process.
My innodb_autoinc_lock_mode is set to 1, the default, which stands for "consecutive".
Any thoughts?
The number of records does not have to match the auto-increment id. Records may have been deleted. The auto-increment was set manually or for other reasons. You can set the next id with this command
ALTER TABLE tbl AUTO_INCREMENT = 178985;
After that the IDs should be generated above that number.
Yes, you ran some other insert query, rows were rejected probably due to unique constraints but auto-increment still incremented, or they were deleted after that. Alternatively if someone else (or you in another session) started a transaction, inserted some rows but didn't commit yet... the auto_increment value would also be larger.
Just wanted to runt this by someone who knows more MySQL than myself
I perform a MySQL insert where I insert say 10 rows in one query. Then when you get MySQL's last insert id, it gives you the ID of the first inserted ID.
Is it safe to assume that the other IDs are consecutively the insert_id - insert_id+9? or is there any possible way this could not turn out to be the case?
Thanks
Yes, there is a possibility this will not always be the case.
With innodb_autoinc_lock_mode = 2, the rows inserted by a single INSERT statement can be assigned AUTO_INCREMENT values that are not consecutive (when concurrent INSERT statements are running.)
http://dev.mysql.com/doc/refman/5.1/en/innodb-auto-increment-handling.html
I'm using MySQL's AUTO_INCREMENT field and InnoDB to support transactions. I noticed when I rollback the transaction, the AUTO_INCREMENT field is not rollbacked? I found out that it was designed this way but are there any workarounds to this?
It can't work that way. Consider:
program one, you open a transaction and insert into a table FOO which has an autoinc primary key (arbitrarily, we say it gets 557 for its key value).
Program two starts, it opens a transaction and inserts into table FOO getting 558.
Program two inserts into table BAR which has a column which is a foreign key to FOO. So now the 558 is located in both FOO and BAR.
Program two now commits.
Program three starts and generates a report from table FOO. The 558 record is printed.
After that, program one rolls back.
How does the database reclaim the 557 value? Does it go into FOO and decrement all the other primary keys greater than 557? How does it fix BAR? How does it erase the 558 printed on the report program three output?
Oracle's sequence numbers are also independent of transactions for the same reason.
If you can solve this problem in constant time, I'm sure you can make a lot of money in the database field.
Now, if you have a requirement that your auto increment field never have gaps (for auditing purposes, say). Then you cannot rollback your transactions. Instead you need to have a status flag on your records. On first insert, the record's status is "Incomplete" then you start the transaction, do your work and update the status to "compete" (or whatever you need). Then when you commit, the record is live. If the transaction rollsback, the incomplete record is still there for auditing. This will cause you many other headaches but is one way to deal with audit trails.
Let me point out something very important:
You should never depend on the numeric features of autogenerated keys.
That is, other than comparing them for equality (=) or unequality (<>), you should not do anything else. No relational operators (<, >), no sorting by indexes, etc. If you need to sort by "date added", have a "date added" column.
Treat them as apples and oranges: Does it make sense to ask if an apple is the same as an orange? Yes. Does it make sense to ask if an apple is larger than an orange? No. (Actually, it does, but you get my point.)
If you stick to this rule, gaps in the continuity of autogenerated indexes will not cause problems.
I had a client needed the ID to rollback on a table of invoices, where the order must be consecutive
My solution in MySQL was to remove the AUTO-INCREMENT and pull the latest Id from the table, add one (+1) and then insert it manually.
If the table is named "TableA" and the Auto-increment column is "Id"
INSERT INTO TableA (Id, Col2, Col3, Col4, ...)
VALUES (
(SELECT Id FROM TableA t ORDER BY t.Id DESC LIMIT 1)+1,
Col2_Val, Col3_Val, Col4_Val, ...)
Why do you care if it is rolled back? AUTO_INCREMENT key fields are not supposed to have any meaning so you really shouldn't care what value is used.
If you have information you're trying to preserve, perhaps another non-key column is needed.
I do not know of any way to do that. According to the MySQL Documentation, this is expected behavior and will happen with all innodb_autoinc_lock_mode lock modes. The specific text is:
In all lock modes (0, 1, and 2), if a
transaction that generated
auto-increment values rolls back,
those auto-increment values are
“lost.” Once a value is generated for
an auto-increment column, it cannot be
rolled back, whether or not the
“INSERT-like” statement is completed,
and whether or not the containing
transaction is rolled back. Such lost
values are not reused. Thus, there may
be gaps in the values stored in an
AUTO_INCREMENT column of a table.
If you set auto_increment to 1 after a rollback or deletion, on the next insert, MySQL will see that 1 is already used and will instead get the MAX() value and add 1 to it.
This will ensure that if the row with the last value is deleted (or the insert is rolled back), it will be reused.
To set the auto_increment to 1, do something like this:
ALTER TABLE tbl auto_increment = 1
This is not as efficient as simply continuing on with the next number because MAX() can be expensive, but if you delete/rollback infrequently and are obsessed with reusing the highest value, then this is a realistic approach.
Be aware that this does not prevent gaps from records deleted in the middle or if another insert should occur prior to you setting auto_increment back to 1.
INSERT INTO prueba(id)
VALUES (
(SELECT IFNULL( MAX( id ) , 0 )+1 FROM prueba target))
If the table doesn't contain values or zero rows
add target for error mysql type update FROM on SELECT
If you need to have the ids assigned in numerical order with no gaps, then you can't use an autoincrement column. You'll need to define a standard integer column and use a stored procedure that calculates the next number in the insert sequence and inserts the record within a transaction. If the insert fails, then the next time the procedure is called it will recalculate the next id.
Having said that, it is a bad idea to rely on ids being in some particular order with no gaps. If you need to preserve ordering, you should probably timestamp the row on insert (and potentially on update).
Concrete answer to this specific dilemma (which I also had) is the following:
1) Create a table that holds different counters for different documents (invoices, receipts, RMA's, etc..); Insert a record for each of your documents and add the initial counter to 0.
2) Before creating a new document, do the following (for invoices, for example):
UPDATE document_counters SET counter = LAST_INSERT_ID(counter + 1) where type = 'invoice'
3) Get the last value that you just updated to, like so:
SELECT LAST_INSERT_ID()
or just use your PHP (or whatever) mysql_insert_id() function to get the same thing
4) Insert your new record along with the primary ID that you just got back from the DB. This will override the current auto increment index, and make sure you have no ID gaps between you records.
This whole thing needs to be wrapped inside a transaction, of course. The beauty of this method is that, when you rollback a transaction, your UPDATE statement from Step 2 will be rolled back, and the counter will not change anymore. Other concurrent transactions will block until the first transaction is either committed or rolled back so they will not have access to either the old counter OR a new one, until all other transactions are finished first.
SOLUTION:
Let's use 'tbl_test' as an example table, and suppose the field 'Id' has AUTO_INCREMENT attribute
CREATE TABLE tbl_test (
Id int NOT NULL AUTO_INCREMENT ,
Name varchar(255) NULL ,
PRIMARY KEY (`Id`)
)
;
Let's suppose that table has houndred or thousand rows already inserted and you don't want to use AUTO_INCREMENT anymore; because when you rollback a transaction the field 'Id' is always adding +1 to AUTO_INCREMENT value.
So to avoid that you might make this:
Let's remove AUTO_INCREMENT value from column 'Id' (this won't delete your inserted rows):
ALTER TABLE tbl_test MODIFY COLUMN Id int(11) NOT NULL FIRST;
Finally, we create a BEFORE INSERT Trigger to generate an 'Id' value automatically. But using this way won't affect your Id value even if you rollback any transaction.
CREATE TRIGGER trg_tbl_test_1
BEFORE INSERT ON tbl_test
FOR EACH ROW
BEGIN
SET NEW.Id= COALESCE((SELECT MAX(Id) FROM tbl_test),0) + 1;
END;
That's it! You're done!
You're welcome.
$masterConn = mysql_connect("localhost", "root", '');
mysql_select_db("sample", $masterConn);
for($i=1; $i<=10; $i++) {
mysql_query("START TRANSACTION",$masterConn);
$qry_insert = "INSERT INTO `customer` (id, `a`, `b`) VALUES (NULL, '$i', 'a')";
mysql_query($qry_insert,$masterConn);
if($i%2==1) mysql_query("COMMIT",$masterConn);
else mysql_query("ROLLBACK",$masterConn);
mysql_query("ALTER TABLE customer auto_increment = 1",$masterConn);
}
echo "Done";