Frage Best Practices für die In-App-Datenbankmigration für Sqlite


Ich benutze sqlite für mein iPhone und ich nehme an, dass sich das Datenbankschema im Laufe der Zeit ändern könnte. Was sind die Fehler, Namenskonventionen und Dinge, auf die man achten sollte, um jedes Mal eine erfolgreiche Migration durchzuführen?

Zum Beispiel habe ich daran gedacht, eine Version an den Datenbanknamen anzuhängen (z. B. Database_v1).


75
2018-06-13 00:02


Ursprung


Antworten:


Ich pflege eine Anwendung, die in regelmäßigen Abständen eine SQLite-Datenbank aktualisieren und alte Datenbanken auf das neue Schema migrieren muss. Und hier ist, was ich mache:

Um die Datenbankversion zu verfolgen, verwende ich die eingebaute Benutzerversionsvariable, die sqlite zur Verfügung stellt (sqlite tut nichts mit dieser Variablen, Sie können es frei verwenden, wie es Ihnen gefällt). Es beginnt bei 0, und Sie können diese Variable mit den folgenden SQL-Anweisungen abrufen / setzen:

> PRAGMA user_version;  
> PRAGMA user_version = 1;

Wenn die App gestartet wird, überprüfe ich die aktuelle Benutzerversion, lege alle Änderungen an, die erforderlich sind, um das Schema auf den neuesten Stand zu bringen, und aktualisiere dann die Benutzerversion. Ich wickle die Updates in eine Transaktion ein, sodass die Änderungen nicht festgeschrieben werden, wenn etwas schief geht.

Um Schemaänderungen vorzunehmen, unterstützt sqlite die "ALTER TABLE" -Syntax für bestimmte Operationen (Umbenennen der Tabelle oder Hinzufügen einer Spalte). Dies ist eine einfache Möglichkeit, vorhandene Tabellen direkt zu aktualisieren. Siehe die Dokumentation hier: http://www.sqlite.org/lang_altertable.html. Zum Löschen von Spalten oder anderen Änderungen, die nicht von der "ALTER TABLE" -Syntax unterstützt werden, erstelle ich eine neue Tabelle, migriere das Datum in die Tabelle, lösche die alte Tabelle und benenne die neue Tabelle in den ursprünglichen Namen um.


89
2018-06-15 22:04



Die Antwort von Just Curious ist tot (Sie haben meinen Punkt!), Und es ist, was wir verwenden, um die Version des Datenbank-Schemas zu verfolgen, die sich gerade in der App befindet.

Um die erforderlichen Migrationen auszuführen, um user_version mit der erwarteten Schemaversion der App zu vergleichen, verwenden wir eine switch-Anweisung. Hier sehen Sie ein Beispiel, wie das in unserer App aussieht Streifen:

- (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];
}

28
2018-06-29 14:15



Lassen Sie mich einen Migrationscode mit FMDB und MBProgressHUD teilen.

So lesen und schreiben Sie die Versionsnummer des Schemas (das ist vermutlich Teil einer Modellklasse, in meinem Fall handelt es sich um eine Singleton-Klasse namens Database):

- (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);
}

Hier ist [self database] Methode, die die Datenbank langsam öffnet:

- (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;
}

Und hier sind Migrationsmethoden, die vom View-Controller aufgerufen werden:

- (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]);
}

Und hier ist der Root-View-Controller-Code, der die Migration aufruft, indem Sie MBProgressHUD verwenden, um einen Fortschrittsbalken anzuzeigen:

- (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];
        }];
    }
}

18
2017-09-17 23:24



