Detailliertes Beispiel für die Datenmigration bei einem IOS-Datenbank-Upgrade

Detailliertes Beispiel für die Datenmigration bei einem IOS-Datenbank-Upgrade

Detailliertes Beispiel für die Datenmigration bei einem IOS-Datenbank-Upgrade

Zusammenfassung:

Vor langer Zeit bin ich auf ein Referenzszenario für ein Upgrade einer Datenbankversion gestoßen. Damals bestand der Ansatz darin, einfach die alten Datenbankdateien zu löschen und die Datenbank- und Tabellenstruktur neu aufzubauen. Diese gewaltsame Upgrade-Methode würde zum Verlust alter Daten führen. Jetzt scheint dies keine elegante Lösung zu sein. Jetzt verwendet ein neues Projekt die Datenbank erneut und ich muss dieses Problem überdenken. Ich hoffe, dieses Problem auf elegantere Weise lösen zu können. Wir werden in Zukunft auf ähnliche Szenarien stoßen und wir alle wollen es besser machen, nicht wahr?

Die ideale Situation ist: Wenn die Datenbank aktualisiert wird, ändern sich die Tabellenstruktur, der Primärschlüssel und die Einschränkungen. Nachdem die neue Tabellenstruktur eingerichtet wurde, werden die Daten automatisch aus der alten Tabelle abgerufen und dieselben Felder werden zugeordnet und migriert. In den meisten Geschäftsszenarien umfassen Upgrades der Datenbankversion nur das Hinzufügen oder Entfernen von Feldern und das Ändern von Primärschlüsseleinschränkungen. Daher basiert die im Folgenden zu implementierende Lösung auch auf den grundlegendsten und am häufigsten verwendeten Geschäftsszenarien. Bei komplexeren Szenarien können Sie diese Basis erweitern, um Ihren Erwartungen gerecht zu werden.

Auswahl und Finalisierung

Nach meiner Onlinesuche habe ich keine einfache und vollständige Lösung für Datenbank-Upgrade und Datenmigration gefunden. Ich habe einige Ideen gefunden.

1. Alte Daten löschen und Tabelle neu aufbauen

Vorteile: Einfach Nachteile: Datenverlust

2. Ändern Sie die Tabellenstruktur basierend auf der vorhandenen Tabelle

Vorteile: Möglichkeit, Daten beizubehalten. Nachteile: Die Regeln sind relativ kompliziert. Sie müssen eine Datenbankfeldkonfigurationsdatei erstellen, dann die Konfigurationsdatei lesen und SQL ausführen, um die Tabellenstruktur, Einschränkungen, Primärschlüssel usw. zu ändern. Das Aktualisieren der Datenbank über mehrere Versionen hinweg wird mühsam und mühsam.

3. Erstellen Sie eine temporäre Tabelle, kopieren Sie die alten Daten in die temporäre Tabelle, löschen Sie dann die alte Datentabelle und legen Sie die temporäre Tabelle als Datentabelle fest.

Vorteile: Möglichkeit zur Datenspeicherung, Unterstützung von Änderungen der Tabellenstruktur, Änderungen an Einschränkungen und Primärschlüsseln, relativ einfache Implementierung Nachteile: erfordert viele Schritte zur Implementierung

Unter Berücksichtigung aller Faktoren ist die dritte Methode die zuverlässigere Lösung.

Hauptschritte

Basierend auf dieser Idee werden die wichtigsten Schritte der Datenbankaktualisierung wie folgt analysiert:

  • Holen Sie sich die alte Tabelle in der Datenbank
  • Ändern Sie den Tabellennamen, fügen Sie das Suffix „_bak“ hinzu und verwenden Sie die alte Tabelle als Sicherungstabelle
  • Erstellen einer neuen Tabelle
  • Holen Sie sich die neu erstellte Tabelle
  • Durchlaufen Sie die alte und die neue Tabelle, vergleichen und extrahieren Sie die Felder der Tabelle, die migriert werden müssen
  • Datenmigrationsverarbeitung
  • Löschen der Sicherungstabelle

Analyse der verwendeten SQL-Anweisungen

Diese Operationen beziehen sich alle auf Datenbankoperationen. Der Schlüssel zum Problem sind also die SQL-Anweisungen der entsprechenden Schritte. Im Folgenden finden Sie eine Analyse der wichtigsten verwendeten SQL-Anweisungen:

Holen Sie sich die alte Tabelle in der Datenbank

SELECT * von sqlite_master WHERE Typ = "Tabelle"

