iosfmdb

FMDB: Database is locked when updating


I'm using FMDB and I'm using FMDatabase when I want to return some value and FMDatabaseQueue when I don't need to return any value. When I add to database and I use the FMDatabaseQueue I've no errors (except when I create a new database at the beginning when I'm deleting the database and then create [no database to delete so it creates a new one]). The problem is when I'm updating my database with the same FMDatabaseQueue I've an error:

Unknown error finalizing or resetting statement (5: database is locked)

DB Query: UPDATE traps_table SET dist=34721.853417, dist_to_close_point=35734.768293 WHERE _id=10

When I try the query on the database file itself I get no errors and all went great.

Code below:

- (void)updateTrapInDatabase:(FMDatabase*)db distance:(double)distance trapID:(int)trapID distanceToClosePoint:(double)distanceToClosePoint
{
    NSString *statment = [NSString stringWithFormat:@"UPDATE %@ SET %@=?, %@=? WHERE %@=?", TRAP_TABLE, DISTANCE, DISTANCE_TO_CLOSE_POINT, ID];
    BOOL update = [db executeUpdate:statment, @(distance), @(distanceToClosePoint), @(trapID)];
    NSLog(@"Update trapID: %i, success: %@", trapID, update?@"YES":[NSString stringWithFormat:@"NO: %@", [db lastErrorMessage]]);
}

The code where the function referenced from:

- (void)UpdateDistanceToTrap_currentLocation:(CLLocation*)currentLocation old_location:(CLLocation*)old_location theAzimuth:(double)theAzimuth theSpeed:(double)theSpeed
{
    NSLog(@"UpdateDistanceToTrap()");

    __block double latColumn, lonColumn, idColumn, closePointLatColumn, closePointLonColumn, alarmDistanceColumn;
    __block int _id = -1, alarmDistance = -1;
    __block double distance;
    __block CLLocation *trap_location = nil;

    __block NSString *sqlStatment = [NSString stringWithFormat:@"SELECT * FROM %@", TRAP_TABLE];

    FMResultSet *query = [trapsDB.db executeQuery:sqlStatment];

    if ([query next]) {
        latColumn = [query doubleForColumn:LAT];
        lonColumn = [query doubleForColumn:LON];
        closePointLatColumn = [query doubleForColumn:CLOSE_POINT_LAT];
        closePointLonColumn = [query doubleForColumn:CLOSE_POINT_LON];
        idColumn = [query intForColumn:@"_id"];
        alarmDistanceColumn = [query intForColumn:ALARMDISTANCE];

        do {
            _id = [query intForColumn:@"_id"];
            alarmDistance = [query intForColumn:ALARMDISTANCE];

            if ([old_location distanceFromLocation:currentLocation] > kDISTANCE_THRESHOLD_FOR_CHECK_LOCATIONS) {
                trap_location = [[CLLocation alloc] initWithLatitude:[query doubleForColumn:LAT]
                                                           longitude:[query doubleForColumn:LON]];

                distance = [trap_location distanceFromLocation:currentLocation];

                if (distance < alarmDistance + [ref integerForKey:@"DISTANCE_OFFSET"]) {
                    [trapsDB enterOrOutFromArea_operation:kENTER andTrapID:_id];

                    [self startSaveOnMyLocations:currentLocation.coordinate.latitude
                                             lon:currentLocation.coordinate.longitude
                                            time:CreateDate()
                                           speed:theSpeed
                                         azimuth:theAzimuth
                                          trapID:_id];
                }
                else {
                    if ([trapsDB isEnterToArea_trapID:_id] == kENTER) {
                        [self startPostXml:_id];
                        [trapsDB enterOrOutFromArea_operation:kOUT andTrapID:_id];
                    }
                }
            }
            if ([query doubleForColumn:CLOSE_POINT_LAT] != 0.0 || [query doubleForColumn:CLOSE_POINT_LAT] != 0) {
                trap_location = [[CLLocation alloc] initWithLatitude:[query doubleForColumn:CLOSE_POINT_LAT]
                                                           longitude:[query doubleForColumn:CLOSE_POINT_LON]];

                double distanceToClosePoint = [trap_location distanceFromLocation:currentLocation];

                trap_location = [[CLLocation alloc] initWithLatitude:[query doubleForColumn:LAT]
                                                           longitude:[query doubleForColumn:LON]];

                distance = [trap_location distanceFromLocation:currentLocation];

                [trapsDB updateTrapInDatabase:trapsDB.db distance:distance trapID:_id distanceToClosePoint:distanceToClosePoint];
            }
            else {
                trap_location = [[CLLocation alloc] initWithLatitude:[query doubleForColumn:LAT]
                                                           longitude:[query doubleForColumn:LON]];

                distance = [trap_location distanceFromLocation:currentLocation];

                [trapsDB updateTrapInDatabase:trapsDB.db distance:distance trapID:_id distanceToClosePoint:distance];
            }
        } while ([query next]);
    }
}

Whats wrong with my code?

EDIT:

TrapService.mm:

- (void)UpdateDistanceToTrap_currentLocation:(CLLocation*)currentLocation old_location:(CLLocation*)old_location theAzimuth:(double)theAzimuth theSpeed:(double)theSpeed;

#define trapsDB     ([SQLiteHandler sharedDatabase])

SQLiteHandler.m:

@property (strong, nonatomic) FMDatabase *db;

- (void)updateTrapInDatabase:(FMDatabase *)db distance:(double)distance trapID:(int)trapID distanceToClosePoint:(double)distanceToClosePoint;

EDIT-2: Updated my fixed code.


Solution

  • You are inside a inDatabase call, iterating through a SELECT statement and you then attempt to do another inDatabase call. If you're going to do that, you probably want to pass the FMDatabase as a parameter to the method, and not call inDatabase again:

    - (void)updateTrapInDatabase:(FMDatabase *)db distance:(double)distance trapID:(int)trapID distanceToClosePoint:(double)distanceToClosePoint
    {
        NSString *sqlStatment = [NSString stringWithFormat:@"UPDATE %@ SET %@=?, %@=? WHERE %@=?", TRAP_TABLE, DISTANCE, DISTANCE_TO_CLOSE_POINT, ID];
    
        BOOL update = [db executeUpdate:sqlStatment, @(distance), @(distanceTpClosePoint), @(trapID)];
        NSLog(@"Update trapID: %i, success: %@", trapID, update?@"YES":[NSString stringWithFormat:@"NO: %@", [db lastErrorMessage]]);
    }
    

    and replacing your call to:

    [trapsDB updateTrapDistance:distance andTrapID:_id andDistanceToClosePoint:distanceToClosePoint];
    

    with:

    [trapsDB updateTrapInDatabase:db distance:distance trapID:_id distanceToClosePoint:distanceToClosePoint];
    

    (As an aside, it's generally prudent to use ? placeholders in your SQL (avoids precision related problems with float variables, avoids SQL injection problems and quoting problems with NSString variables), so I also tweaked the SQL above. Also, if a SQL statement fails, I always log lastErrorMessage, otherwise I'm just guessing why the SQL failed. Use or disregard these unrelated tweaks as you see fit.)

    But the main point is that one should never call inDatabase while already in an inDatabase. What's curious, though, is that calling inDatabase while already in an inDatabase generally causes a deadlock, not a "database is locked" error. Frankly, the entire purpose of FMDatabaseQueue is to mediate database requests from different threads so that a database will never be busy. The only time you would generally receive "database is locked" error is if you had more than one FMDatabaseQueue or FMDatabase object. Your app should only have one database object open at any given time.