Bug #21726 Incorrect result with multiple invocations of LAST_INSERT_ID

Submitted: 18 Aug 2006 19:57 Modified: 13 Nov 2006 18:28
Reporter: Dean Ellis Email Updates:
Status: Closed Impact on me:
None 
Category:MySQL Server: Stored Routines Severity:S1 (Critical)
Version:5.0.25-bk,4.1 OS:
Assigned to: Tomash Brechko CPU Architecture:Any

[18 Aug 2006 19:57] Dean Ellis
Description:
Multiple invocations of LAST_INSERT_ID() always return the initial result of the function within a stored procedure.

How to repeat:
CREATE TABLE t1 ( a INT AUTO_INCREMENT PRIMARY KEY );
CREATE TABLE t2 ( a INT );
DELIMITER //
CREATE PROCEDURE p1 ()
BEGIN
 INSERT INTO t1 VALUES (NULL);
 INSERT INTO t2 VALUES (LAST_INSERT_ID());
 INSERT INTO t1 VALUES (NULL);
 INSERT INTO t2 VALUES (LAST_INSERT_ID());
 INSERT INTO t1 VALUES (NULL);
 INSERT INTO t2 VALUES (LAST_INSERT_ID());
END//
DELIMITER ;
CALL p1();
SELECT * FROM t1;
SELECT * FROM t2;

Currently produces output of:

mysql> SELECT * FROM t1;
+---+
| a |
+---+
| 1 | 
| 2 | 
| 3 | 
+---+
3 rows in set (0.00 sec)

mysql> SELECT * FROM t2;
+------+
| a    |
+------+
|    1 | 
|    1 | 
|    1 | 
+------+
3 rows in set (0.00 sec)

Suggested fix:
n/a
[31 Aug 2006 5:32] Valeriy Kravchuk
Bug #21808 was marked as a duplicate of this one. 5.0.24, Windows.
[31 Aug 2006 8:01] Guilhem Bichot
It will be interesting to check if this bug also exists in 5.1: I changed some things in LAST_INSERT_ID() in 5.1 in July.
[5 Sep 2006 19:48] Vance Rodriguez
This bug may have/be a duplicate at Bug #21616.
[14 Sep 2006 19:44] Sveta Smirnova
There is duplicate bug #22327
[22 Sep 2006 12:34] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/commits/12394

ChangeSet@1.2264, 2006-09-22 16:33:20+04:00, kroki@moonlight.intranet +10 -0
  BUG#21726: Incorrect result with multiple invocations of LAST_INSERT_ID
  
  Non-upper-level INSERTs (the ones in the body of stored procedure,
  stored function, or trigger) into a table that have AUTO_INCREMENT
  column didn't affected the result of LAST_INSERT_ID() on this level.
  
  The problem was introduced with the fix of bug 6880, where current
  insert_id value that was remembered on the first call to LAST_INSERT_ID()
  was returned from that function until it was reset before the next
  _upper-level_ statement.  The very procedure of remembering current
  insert_id had another defects that made the fix for bug 6880 only a
  partial solution.
  
  The fix for bug#21726 employs the following protocol:
  
   - remember insert_id value after the statement execution (which is at
     that point equals to the first insert_id value generated by the
     statement or the result of its explicit setting).
  
   - on statements "LAST_INSERT_ID(expr)", "SET LAST_INSERT_ID= expr", and
     on binary log command "LAST_INSERT_ID= constant" update remembered
     insert_id value.
  
  Thus, the value returned by LAST_INSERT_ID() is not affected by values
  generated by current statement, but affected by its explicit update.
  
  This fix is for 5.0 only, as in 5.1 WL 3146 solves the same problems in
  a more generic way.  This patch tries to produce the same results as 5.1.
[27 Sep 2006 16:14] Konstantin Osipov
I'm the second reviewer here, after Rafal. Setting a ETA.
[28 Sep 2006 19:05] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/commits/12762