Das Ergebnis ist wie folgt. Sie können sehen, dass es Datenbankfelder wie Typ | Name | tbl_name | rootpage | sql gibt. Wir müssen nur das Namensfeld verwenden, das den Datenbanknamen darstellt.

sqlite> SELECT * from sqlite_master WHERE Typ='Tabelle'
 ...> ;
+----------+------------------+-----------+----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Typ | Name | Tabellenname | Stammseite | SQL |
+----------+------------------+-----------+----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Tabelle | t_message_bak | t_message_bak | 2 | TABELLE "t_message_bak" ERSTELLEN (Nachrichten-ID TEXT, Nachrichtentyp INTEGER, Nachrichten-Json-Inhalt TEXT, Zeitzeichenfolge abrufen INTEGER, Zeitzeichenfolge posten INTEGER, Status lesen INTEGER, PRIMÄRSCHLÜSSEL(Nachrichten-ID)) |
| Tabelle | t_Nachricht | t_Nachricht | 4 | TABELLE ERSTELLEN t_Nachricht (
 Nachrichten-ID TEXT, 
 Nachrichtentyp INTEGER,
 messageJsonContent TEXT, 
 retriveTimeString INTEGER, 
 postTimeString INTEGER, 
 Lesestatus INTEGER, 
 Spalte hinzufügen INTEGER,
 PRIMARY KEY(Nachrichten-ID)
) |
+----------+------------------+-----------+----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
2 Zeilen im Datensatz (0,03 Sekunden)

Ändern Sie den Tabellennamen, fügen Sie das Suffix „_bak“ hinzu und verwenden Sie die alte Tabelle als Sicherungstabelle

-- Ändert die Tabelle t_message in die Tabelle t_message_bak ALTER TABLE t_message RENAME TO t_message_bak

Abrufen von Tabellenfeldinformationen

-- Holen Sie sich die Feldinformationen der Tabelle t_message_bak PRAGMA table_info('t_message_bak')

Die erhaltenen Tabellenfeldinformationen lauten wie folgt. Sie können sehen: | cid | name | type | notnull | dflt_value | pk | Bei diesen Datenbankfeldern müssen wir nur das Namensfeld verwenden, das den Feldnamen darstellt.

sqlite> PRAGMA table_info('t_message_bak');
+------+--------------------+---------+---------+------------+------+
| cid | Name | Typ | nicht null | dflt_value | pk |
+------+--------------------+---------+---------+------------+------+
| 0 | Nachrichten-ID | TEXT | 0 | NULL | 1 |
| 1 | Nachrichtentyp | INTEGER | 0 | NULL | 0 |
| 2 | messageJsonContent | TEXT | 0 | NULL | 0 |
| 3 | retriveTimeString | INTEGER | 0 | NULL | 0 |
| 4 | postTimeString | INTEGER | 0 | NULL | 0 |
| 5 | Lesestatus | INTEGER | 0 | NULL | 0 |
+------+--------------------+---------+---------+------------+------+
6 Zeilen im Datensatz (0,01 Sekunden)

Verwenden von Unterabfragen zur Datenmigration

INSERT INTO t_message(Nachrichten-ID, Nachrichtentyp, Nachrichten-JSON-Inhalt, Zeitzeichenfolge abrufen,
 postTimeString, readState) SELECT Nachrichten-ID, Nachrichtentyp, Nachrichten-Json-Inhalt, retriveTimeString,
 postTimeString, readState VON t_message_bak

Kopieren Sie die Werte der Felder messageID, messageType, messageJsonContent, retriveTimeString, postTimeString und readState in der Tabelle t_message_bak in die Tabelle t_message

Code-Implementierung

Als nächstes folgt der Schritt der Codeimplementierung.

