IOS 數據庫升級數據遷移的實例詳解
概要:
很久以前就遇到過數據庫版本升級的引用場景,當時的做法是簡單的刪除舊的數據庫文件,重建數據庫和表結構,這種暴力升級的方式會導致舊的數據的丟失,現在看來這并不不是一個優雅的解決方案,現在一個新的項目中又使用到了數據庫,我不得不重新考慮這個問題,我希望用一種比較優雅的方式去解決這個問題,以后我們還會遇到類似的場景,我們都想做的更好不是嗎?
理想的情況是:數據庫升級,表結構、主鍵和約束有變化,新的表結構建立之后會自動的從舊的表檢索數據,相同的字段進行映射遷移數據,而絕大多數的業務場景下的數據庫版本升級是只涉及到字段的增減、修改主鍵約束,所以下面要實現的方案也是從最基本的、最常用的業務場景去做一個實現,至于更加復雜的場景,可以在此基礎上進行擴展,達到符合自己的預期的。
選型定型
網上搜索了下,并沒有數據庫升級數據遷移簡單完整的解決方案,找到了一些思路
1.清除舊的數據,重建表
優點:簡單
缺點:數據丟失
2.在已有表的基礎上對表結構進行修改
優點:能夠保留數據
缺點:規則比較繁瑣,要建立一個數據庫的字段配置文件,然后讀取配置文件,執行SQL修改表結構、約束和主鍵等等,涉及到跨多個版本的數據庫升級就變得繁瑣并且麻煩了
3.創建臨時表,把舊的數據拷貝到臨時表,然后刪除舊的數據表并且把臨時表設置為數據表。
優點:能夠保留數據,支持表結構的修改,約束、主鍵的變更,實現起來比較簡單
缺點:實現的步驟比較多
綜合考慮,第三種方法是一個比較靠譜的方案。
主要步驟
根據這個思路,分析了一下數據庫升級了主要步驟大概如下:
使用到的SQL語句分析
這些操作都是和數據庫操作有關系的,所以問題的關鍵是對應步驟的SQL語句了,下面分析下用到的主要的SQL語句:
獲取數據庫中舊的表
SELECT * from sqlite_master WHERE type='table'
結果如下,可以看到有type | name | tbl_name | rootpage | sql 這些數據庫字段,我們只要用到name也就是數據庫名稱這個字段就行了
sqlite> SELECT * from sqlite_master WHERE type='table' ...> ;+-------+---------------+---------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+| type | name | tbl_name | rootpage | sql |+-------+---------------+---------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+| table | t_message_bak | t_message_bak | 2 | CREATE TABLE "t_message_bak" (messageID TEXT, messageType INTEGER, messageJsonContent TEXT, retriveTimeString INTEGER, postTimeString INTEGER, readState INTEGER, PRIMARY KEY(messageID)) || table | t_message | t_message | 4 | CREATE TABLE t_message ( messageID TEXT, messageType INTEGER, messageJsonContent TEXT, retriveTimeString INTEGER, postTimeString INTEGER, readState INTEGER, addColumn INTEGER, PRIMARY KEY(messageID)) |+-------+---------------+---------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+2 行于數據集 (0.03 秒)
修改表名,添加后綴“_bak”,把舊的表當做備份表
-- 把t_message表修改為t_message_bak表 ALTER TABLE t_message RENAME TO t_message_bak
獲取表字段信息
-- 獲取t_message_bak表的字段信息PRAGMA table_info('t_message_bak')
獲取到的表字段信息如下,可以看到有| cid | name | type | notnull | dflt_value | pk | 這些數據庫字段,我們只要用到name也就是字段名稱這個字段就行了
sqlite> PRAGMA table_info('t_message_bak');+------+--------------------+---------+---------+------------+------+| cid | name | type | notnull | dflt_value | pk |+------+--------------------+---------+---------+------------+------+| 0 | messageID | TEXT | 0 | NULL | 1 || 1 | messageType | INTEGER | 0 | NULL | 0 || 2 | messageJsonContent | TEXT | 0 | NULL | 0 || 3 | retriveTimeString | INTEGER | 0 | NULL | 0 || 4 | postTimeString | INTEGER | 0 | NULL | 0 || 5 | readState | INTEGER | 0 | NULL | 0 |+------+--------------------+---------+---------+------------+------+6 行于數據集 (0.01 秒)
使用子查詢進行數據遷移處理
INSERT INTO t_message(messageID, messageType, messageJsonContent, retriveTimeString, postTimeString, readState) SELECT messageID, messageType, messageJsonContent, retriveTimeString, postTimeString, readState FROM t_message_bak
把t_message_bak表中的messageID, messageType, messageJsonContent, retriveTimeString, postTimeString, readState這些字段的值復制到t_message表中
代碼實現
接下來就到了代碼的實現步驟了
// 創建新的臨時表,把數據導入臨時表,然后用臨時表替換原表- (void)baseDBVersionControl { NSString * version_old = ValueOrEmpty(MMUserDefault.dbVersion); NSString * version_new = [NSString stringWithFormat:@"%@", DB_Version]; NSLog(@"dbVersionControl before: %@ after: %@",version_old,version_new); // 數據庫版本升級 if (version_old != nil && ![version_new isEqualToString:version_old]) { // 獲取數據庫中舊的表 NSArray* existsTables = [self sqliteExistsTables]; NSMutableArray* tmpExistsTables = [NSMutableArray array]; // 修改表名,添加后綴“_bak”,把舊的表當做備份表 for (NSString* tablename in existsTables) { [tmpExistsTables addObject:[NSString stringWithFormat:@"%@_bak", tablename]]; [self.databaseQueue inDatabase:^(FMDatabase *db) { NSString* sql = [NSString stringWithFormat:@"ALTER TABLE %@ RENAME TO %@_bak", tablename, tablename]; [db executeUpdate:sql]; }]; } existsTables = tmpExistsTables; // 創建新的表 [self initTables]; // 獲取新創建的表 NSArray* newAddedTables = [self sqliteNewAddedTables]; // 遍歷舊的表和新表,對比取出需要遷移的表的字段 NSDictionary* migrationInfos = [self generateMigrationInfosWithOldTables:existsTables newTables:newAddedTables]; // 數據遷移處理 [migrationInfos enumerateKeysAndObjectsUsingBlock:^(NSString* newTableName, NSArray* publicColumns, BOOL * _Nonnull stop) { NSMutableString* colunmsString = [NSMutableString new]; for (int i = 0; i<publicColumns.count; i++) { [colunmsString appendString:publicColumns[i]]; if (i != publicColumns.count-1) { [colunmsString appendString:@", "]; } } NSMutableString* sql = [NSMutableString new]; [sql appendString:@"INSERT INTO "]; [sql appendString:newTableName]; [sql appendString:@"("]; [sql appendString:colunmsString]; [sql appendString:@")"]; [sql appendString:@" SELECT "]; [sql appendString:colunmsString]; [sql appendString:@" FROM "]; [sql appendFormat:@"%@_bak", newTableName]; [self.databaseQueue inDatabase:^(FMDatabase *db) { [db executeUpdate:sql]; }]; }]; // 刪除備份表 [self.databaseQueue inDatabase:^(FMDatabase *db) { [db beginTransaction]; for (NSString* oldTableName in existsTables) { NSString* sql = [NSString stringWithFormat:@"DROP TABLE IF EXISTS %@", oldTableName]; [db executeUpdate:sql]; } [db commit]; }]; MMUserDefault.dbVersion = version_new; } else { MMUserDefault.dbVersion = version_new; }}- (NSDictionary*)generateMigrationInfosWithOldTables:(NSArray*)oldTables newTables:(NSArray*)newTables { NSMutableDictionary<NSString*, NSArray* >* migrationInfos = [NSMutableDictionary dictionary]; for (NSString* newTableName in newTables) { NSString* oldTableName = [NSString stringWithFormat:@"%@_bak", newTableName]; if ([oldTables containsObject:oldTableName]) { // 獲取表數據庫字段信息 NSArray* oldTableColumns = [self sqliteTableColumnsWithTableName:oldTableName]; NSArray* newTableColumns = [self sqliteTableColumnsWithTableName:newTableName]; NSArray* publicColumns = [self publicColumnsWithOldTableColumns:oldTableColumns newTableColumns:newTableColumns]; if (publicColumns.count > 0) { [migrationInfos setObject:publicColumns forKey:newTableName]; } } } return migrationInfos;}- (NSArray*)publicColumnsWithOldTableColumns:(NSArray*)oldTableColumns newTableColumns:(NSArray*)newTableColumns { NSMutableArray* publicColumns = [NSMutableArray array]; for (NSString* oldTableColumn in oldTableColumns) { if ([newTableColumns containsObject:oldTableColumn]) { [publicColumns addObject:oldTableColumn]; } } return publicColumns;}- (NSArray*)sqliteTableColumnsWithTableName:(NSString*)tableName { __block NSMutableArray<NSString*>* tableColumes = [NSMutableArray array]; [self.databaseQueue inDatabase:^(FMDatabase *db) { NSString* sql = [NSString stringWithFormat:@"PRAGMA table_info('%@')", tableName]; FMResultSet *rs = [db executeQuery:sql]; while ([rs next]) { NSString* columnName = [rs stringForColumn:@"name"]; [tableColumes addObject:columnName]; } }]; return tableColumes;}- (NSArray*)sqliteExistsTables { __block NSMutableArray<NSString*>* existsTables = [NSMutableArray array]; [self.databaseQueue inDatabase:^(FMDatabase *db) { NSString* sql = @"SELECT * from sqlite_master WHERE type='table'"; FMResultSet *rs = [db executeQuery:sql]; while ([rs next]) { NSString* tablename = [rs stringForColumn:@"name"]; [existsTables addObject:tablename]; } }]; return existsTables;}- (NSArray*)sqliteNewAddedTables { __block NSMutableArray<NSString*>* newAddedTables = [NSMutableArray array]; [self.databaseQueue inDatabase:^(FMDatabase *db) { NSString* sql = @"SELECT * from sqlite_master WHERE type='table' AND name NOT LIKE '%_bak'"; FMResultSet *rs = [db executeQuery:sql]; while ([rs next]) { NSString* tablename = [rs stringForColumn:@"name"]; [newAddedTables addObject:tablename]; } }]; return newAddedTables;}
問題
sqlite 刪除表文件的大小不變的問題
如有疑問請留言或者到本站社區交流討論,感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
新聞熱點
疑難解答