ChangeSet@1.2289, 2006-09-28 23:04:11+04:00, kroki@moonlight.intranet +13 -0
  BUG#21726: Incorrect result with multiple invocations of LAST_INSERT_ID
  
  Non-upper-level INSERTs (the ones in the body of stored procedure,
  stored function, or trigger) into a table that have AUTO_INCREMENT
  column didn't affected the result of LAST_INSERT_ID() on this level.
  
  The problem was introduced with the fix of bug 6880, which in turn was
  introduced with the fix of bug 3117, where current insert_id value was
  remembered on the first call to LAST_INSERT_ID() (bug 3117) and was
  returned from that function until it was reset before the next
  _upper-level_ statement (bug 6880).
  
  The fix for bug#21726 brings back the behaviour of version 4.0, and
  implements the following: remember insert_id value in
  Item_func_last_insert_id::fix_fields() (which at that point equals to
  the first insert_id value generated by the previous statement), and
  return that remembered value from THD::insert_id().
  
  Thus, the value returned by LAST_INSERT_ID() is not affected by values
  generated by current statement, nor by LAST_INSERT_ID(expr) calls in
  this statement.
  
  Version 5.1 does not have this bug (it was fixed by WL 3146).
[2 Oct 2006 10:29] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/commits/12930

ChangeSet@1.2293, 2006-10-02 14:28:23+04:00, kroki@moonlight.intranet +14 -0
  BUG#21726: Incorrect result with multiple invocations of LAST_INSERT_ID
  
  Non-upper-level INSERTs (the ones in the body of stored procedure,
  stored function, or trigger) into a table that have AUTO_INCREMENT
  column didn't affected the result of LAST_INSERT_ID() on this level.
  
  The problem was introduced with the fix of bug 6880, which in turn was
  introduced with the fix of bug 3117, where current insert_id value was
  remembered on the first call to LAST_INSERT_ID() (bug 3117) and was
  returned from that function until it was reset before the next
  _upper-level_ statement (bug 6880).
  
  The fix for bug#21726 brings back the behaviour of version 4.0, and
  implements the following: remember insert_id value at the beginning
  of the statement or expression (which at that point equals to
  the first insert_id value generated by the previous statement), and
  return that remembered value from LAST_INSERT_ID() or @@LAST_INSERT_ID.
  
  Thus, the value returned by LAST_INSERT_ID() is not affected by values
  generated by current statement, nor by LAST_INSERT_ID(expr) calls in
  this statement.
  
  Version 5.1 does not have this bug (it was fixed by WL 3146).
[2 Oct 2006 13:45] Tomash Brechko
Pushed to 5.0.
[2 Oct 2006 13:50] Tomash Brechko
Pushed to 5.0.26, that is.
[2 Oct 2006 15:04] Konstantin Osipov
For the record: the patch was approved over email, phone, irc. Me, Rafal and Guilhem all looked at the final patch.
[3 Oct 2006 9:39] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/commits/12990

ChangeSet@1.2294, 2006-10-03 13:38:16+04:00, kroki@moonlight.intranet +9 -0
  Fix for the patch for bug#21726: Incorrect result with multiple
  invocations of LAST_INSERT_ID.
  
  Reding of LAST_INSERT_ID inside stored function wasn't noted by caller,
  and no LAST_INSERT_ID_EVENT was issued for binary log.
  
  The solution is to add THD::last_insert_id_used_bin_log, which is much
  like THD::last_insert_id_used, but is reset only for upper-level
  statements.  This new variable is used to issue LAST_INSERT_ID_EVENT.
[3 Oct 2006 20:12] Russ Brown
Is there a workaround that I can use to get around this problem while I wait for an official release which contains the fix?

The only one I can think of is to wrap the inserts into individual functions, but that seems really messy and over the top...

Thanks.
[4 Oct 2006 19:12] Paul DuBois
Noted in 5.0.26.