// Eine neue temporäre Tabelle erstellen, die Daten in die temporäre Tabelle importieren und dann die Originaltabelle durch die temporäre Tabelle ersetzen - (void)baseDBVersionControl {
 NSString * version_alt = ValueOrEmpty(MMUserDefault.dbVersion);
 NSString * version_new = [NSString stringWithFormat:@"%@", DB_Version];
 NSLog(@"dbVersionControl vor: %@ nach: %@",version_alt,version_neu);

 // Upgrade der Datenbankversion if (version_old != nil && ![version_new isEqualToString:version_old]) {

  // Holen Sie die alten Tabellen in der Datenbank NSArray* existsTables = [self sqliteExistsTables];
  NSMutableArray* tmpExistsTables = [NSMutableArray-Array];

  // Ändern Sie den Tabellennamen, fügen Sie das Suffix „_bak“ hinzu und verwenden Sie die alte Tabelle als Sicherungstabelle für (NSString* tablename in existsTables) {
   [tmpExistsTables addObject:[NSString stringWithFormat:@"%@_bak", Tabellenname]];
   [self.databaseQueue inDatabase:^(FMDatabase *db) {
    NSString* sql = [NSString stringWithFormat:@"ALTER TABLE %@ RENAME TO %@_bak", Tabellenname, Tabellenname];
    [db ausführenUpdate:sql];
   }];
  }
  existsTables = tmpExistsTables;

  // Eine neue Tabelle erstellen [self initTables];

  // Holen Sie die neu erstellte Tabelle NSArray* newAddedTables = [self sqliteNewAddedTables];

  // Die alte und die neue Tabelle durchlaufen, die Felder der Tabelle vergleichen und extrahieren, die migriert werden müssen NSDictionary* migrationInfos = [self generateMigrationInfosWithOldTables:existsTables newTables:newAddedTables];

  // Datenmigration wird verarbeitet [migrationInfos enumerateKeysAndObjectsUsingBlock:^(NSString* newTableName, NSArray* publicColumns, BOOL * _Nonnull stop) {
   NSMutableString* colunmsString = [NSMutableString neu];
   für (int i = 0; i<publicColumns.count; i++) {
    [SpaltenString appendString:öffentlicheSpalten[i]];
    wenn (i != publicColumns.count-1) {
     [Spaltenzeichenfolge Anfügezeichenfolge:@", "];
    }
   }
   NSMutableString* sql = [NSMutableString neu];
   [sql anhängenString:@"INSERT INTO "];
   [sql appendString:neuerTabellenname];
   [sql anhängenString:@"("];
   [sql anhängenString:SpaltenString];
   [sql anhängenString:@")"];
   [sql Anfügezeichenfolge:@" AUSWÄHLEN "];
   [sql anhängenString:SpaltenString];
   [sql anhängenString:@" FROM "];
   [sql appendFormat:@"%@_bak", neuerTabellenname];

   [self.databaseQueue inDatabase:^(FMDatabase *db) {
    [db ausführenUpdate:sql];
   }];
  }];

  // Lösche die Backup-Tabelle [self.databaseQueue inDatabase:^(FMDatabase *db) {
   [db beginTransaktion];
   für (NSString* alterTabellenname in existierTabellen) {
    NSString* sql = [NSString stringWithFormat:@"DROP TABLE IF EXISTS %@", alterTabellenname];
    [db ausführenUpdate:sql];
   }
   [Datenbank-Commit];
  }];

  MMUserDefault.dbVersion = version_new;

 } anders {
  MMUserDefault.dbVersion = version_new;
 }
}

- (NSDictionary*)generateMigrationInfosWithOldTables:(NSArray*)oldTables newTables:(NSArray*)newTables {
 NSMutableDictionary<NSString*, NSArray* >* migrationInfos = [NSMutableDictionary-Wörterbuch];
 für (NSString* neuerTabellenname in neueTabellen) {
  NSString* alterTabellenname = [NSString stringWithFormat:@"%@_bak", neuerTabellenname];
  if ([alteTabellen enthältObjekt:alterTabellenname]) {
   //Feldinformationen der Tabellendatenbank abrufen NSArray* oldTableColumns = [self sqliteTableColumnsWithTableName:oldTableName];
   NSArray* neueTabellenspalten = [self sqliteTableColumnsWithTableName:neuerTabellenname];
   NSArray* publicColumns = [self publicColumnsWithOldTableColumns:alteTableColumns newTableColumns:neueTableColumns];

   wenn (publicColumns.count > 0) {
    [migrationInfos setObject:publicColumns fürKey:newTableName];
   }
  }
 }
 Migrationsinformationen zurückgeben;
}

- (NSArray*)publicColumnsWithOldTableColumns:(NSArray*)oldTableColumns newTableColumns:(NSArray*)newTableColumns {
 NSMutableArray* publicColumns = [NSMutableArray-Array];
 für (NSString* alteTabellenspalte in alteTabellenspalten) {
  if ([neueTabellenspalten enthältObjekt:alteTabellenspalte]) {
   [publicColumns addObject:alteTabellenspalte];
  }
 }
 öffentliche Spalten zurückgeben;
}

