0

I am having some difficulty with MySQL seeming to insert a row twice. - Basically, I need to save a local copy of some information that is retrieved from a remote data source. So when a user views information that is from the remote source, I check to see if I have a local copy of the information I need to store. If I don't have a local copy I add a record of that information. - The issue I am having is that every I would say 20-30 inserts I get a duplicate. I keep track of the insert and update times using the NOW function, and both records seem to be inserted at the same time. Here is my PHP code, any help would be very appreciated, I am stumped:

// We have the location, see if we have a local record for that location
        $idLocation = locationID_for_factualID($factual_id);
        if(!$idLocation) {
            // We do not have local information about the location, add it
            $mysqli = open_mysql_connection();
            $stmt = $mysqli->prepare("INSERT INTO Location (
                                                    factual_id,
                                                    dateAdded, 
                                                    dateModified, 
                                                    locationName,
                                                    latitude, 
                                                    longitude) 
                                                VALUES (?, NOW(), NOW(), ?, ?, ?)");
            if($stmt) {
                $stmt->bind_param("ssdd",$factual_id, $location["name"], doubleval($location["latitude"]), doubleval($location["longitude"]));
                $stmt->execute();
                // Check if the location was added
                if($stmt->affected_rows == 1){
                    $idLocation = locationID_for_factualID($factual_id);
                }
                $stmt->close();
                $mysqli->close();
            }
            else {
                return FALSE;
            }
        }

Here are two rows that seem to be inserted back to back:

idLocation | factual_id | dateAdded | dateModified | locationName | latitude | longitude
520 | 5f79360f-330f-4035-ae75-e872ea14cfdd | 2013-04-09 14:36:55 | 2013-04-09 14:36:55 | Quiznos | 40.1802 | -74.0258
521 | 5f79360f-330f-4035-ae75-e872ea14cfdd | 2013-04-09 14:36:55 | 2013-04-09 14:36:55 | Quiznos | 40.1802 | -74.0258
CatamaranDan
  • 83
  • 1
  • 8
  • what is `locationID_for_factualID` doing? – Marcin Orlowski Apr 09 '13 at 14:55
  • locationID_for_factualID is returning the Location.idLocation if it finds a row with the factual ID, otherwise it is returning FALSE. So basically if the row exists it returns my local id for that location, otherwise it returns FALSE. – CatamaranDan Apr 09 '13 at 15:00

1 Answers1

0

Although I don't see your implementation of locationID_for_factualID, I'm pretty sure it's doing something like SELECT idLocation ... WHERE factual_id = '$factual_id' (don't forget to use query params!).

I can imagine you're having a race condition, i.e. two requests callign the same script with the same input data at almost the same time, then calling the function locationID_for_factualID and both finding that $idLocation == false. Then, both requests execute the INSERT and you end up with a duplicate record.

The standard solution for this issue is to use transactions. Have a look here how this is done. The important part is wrapping the SELECT inside of locationID_for_factualID AND the INSERT with the same transaction, i.e. between START TRANSACTION and COMMIT. You'll probably have to change the implementation of locationID_for_factualID to use the same DB connection as the following INSERT.

As an aside, you might want to create a UNIQUE INDEX on column factual_id. This will prevent the same factual_id from occurring more than once by letting the INSERT statement fail if it tries to insert a duplicate.

Community
  • 1
  • 1
Marcellus
  • 1,277
  • 8
  • 7
  • Thanks, I can make those changes and will look into this immediately. However, I am fairly sure that this is not a race condition, as I just tested this and I was the only user. I guess it is possible that the device I was using made two requests, but I highly doubt it. – CatamaranDan Apr 09 '13 at 15:17