The value of LAST_INSERT_ID() was not always updated correctly within
stored routines.
[5 Oct 2006 9:54] Tomash Brechko
Setting back to 'In progress' for 4.1.
[6 Oct 2006 9:35] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/commits/13222

ChangeSet@1.2554, 2006-10-06 13:34:07+04:00, kroki@moonlight.intranet +13 -0
  BUG#21726: Incorrect result with multiple invocations of LAST_INSERT_ID.
  
  Note: bug#21726 does not directly apply to 4.1, as it doesn't have stored
  procedures.  However, 4.1 had some bugs that were fixed in 5.0 by the
  patch for bug#21726, and this patch is a backport of those fixes.
  Namely, in 4.1 it fixes:
  
    - LAST_INSERT_ID(expr) didn't return value of expr (4.1 specific).
  
    - LAST_INSERT_ID() could return the value generated by current
      statement if the call happens after the generation, like in
  
        CREATE TABLE t1 (i INT AUTO_INCREMENT PRIMARY KEY, j INT);
        INSERT INTO t1 VALUES (NULL, 0), (NULL, LAST_INSERT_ID());
  
    - Redundant binary log LAST_INSERT_ID_EVENTs could be generated.
[11 Oct 2006 12:20] Paul van Rossem
Bug still present in 5.0.26.
[11 Oct 2006 12:28] Tomash Brechko
Paul, could you please provide an example the reveals this bug in 5.0.26?
[12 Oct 2006 15:46] Peter Andrews
This bug is stil present in 5.1.11 beta windows.

Here is a sample that returns increasing values for last_insert_id() despite the table remaining with only one row:

DROP TABLE IF EXISTS `d`;

CREATE TABLE `d` (
 `k` int(11) NOT NULL auto_increment,
 `a` int(11) default NULL,
 `c` int(11) default NULL,
 PRIMARY KEY  (`k`),
 UNIQUE KEY `idx_1` (`a`)
) ENGINE=InnoDB;
insert into d values(null,4,2);
insert into d values(null,5,2),(null,4,2);
select last_insert_id();
select * from d;

insert into d values(null,5,2),(null,4,2);
select last_insert_id();
select * from d;

insert into d values(null,5,2),(null,4,2);
select last_insert_id();
select * from d;

insert into d values(null,5,2),(null,4,2);
select last_insert_id();
select * from d;

insert into d values(null,5,2),(null,4,2);
select last_insert_id();
select * from d;
[13 Oct 2006 7:22] Paul van Rossem
Example where this still fails in 5.0.26:

CREATE TABLE T3
  (T3ID INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY
);

CREATE TABLE T1
  (T1ID INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY, 
   T3Ref INTEGER,
   FOREIGN KEY (T3Ref) REFERENCES T3(T3ID) ON DELETE RESTRICT ON UPDATE CASCADE
);

CREATE TABLE T2
  (T2ID INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
   T1Ref INTEGER NOT NULL,
   FOREIGN KEY (T1Ref) REFERENCES T1(T1ID) ON DELETE CASCADE ON UPDATE CASCADE
);

INSERT INTO T3(T3ID) VALUES (123);
INSERT INTO T1(T1ID, T3Ref) VALUES (33, 123);
INSERT INTO T2(T2ID, T1Ref) VALUES (222, 33);

CREATE PROCEDURE T1_COPY (srcT1 INTEGER)
BEGIN
  DECLARE dstT1 INTEGER
  DECLARE srcT3 INTEGER; DECLARE dstT3 INTEGER;
  DECLARE srcT2 INTEGER; DECLARE dstT2 INTEGER;
  DECLARE done SMALLINT DEFAULT 0;
  DECLARE t2Cursor CURSOR FOR SELECT T2ID FROM T2 WHERE T1Ref = srcT1;
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
  SELECT T3Ref INTO srcT3 FROM T1 WHERE T1ID = srcT1;
  CALL T3_COPY(srcT3, dstT3);
  INSERT INTO T1(T3Ref) VALUES (dstT3);
  SET dstT1 = LAST_INSERT_ID();  /* this sets the value of the last T3 insert,              
                                     not of the T1 insert! */
  OPEN t2Cursor;
  REPEAT
    FETCH t2Cursor INTO srcT2;
    IF NOT done THEN 
       CALL T2_COPY(srcT2, dstT1, dstT2); /* fails because of invalid dstT1 */
    END IF;
  UNTIL done END REPEAT;