Die beste Lösung IMO ist ein SQLite-Upgrade-Framework zu erstellen. Ich hatte das gleiche Problem (in der C # -Welt) und ich habe mein eigenes solches Framework erstellt. Sie können darüber lesen Hier. Es funktioniert perfekt und macht meine (bisher alptraumhaften) Upgrades mit minimalem Aufwand auf meiner Seite.

Obwohl die Bibliothek in C # implementiert ist, sollten die dort vorgestellten Ideen auch in Ihrem Fall funktionieren.


4
2017-07-30 07:30



Wenn Sie das Datenbankschema und den gesamten Code, der es in lockstep verwendet, ändern, wie es in eingebetteten und im Telefon befindlichen Apps der Fall ist, ist das Problem tatsächlich gut unter Kontrolle (nichts ist vergleichbar mit dem Albtraum der Schemamigration in einer Unternehmensdatenbank) das kann Hunderte von Apps bedienen - nicht alle unter der Kontrolle des DBA ;-).


1
2018-06-13 00:08



Einige Hinweise...

1) Ich empfehle, den gesamten Code zu verwenden, um Ihre Datenbank in eine NSOperation zu migrieren und sie im Hintergrundthread auszuführen. Sie können eine benutzerdefinierte UIAlertView mit einem Drehfeld anzeigen, während die Datenbank migriert wird.

2) Stellen Sie sicher, dass Sie Ihre Datenbank aus dem Paket in die Dokumente der App kopieren und sie von diesem Ort aus verwenden. Andernfalls überschreiben Sie einfach die gesamte Datenbank mit jedem App-Update und migrieren dann die neue leere Datenbank.

3) FMDB ist großartig, aber seine executeQuery-Methode kann PRAGMA-Abfragen aus irgendeinem Grund nicht ausführen. Sie müssen Ihre eigene Methode schreiben, die sqlite3 direkt verwendet, wenn Sie die Schemaversion mit PRAGMA user_version überprüfen möchten.

4) Diese Codestruktur stellt sicher, dass Ihre Updates der Reihe nach ausgeführt werden und dass alle Updates ausgeführt werden, unabhängig davon, wie lange der Benutzer zwischen den App-Updates läuft. Es könnte weiter refaktoriert werden, aber das ist eine sehr einfache Art, es zu betrachten. Diese Methode kann sicher jedes Mal ausgeführt werden, wenn Ihre Daten Singleton instanziiert wird, und kostet nur eine winzige DB-Abfrage, die nur einmal pro Sitzung auftritt, wenn Sie Ihre Daten Singleton ordnungsgemäß einrichten.

- (void)upgradeDatabaseIfNeeded {
    if ([self databaseSchemaVersion] < 3)
    {
        if ([self databaseSchemaVersion] < 2)
        {
            if ([self databaseSchemaVersion] < 1)
            {
                // run statements to upgrade from 0 to 1
            }
            // run statements to upgrade from 1 to 2
        }
        // run statements to upgrade from 2 to 3

        // and so on...

        // set this to the latest version number
        [self setDatabaseSchemaVersion:3];
    }
}

1
2017-09-28 19:54



1. Erstellen /migrations Ordner mit der Liste der SQL-basierten Migrationen, wobei jede Migration in etwa wie folgt aussieht:

/migrations/001-categories.sql

-- Up
CREATE TABLE Category (id INTEGER PRIMARY KEY, name TEXT);
INSERT INTO Category (id, name) VALUES (1, 'Test');

-- Down
DROP TABLE User;

/migrations/002-posts.sql

-- Up
CREATE TABLE Post (id INTEGER PRIMARY KEY, categoryId INTEGER, text TEXT);

-- Down
DROP TABLE Post;

2. Erstellen Sie eine db-Tabelle mit der Liste der angewandten Migrationen, zum Beispiel:

CREATE TABLE Migration (name TEXT);

3. Aktualisieren Sie die Anwendungs ​​- Bootstrap - Logik so, dass sie vor dem Start die Liste der Migrationen von der /migrations Ordner und führt die Migrationen aus, die noch nicht angewendet wurden.

Hier ist ein Beispiel mit JavaScript implementiert: SQLite-Client für Node.js-Apps


1
2018-05-20 18:45