- (NSArray*)sqliteTableColumnsWithTableName:(NSString*)tableName {
 __block NSMutableArray<NSString*>* tableColumes = [NSMutableArray-Array];
 [self.databaseQueue inDatabase:^(FMDatabase *db) {
  NSString* sql = [NSString stringWithFormat:@"PRAGMA table_info('%@')", Tabellenname];
  FMResultSet *rs = [db ausführenAbfrage:sql];
  während ([rs weiter]) {
   NSString* Spaltenname = [rs Zeichenfolge für Spalte:@"Name"];
   [Tabellenspalten addObject:Spaltenname];
  }
 }];
 gibt Tabellenspalten zurück;
}

- (NSArray*)sqliteExistsTables {
 __block NSMutableArray<NSString*>* existsTables = [NSMutableArray-Array];
 [self.databaseQueue inDatabase:^(FMDatabase *db) {
  NSString* sql = @"SELECT * from sqlite_master WHERE Typ='Tabelle'";
  FMResultSet *rs = [db ausführenAbfrage:sql];
  während ([rs weiter]) {
   NSString* Tabellenname = [rs stringForColumn:@"name"];
   [existsTables addObject:Tabellenname];
  }
 }];
 gibt existiereTabellen zurück;
}

- (NSArray*)sqliteNewAddedTables {
 __block NSMutableArray<NSString*>* newAddedTables = [NSMutableArray-Array];
 [self.databaseQueue inDatabase:^(FMDatabase *db) {
  NSString* sql = @"SELECT * from sqlite_master WHERE Typ='Tabelle' UND Name NICHT WIE '%_bak'";
  FMResultSet *rs = [db ausführenAbfrage:sql];
  während ([rs weiter]) {
   NSString* Tabellenname = [rs stringForColumn:@"name"];
   [newAddedTables addObject:Tabellenname];
  }
 }];
 gibt neue hinzugefügte Tabellen zurück;
}

Frage

Das Problem, dass SQLite Tabellendateien löscht, ohne ihre Größe zu ändern

Wenn Sie Fragen haben, hinterlassen Sie bitte eine Nachricht oder kommen Sie zur Diskussion in die Community. Vielen Dank fürs Lesen und ich hoffe, es kann Ihnen helfen. Vielen Dank für Ihre Unterstützung dieser Site!

Das könnte Sie auch interessieren:
  • Detaillierte Erklärung zur Verwendung der Axios-Methode der HTTP-Bibliothek in VUE
  • Detaillierte Erläuterung des zugrunde liegenden Beispiels der Benachrichtigungsframeworkbibliothek des iOS-Systems
  • Eine kurze Diskussion über die leistungsstarke und benutzerfreundliche iOS-Routingbibliothek FFRouter, die URL Rewrite unterstützt
  • iOS-Entwicklungsnotizen: Tastatur, statische Bibliothek, Animation und Crash-Positionierung
  • Detaillierte Anwendungsbeispiele basierend auf der iOS Realm-Datenbank
  • So debuggen Sie die Datenbank elegant in der iOS-Entwicklung
  • Detaillierte Erläuterung der Erstellung statischer .a- und .framework-Bibliotheken und der Verwendung von .bundle-Ressourcenpaketen in iOS
  • IOS UIImagePickerController ruft Bilder von Kamera, Galerie und Album ab
  • Beispiele zum Hinzufügen, Löschen, Ändern und Überprüfen der FMDB-Datenbank in iOS
  • Der Unterschied zwischen dynamischer und statischer iOS-Bibliothek

<<:  Anpassungsmethode des Linux-Peripheriedateisystems

>>:  Beispiel für die Implementierung der Wertübertragung zwischen WeChat-Miniprogrammseiten

Artikel empfehlen

Detaillierte Installation und Konfiguration von hadoop2.7.2 unter Ubuntu15.10

Im Internet und in vielen Büchern gibt es viele T...

Beispiel zum Ändern des Zeilenabstands einer HTML-Tabelle

Bei der Verwendung von HTML-Tabellen müssen wir m...

Grafisches Tutorial zur Installation und Konfiguration von MySQL 5.7.18 winx64

Die Installation komprimierter Pakete hat sich se...

CSS-Syntax für Tabellenränder

<br /> CSS-Syntax für Tabellenränder Zu den ...

Docker-Grundlagen

Vorwort: Docker ist eine Open-Source-Anwendungsco...

Gründe, warum MySQL den Abfrage-Cache abgebrochen hat

MySQL hatte zuvor einen Abfragecache, Query Cache...

Detaillierte Erklärung der Kodierungsprobleme bei MySQL-Befehlszeilenoperationen

1. Überprüfen Sie die MySQL-Datenbankkodierung my...