125 votes

Meilleures pratiques pour la migration de base de données dans l'application pour Sqlite

J'utilise sqlite pour mon iphone et je prévois que le schéma de base de données pourrait changer avec le temps. Quels sont les pièges, les conventions de dénomination et les éléments à surveiller pour réussir une migration à chaque fois?

Par exemple, j'ai envisagé d'ajouter une version au nom de la base de données (par exemple, Database_v1).

146voto

Clusterflock Points 784

Je maintiens une application qui doit régulièrement mettre à jour une base de données sqlite et migrer des bases de données vers le nouveau schéma et voici ce que je fais:

Pour le suivi de la version de base de données, j'utilise le bâti de l'utilisateur-version variable que sqlite (sqlite ne fait rien avec cette variable, vous êtes libre de l'utiliser comme bon vous semble). Il commence à 0, et vous pouvez obtenir/définir cette variable avec la suite de sqlite déclarations:

> PRAGMA user_version;  
> PRAGMA user_version = 1;

Lorsque l'application démarre, j'ai vérifier le courant de l'utilisateur-version, appliquer toutes les modifications qui sont nécessaires pour mettre le schéma à jour, puis mise à jour de l'utilisateur-version. J'enveloppe les mises à jour dans une transaction de sorte que si quelque chose va mal, les changements ne sont pas commis.

Pour apporter des modifications de schéma, sqlite prend en charge "ALTER TABLE" la syntaxe pour certaines opérations (changer le nom de la table ou de l'ajout d'une colonne). C'est un moyen facile de mettre à jour les tables en place. Voir la documentation ici: http://www.sqlite.org/lang_altertable.html. Pour la suppression de colonnes ou d'autres changements qui ne sont pas pris en charge par les "ALTER TABLE" de la syntaxe, j'ai créer une nouvelle table, migrer date, à la chute de l'ancien tableau, et renommez la nouvelle table à son nom d'origine.

35voto

Billy Gray Points 897

La réponse de Just Curious est sans appel (vous avez compris mon point!), Et c'est ce que nous utilisons pour suivre la version du schéma de base de données qui est actuellement dans l'application.

Pour exécuter les migrations qui doivent avoir lieu pour que version_utilisateur corresponde à la version de schéma attendue de l'application, nous utilisons une instruction switch. Voici un exemple détaillé de ce à quoi cela ressemble dans notre application Strip :

 - (void) migrateToSchemaFromVersion:(NSInteger)fromVersion toVersion:(NSInteger)toVersion { 
    // allow migrations to fall thru switch cases to do a complete run
    // start with current version + 1
    [self beginTransaction];
    switch (fromVersion + 1) {
        case 3:
            // change pin type to mode 'pin' for keyboard handling changes
            // removing types from previous schema
            sqlite3_exec(db, "DELETE FROM types;", NULL, NULL, NULL);
            NSLog(@"installing current types");
            [self loadInitialData];
        case 4:
            //adds support for recent view tracking
            sqlite3_exec(db, "ALTER TABLE entries ADD COLUMN touched_at TEXT;", NULL, NULL, NULL);
        case 5:
            {
                sqlite3_exec(db, "ALTER TABLE categories ADD COLUMN image TEXT;", NULL, NULL, NULL);
                sqlite3_exec(db, "ALTER TABLE categories ADD COLUMN entry_count INTEGER;", NULL, NULL, NULL);
                sqlite3_exec(db, "CREATE INDEX IF NOT EXISTS categories_id_idx ON categories(id);", NULL, NULL, NULL);
                sqlite3_exec(db, "CREATE INDEX IF NOT EXISTS categories_name_id ON categories(name);", NULL, NULL, NULL);
                sqlite3_exec(db, "CREATE INDEX IF NOT EXISTS entries_id_idx ON entries(id);", NULL, NULL, NULL);

               // etc...
            }
    }

    [self setSchemaVersion];
    [self endTransaction];
}
 

20voto

Andrey Tarantsov Points 4487

Permettez-moi de partager du code de migration avec FMDB et MBProgressHUD.

Voici comment lire et écrire le numéro de version de schéma (c'est sans doute une partie d'un modèle de classe, dans mon cas, c'est une classe singleton appelé Base de données):

- (int)databaseSchemaVersion {
    FMResultSet *resultSet = [[self database] executeQuery:@"PRAGMA user_version"];
    int version = 0;
    if ([resultSet next]) {
        version = [resultSet intForColumnIndex:0];
    }
    return version;
}

- (void)setDatabaseSchemaVersion:(int)version {
    // FMDB cannot execute this query because FMDB tries to use prepared statements
    sqlite3_exec([self database].sqliteHandle, [[NSString stringWithFormat:@"PRAGMA user_version = %d", DatabaseSchemaVersionLatest] UTF8String], NULL, NULL, NULL);
}

Voici [self database] méthode que paresseusement ouvre la base de données:

- (FMDatabase *)database {
    if (!_databaseOpen) {
        _databaseOpen = YES;

        NSString *documentsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
        NSString *databaseName = [NSString stringWithFormat:@"userdata.sqlite"];

        _database = [[FMDatabase alloc] initWithPath:[documentsDir stringByAppendingPathComponent:databaseName]];
        _database.logsErrors = YES;

        if (![_database openWithFlags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FILEPROTECTION_COMPLETE]) {
            _database = nil;
        } else {
            NSLog(@"Database schema version is %d", [self databaseSchemaVersion]);
        }
    }
    return _database;
}

Et voici des méthodes de migration appelée à partir de la vue-contrôleur:

- (BOOL)databaseNeedsMigration {
    return [self databaseSchemaVersion] < databaseSchemaVersionLatest;
}

- (void)migrateDatabase {
    int version = [self databaseSchemaVersion];
    if (version >= databaseSchemaVersionLatest)
        return;

    NSLog(@"Migrating database schema from version %d to version %d", version, databaseSchemaVersionLatest);

    // ...the actual migration code...
    if (version < 1) {
        [[self database] executeUpdate:@"CREATE TABLE foo (...)"];
    }

    [self setDatabaseSchemaVersion:DatabaseSchemaVersionLatest];
    NSLog(@"Database schema version after migration is %d", [self databaseSchemaVersion]);
}

Et voici la vue de la racine de contrôleur de code qui appelle la migration, à l'aide de MBProgressHUD pour afficher une progression de la lunette:

- (void)viewDidAppear {
    [super viewDidAppear];
    if ([[Database sharedDatabase] userDatabaseNeedsMigration]) {
        MBProgressHUD *hud = [[MBProgressHUD alloc] initWithView:self.view.window];
        [self.view.window addSubview:hud];
        hud.removeFromSuperViewOnHide = YES;
        hud.graceTime = 0.2;
        hud.minShowTime = 0.5;
        hud.labelText = @"Upgrading data";
        hud.taskInProgress = YES;
        [[UIApplication sharedApplication] beginIgnoringInteractionEvents];

        [hud showAnimated:YES whileExecutingBlock:^{
            [[Database sharedDatabase] migrateUserDatabase];
        } onQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0) completionBlock:^{
            [[UIApplication sharedApplication] endIgnoringInteractionEvents];
        }];
    }
}

4voto

Liron Levi Points 701

La meilleure solution IMO consiste à créer un framework de mise à niveau SQLite. J'ai eu le même problème (dans le monde C #) et j'ai construit mon propre cadre. Vous pouvez lire à ce sujet ici . Cela fonctionne parfaitement et fait fonctionner mes mises à niveau (auparavant cauchemardesques) avec un minimum d'effort de mon côté.

Bien que la bibliothèque soit implémentée en C #, les idées présentées devraient également bien fonctionner dans votre cas.

1voto

Alex Martelli Points 330805

Si vous modifiez le schéma de base de données et tout le code qui l'utilise en bloc, comme c'est probablement le cas dans les applications intégrées et localisées, le problème est en réalité bien maîtrisé (rien de comparable au cauchemar que constitue la migration de schéma sur une base de données d'entreprise. cela peut servir des centaines d'applications - pas tous sous le contrôle de la DBA non plus ;-).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X