0

I am intermittently seeing a problem when trying to select a row immediately after inserting it in PHP. This issue will only happen once in every 100-1000 occurances. This is using php 4.4, with no option to upgrade / be able to use PDO or mysqli. I was considering using transactions, but both queries execute and would continue to do so even with transactions, but the second query runs and returns empty RS so I doubt that transactions would hatch it.

As an example, take a table with two columns: an auto-increment counter, and a stored value. The value must be unique as well, but joins are made on the counter, not the value. Values can be entered by more than one user at a time, so I cannot simply trust the highest counter to give me the value I just inserted.

    data:
    data_id int(11) UNIQUE AUTO_INCREMENT NOT NULL
    myData varchar(50) UNIQUE NOT NULL

Now each user is able to enter a new $val at any time, and when they enter it, an entry must also be made in the status table.

    statuses:
    status_id int(11) UNIQUE AUTO_INCREMENT NOT NULL
    data_id int(11) NOT NULL
    status int(1) NOT NULL

The code I'm using to accomplish this is:

// Get value
$insData = $_GET['VAL'];
// Insert value
$ins = mysql_query( "INSERT INTO data (myData) VALUES ('" . $insData . "')" )
    or die(mysql_error());
// Get value's id
$res = mysql_query( "SELECT data_id FROM data WHERE myData='" . $insData . "'" )
    or die(mysql_error());
// From here I would insert into the statuses table,
// but the new entry into the data table is sometimes not found!

Once every 100-1000 times the code gets run, the select will execute and return no rows. The data insert will execute correctly. I didn't believe that this was possible because the insert or die forces PHP to wait for a return value so that it would know whether or not to execute the 'or die' portion of the line. Is it possible that PHP has received a return value from the insert command before it has finished executing, thus the select statement immediately following the insert fails to return any rows? And how could this intermittent problem be prevented in the future?

Farren
  • 1
  • 2
  • 2
    **Warning:** mysql extension is [deprecated](http://stackoverflow.com/questions/13944956) as of PHP 5.5.0, and will be removed in the future. Instead, the [MySQLi](http://www.php.net/manual/en/book.mysqli.php) or [PDO_MySQL](http://www.php.net/manual/en/ref.pdo-mysql.php) extension should be used. Please don't use `mysql_*` to develop new code. – bansi Sep 06 '13 at 15:49
  • 2
    As stated in the question, I know mysql extension is deprecated. The environment is PHP 4.4, and cannot and will not ever be upgraded. MySQLi and PDO are not supported in PHP 4.4, so the point is mute. – Farren Sep 06 '13 at 15:55
  • Have you noticed any patterns on the insData that failed? – Mihai Sep 06 '13 at 16:00
  • After INSERT verify the number of affected rows using mysql_affected_rows() for a test. – Maykonn Sep 06 '13 at 16:01
  • Try this `SELECT data_id FROM data ORDER BY id DESC LIMIT 0, 1` and see what happens. – MahanGM Sep 06 '13 at 16:16
  • MahanGM: Can't select the last inserted ID (limit 1) because there are many users inserting all at once, and no way to guarantee each user would get their own insert. Wing: data that is inserted / selected is sanitized, but I didn't bother to add irrelevant code (question is long enough as-is!). Mihai: No pattern to errors, can happen at light or peak times. Data added is always a varchar unique identifier, with almost no variation, and is guaranteed to be unique in itself. – Farren Sep 06 '13 at 17:47

2 Answers2

0

Is the data you're trying to INSERT (when you don't get the expected error message) a duplicate record (ie matches a record that's already in TABLE DATA) by chance?

If so, see

specifically, quoting from mysqlgatchas:

"...A further idiosyncracy seems to be this behaviour:

CREATE TABLE insert_test2 (
id INT NOT NULL PRIMARY KEY,
txt VARCHAR(32)
);
INSERT INTO insert_test2 VALUES(1, 'foo');
INSERT INTO insert_test2 VALUES(2, 'bar');

Now watch this:

mysql> INSERT INTO insert_test2 VALUES(1, 'bar');
ERROR 1062: Duplicate entry '1' for key 1

Correct behaviour... now watch this:

mysql> INSERT INTO insert_test2 (id, txt) SELECT i.id, i.txt FROM insert_test i 
WHERE i.id=1;
Query OK, 0 rows affected (0.00 sec)
Records: 1  Duplicates: 1  Warnings: 0

mysql> SELECT * FROM insert_test2;
+----+------+
| id | txt  |
+----+------+
|  1 | foo  |
|  2 | bar  |
+----+------+
2 rows in set (0.00 sec)

Expected behaviour would be for this statement to fail with the same error as above; instead it appears that the statement was successful, with only the Duplicates: 1 notice indicating the statement was effectively ignored.

Update: in version 4.0.20, and possibly previous versions, this statement returns the expected error message..."

Community
  • 1
  • 1
caroline
  • 1
  • 2
  • Definitely not a duplicate! The insert succeeds, I can view the data afterwords, but my subsequent select returns 0 rows, and not having any value for $res['data_id'] I end up inserting a status with data_id = 0. I can only assume that somehow the select happens before the insert completes, but thta shouldn't be possible! – Farren Sep 06 '13 at 17:45
  • Not sure about this at all -- but maybe there's a concurrency issue in between the time your PHP code executes the separate INSERT and SELECT statements? If so, then maybe using one transaction to execute both statements might help (no other session can get in the way). – caroline Sep 06 '13 at 20:48
  • I suppose it's possible; they're only separated to make the code easier to read. – Farren Sep 13 '13 at 19:47
0

There's not a good explanation for why this occasionally fails, although if your table is MyISAM then I suspect you have some sketchiness creeping into your physical table structures. Otherwise there may be something about the values in your unescaped string that is causing the query not to be able to find what it just inserted.

The correct way to identify the last row you inserted it this:

SELECT LAST_INSERT_ID();

or this:

SELECT @@LAST_INSERT_ID;

These are equivalent, and will always give you the value assigned to the auto-increment column of the last row you inserted -- even if another session has inserted data after you.

Michael - sqlbot
  • 169,571
  • 25
  • 353
  • 427
  • I'll try this. Unfortunately I won't know if it's worked until a few weeks of usage have gone by, since I cannt purposefully recreate the problem and even a for loop inserting 10,000 values one-after-another fails to fail. I've only got one auto incrementing column in the DATA table, but what would happen if I used this command and had two auto incrementing columns? Would it default to the primary key? – Farren Sep 06 '13 at 17:56
  • I don't think you can have more than one auto-increment column on a single table in MySQL. `ERROR 1075 (42000): Incorrect table definition; there can be only one auto column and it must be defined as a key` – Michael - sqlbot Sep 06 '13 at 21:14
  • TIL. Only asked because I was curious, anyway. I haven't seen the problem crop up since implementing, but will keep my eye open. – Farren Sep 13 '13 at 19:46