Scott,

The bottom line is that LAST_INSERT_ID() isn't guaranteed to be unchanged by
an INSERT IGNORE that doesn't insert anything, so you have to do something
else.

You need to test that a row was, in fact, inserted by the first INSERT
IGNORE and, if not, do not execute the second INSERT.

If that's difficult, and you really want to use just SQL statements, you
could have the second insert test that there is a row in the first table
with id=LAST_INSERT_ID(), perhaps using INSERT ... SELECT.  I'd worry,
however, that you don't really have a guarantee of the value of
LAST_INSERT_ID().


Here's what's probably happening.  (I haven't looked at the internals, but
it's a close enough model.)  On the second time you do the first INSERT
IGNORE,
 - It reserves id 129 and remembers that as the last insert id
 - It does the INSERT
 - The insert fails, but the error is ignored
However, the last insert id remains 129.  (You have inserted zero records,
the first of which has a key of 129.)

All you are guaranteed is that
    (a) right after you insert something, and before you try to insert
anything else, LAST_INSERT_ID() returns the first id inserted by that INSERT
statement, if any.
    (b) for any thread, the id's increase
    (c) no two threads ever get the same id
However, LAST_INSERT_ID() is not quite transactional, even if you are using
transactional tables.  Once an id has been reserved, it's never given back,
even if the SQL statement is rolled back--so you can get gaps.  The reason
for this is so that two threads can independently insert records with an
auto_increment key without either one having to wait for the other.  This
can make a substantial difference to the concurrency of transactions.

HTH

Bill

--------- original message follows ----------

Date: Thu, 22 Jul 2004 22:56:12 -0700
Subject: Last insert id problem/bug
From: Scott Haneda <[EMAIL PROTECTED]>

I am not sure this is just how it is, my issue, or a bug, I searched the
bugs database, but don't really know what to call this in order to properly
look up the info:

MySQL 4.0.18-standard

CREATE TABLE `addresses` (
  `id` int(11) NOT NULL auto_increment,
  `user_id` int(11) NOT NULL default '0',
  `first_name` varchar(48) NOT NULL default '',
  `last_name` varchar(48) NOT NULL default '',
  `email_address` varchar(48) NOT NULL default '',
  `updated` timestamp(14) NOT NULL,
  `added` timestamp(14) NOT NULL default '00000000000000',
  PRIMARY KEY  (`id`),
  UNIQUE KEY `emailuid` (`email_address`,`user_id`)
) TYPE=InnoDB PACK_KEYS=0 COMMENT='foo';

CREATE TABLE addresses_incampaign (
  id int(11) NOT NULL auto_increment,
  user_id int(11) NOT NULL default '0',
  address_id int(11) NOT NULL default '0',
  campaign_id int(11) NOT NULL default '0',
  updated timestamp(14) NOT NULL,
  added timestamp(14) NOT NULL default '00000000000000',
  PRIMARY KEY  (id),
  UNIQUE KEY address_id (address_id)
) TYPE=InnoDB PACK_KEYS=0 COMMENT='bar';

As you can see, I have a unique key on addresses.email_address and
addresses.user_id, note the unique key on address_id in the second table.

I insert the following data TWICE:

INSERT IGNORE INTO `addresses`
(`user_id`,`first_name`,`last_name`,`email_address`) VALUES (1, 'Claire',
'Altman', '[EMAIL PROTECTED]');
INSERT IGNORE INTO `addresses_incampaign` (`user_id`, `address_id`,
`campaign_id`) VALUES (1,LAST_INSERT_ID(), 2);

Notice I am picking up the last_insert_id in the second insert, I then
insert that data again, since I am using insert ignore and since there are
unique keys on this, I should only still have one records in each table.

Results are as follows:
mysql> select * from addresses;
+-----+---------+------------+-----------+---------------+
| id  | user_id | first_name | last_name | email_address |
+-----+---------+------------+-----------+---------------+
| 148 |       1 | Claire     | Altman    | [EMAIL PROTECTED]     |
+-----+---------+------------+-----------+---------------+
1 row in set (0.00 sec)

mysql> select * from addresses_incampaign ;
+-----+---------+------------+-------------+
| id  | user_id | address_id | campaign_id |
+-----+---------+------------+-------------+
| 128 |       1 |        148 |           2 |
| 129 |       1 |        149 |           2 | <- SOULD NOT HAVE HAPPENED
+-----+---------+------------+-------------+
2 rows in set (0.00 sec)

Record 129 should not have been inserted, it should not have ever gotten a
insert ID back from mysql, mysql said back 149, which is in fact the next
record, but no new record was added to addresses so it really is not valid.

Workarounds and suggestions?
-- 
-------------------------------------------------------------
Scott Haneda                                Tel: 415.898.2602
http://www.newgeo.com                       Fax: 313.557.5052
[EMAIL PROTECTED]                            Novato, CA U.S.A.




-- 
MySQL General Mailing List
For list archives: http://lists.mysql.com/mysql
To unsubscribe:    http://lists.mysql.com/[EMAIL PROTECTED]

Reply via email to