END $

CREATE PROCEDURE T2_COPY (srcT2 INTEGER, dstT1 INTEGER, OUT newT2 INTEGER)
BEGIN
  DECLARE srcT1 INTEGER;
  SELECT T1Ref INTO srcT1 FROM T2 WHERE T2ID = srcT2;
  INSERT INTO T2(T1Ref) VALUES (dstT1);
  SET newT2 = LAST_INSERT_ID();
END $

CREATE PROCEDURE T3_COPY (srcT3 INTEGER, OUT newT3 INTEGER) 
BEGIN
  INSERT INTO T3() VALUES ();
  SET newT3 = LAST_INSERT_ID();
END $

CALL T1_COPY(33, result);  /* fails for resons mentioned above */
[13 Oct 2006 7:59] Tomash Brechko
Paul, after running your test case I get the following correct results:

SELECT * FROM T1;
#+------+-------+
#| T1ID | T3Ref |
#+------+-------+
#|   33 |   123 |
#|   34 |   124 | <= '34' was generated by INSERT INTO T1(T3Ref) VALUES (dstT3);
#+------+-------+    and it was assigned to dstT1.

SELECT * FROM T2;
#+------+-------+
#| T2ID | T1Ref |
#+------+-------+
#|  222 |    33 |
#|  223 |    34 | <= '34' was inserted by INSERT INTO T2(T1Ref) VALUES (dstT1);
#+------+-------+    which is correct.

SELECT * FROM T3;
#+------+
#| T3ID |
#+------+
#|  123 |
#|  124 |
#+------+

If you results differ from the above, please paste them, and also the output of "SELECT VERSION();".  If you have the same results, then I guess everything is correct.
[13 Oct 2006 9:08] Tomash Brechko
Peter, I have opened a new bug#23249 based on your input, thanks.
[13 Oct 2006 11:24] Paul van Rossem
Tomash,
I re-downloaded and re-installed windows server 5.0.26, and now I get the correct results. So the problem must have been with my server installation. Owe you an apology. The problem appears solved with 5.0.26.
Regards, Paul.
[13 Oct 2006 11:32] Tomash Brechko
Paul, that's great.  Thanks for letting us know.
[16 Oct 2006 16:47] Konstantin Osipov
Approved the version for 4.1
[17 Oct 2006 10:01] Tomash Brechko
4.1 version queued to 4.1-runtime and null-merged to 5.0-runtime and 5.1-runtime.
[3 Nov 2006 10:54] Tomash Brechko
Bug#23932 relates to this bug.
[11 Nov 2006 3:27] Paul DuBois
Noted in 4.1.22 changelog.

The value of LAST_INSERT_ID() was not always updated correctly within
stored routines.
[13 Nov 2006 18:28] Paul DuBois
Corrected the 4.1.21 changelog entry:

Redundant binary log LAST_INSERT_ID events could be generated;
LAST_INSERT_ID(expr) didn't return value of expr; LAST_INSERT_ID()
could return the value generated by the current statement if the call
happens after value generation, as in:

CREATE TABLE t1 (i INT AUTO_INCREMENT PRIMARY KEY, j INT);
INSERT INTO t1 VALUES (NULL, 0), (NULL, LAST_INSERT_ID());

Portions of this website are copyright © 2001, 2002 The PHP Group

Page generated in 0.066 sec. using MySQL 8.4.7-u6-cloud

Timestamp references displayed by the system are UTC.
Content reproduced on this site is the property of the respective copyright holders.
It is not reviewed in advance by Oracle and does not necessarily represent the opinion of Oracle or any other party.