PDO + PHP lastInsertId() issue - php

Wouldn't there be a problem with it if for example when a user clicks on a link, a new row is automatically inserted and then the php code requests the last inserted id, and at the same time another row is inserted by another user, so the returned id is actually not the one I'm expecting..?
Am I wrong? Is there a way to do the same without that 'security' hole?
(like maybe from within the prepared statement or something...)
P.S the id is automatically generated.
Thank you.

As mentioned in the manual:
LAST_INSERT_ID() (with no argument) returns a BIGINT (64-bit) value representing the first automatically generated value that was set for an AUTO_INCREMENT column by the most recently executed INSERT statement to affect such a column. For example, after inserting a row that generates an AUTO_INCREMENT value, you can get the value like this:
mysql>SELECT LAST_INSERT_ID();
->195
The currently executing statement does not affect the value of
LAST_INSERT_ID(). Suppose that you generate an AUTO_INCREMENT value
with one statement, and then refer to LAST_INSERT_ID() in a
multiple-row INSERT statement that inserts rows into a table with its
own AUTO_INCREMENT column. The value of LAST_INSERT_ID() will remain
stable in the second statement; its value for the second and later
rows is not affected by the earlier row insertions. (However, if you
mix references to LAST_INSERT_ID() and LAST_INSERT_ID(expr), the
effect is undefined.)
If the previous statement returned an error, the value of
LAST_INSERT_ID() is undefined. For transactional tables, if the
statement is rolled back due to an error, the value of
LAST_INSERT_ID() is left undefined. For manual ROLLBACK, the value of
LAST_INSERT_ID() is not restored to that before the transaction; it
remains as it was at the point of the ROLLBACK.
So, LAST_INSERT_ID() is always transaction-safe (even though you don't use transaction).

The MySQL Server transfers the insert ID as part of the OK message after a successful INSERT. This ID is stored in PDO, therefore without a round-trip to the server PDO can return you the correct ID for your connection in a safe way.
Reference: http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#OK_Packet

To counteract this you would use a transaction.
This would essentially isolate your insert from others, so as long as your Insert/lastInsertId() call is within the same transaction, it will work just fine.

Related

Nesting LAST_INSERT_ID() in a PDO prepared statement?

Using PHP with MySQL and PDO prepared statements, I would like to mimic an opaque id in a simple, safe and efficient way. The idea is to add a random value to the current value of LAST_INSERT_ID.
INSERT INTO table
SET id = LAST_INSERT_ID( LAST_INSERT_ID() + FLOOR(1 + (RAND() * 99)) ),
text = ?,
...
The outer expression sets the id, the inner call gets the current value and modifies it. This works as expected when I test it with the Sequel Pro client. However, in the real script with a prepared statement the last insert id does not get updated/saved. So, I do get random ids, but they are not added up.
What am I missing?
This really does not work, and here's why:
LAST_INSERT_ID() returns 0 on the first call per connection because there is no last insert. Unfortunately, it does not just return the potential next AUTO_INCREMENT value.
If you would split it up into separate commands, first INSERT and then UPDATE within the same statement, LAST_INSERT_ID() would not get updated in between.
More over, "if you mix references to LAST_INSERT_ID() and LAST_INSERT_ID(expr), the effect is undefined" Manual vs. Manual
UPDATEd ids will be ignored by LAST_INSERT_ID()

Do BEFORE UPDATE triggers affect mysqli_insert_id results?

I have a mysql table which has a trigger attached, that logs changes in this table to a second one
CREATE TRIGGER log_table BEFORE UPDATE ON table1
FOR EACH ROW BEGIN
INSERT INTO log_table(filed) VALUES(NEW.field);
END;
//
Now if I perform an INSERT INTO table1 from PHP an call mysqli_insert_id() afterwards.
Would that return the new ID in table1? Or the new ID in log_table?
LAST_INSERT_ID() called after the INSERT statement will return the new ID in table1 (of the INSERT statement), not the one in the log_table (of the trigger).
This is documented in the manual section of LAST_INSERT_ID:
Within the body of a stored routine (procedure or function) or a
trigger, the value of LAST_INSERT_ID() changes the same way as for
statements executed outside the body of these kinds of objects. The
effect of a stored routine or trigger upon the value of
LAST_INSERT_ID() that is seen by following statements depends on the
kind of routine:
If a stored procedure executes statements that change the value of
LAST_INSERT_ID(), the changed value is seen by statements that follow
the procedure call.
For stored functions and triggers that change the value, the value is
restored when the function or trigger ends, so following statements
will not see a changed value.

Reliable or not PDO lastInsertId() when using transactions

I use PDO transaction
try {
DB::$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
DB::$db->beginTransaction();
$db->prepare( insert query );
$db->execute();
$last_insert_id = $db->lastInsertId();
...
...
Multiple concurrent requests are expected on this script.
Question: is it possible that lastInsertId() return incorrect value for the user, who actually inserted the row?
(by "incorrect value" i mean: id that is inserted by some other user).
You're safe. The ID you get will be the correct one.
PDO's lastInsertId (and mysql's last_insert_id to which your PDO delegates the call in this case) gives the last autogenerated ID on a per-connection basis.
From mysql's documentation:
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.
Concurrent connections will not compromise the integrity of the returned id. And as you mention in your comment, transactions have no bearing on this. Just don't forget to commit!
I'll also mention there is the unlikely possibility, if you run multiple statements on the same connection, and if your execute method throws an exception which isnt handled correctly, that lastInsertId could return the id of the last successful insert on that connection. But it can never return an ID from another user's query.

What happens if mysql_insert_id called form different places and different browsers at the same time?

Maybe i have some stupid questions about mysql_insert_id...
I need to get the last inserted Id of a query using mysql_insert_id after mysql_query("INSERT INTO").
mysql_insert_id retrieves the generated Id by the previous query, but my question is...
One php file called test.php with the following code
mysql_query("INSERT INTO table (FirstName, LastName) VALUES ('" . $_POST['FirstName'] . "', '" . $_POST['LastName'] . "'); ")
$lastId = mysql_insert_id();
...continues some code using $lastId
What happens if the test.php file called from different places and different browsers at the SAME TIME ?
or what happens if other php file that containts INSERT INTO query called at the same time ?
What actually Id gets back the mysql_insert_id() ?
We have to do with a rare probability ?
I do not know if I become understandable...
The PHP manual states that the value of mysql_insert_id() returns only the most recently generated AUTO_INCREMENT value:
The value of the MySQL SQL function LAST_INSERT_ID() always contains the most recently generated AUTO_INCREMENT value, and is not reset between queries.
The MySQL manual states the it will return the last AUTO_INCREMENT based on a per-connection basis:
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.
Additionally, you should stop using mysql_ functions as they are being deprecated.
Each client will be sitting on a separate connection. mysql_insert_id() will get the id of the last insert query, based on the passed/current connection. So you don't have to worry about multiple scripts causing problems.
Also, from the code you provided above, your script is vulnerable to a SQL injection attack. Escape your user-input or even better, use PDO/MySQLi.
You get back the autoincrement ID from the last query on your connection. It will not cross connections, so there is no harm in two scripts running simultaneously.
Side note: if you have the option, use the mysqli or PDO libraries instead of the deprecated mysql libarary.

php function last_insert_id() is not working with REPLACE INTO query

I am using REPLACE INTO query to insert in to table but after executing query by using mysql_query() function and if I use last_insert_id() it is only giving me 0 value.
why this is happening so? and how can I overcome this behavior.
:Pankaj
You could try using INSERT INTO ... ON DUPLICATE KEY UPDATE instead. It accomplishes the same thing as REPLACE INTO, but in a single server-side operation. REPLACE INTO can end up causing two operations: delete the old one, insert the new one.
But regardless of the query type, you do have to ensure that the table's primary key is an auto_increment field. last_insert_id() does not work properly otherwise.
REPLACE INTO doesn't seem to affect the vaue that can be obtained via last_insert_id().
It seems to be the expected behavior, judging from this :
LAST_INSERT_ID() give wrong value after REPLACE INTO query
LAST_INSERT_ID()

Categories