IOS 數(shù)據(jù)庫(kù)升級(jí)數(shù)據(jù)遷移的實(shí)例詳解
IOS 數(shù)據(jù)庫(kù)升級(jí)數(shù)據(jù)遷移的實(shí)例詳解
概要:
很久以前就遇到過(guò)數(shù)據(jù)庫(kù)版本升級(jí)的引用場(chǎng)景,當(dāng)時(shí)的做法是簡(jiǎn)單的刪除舊的數(shù)據(jù)庫(kù)文件,重建數(shù)據(jù)庫(kù)和表結(jié)構(gòu),這種暴力升級(jí)的方式會(huì)導(dǎo)致舊的數(shù)據(jù)的丟失,現(xiàn)在看來(lái)這并不不是一個(gè)優(yōu)雅的解決方案,現(xiàn)在一個(gè)新的項(xiàng)目中又使用到了數(shù)據(jù)庫(kù),我不得不重新考慮這個(gè)問(wèn)題,我希望用一種比較優(yōu)雅的方式去解決這個(gè)問(wèn)題,以后我們還會(huì)遇到類似的場(chǎng)景,我們都想做的更好不是嗎?
理想的情況是:數(shù)據(jù)庫(kù)升級(jí),表結(jié)構(gòu)、主鍵和約束有變化,新的表結(jié)構(gòu)建立之后會(huì)自動(dòng)的從舊的表檢索數(shù)據(jù),相同的字段進(jìn)行映射遷移數(shù)據(jù),而絕大多數(shù)的業(yè)務(wù)場(chǎng)景下的數(shù)據(jù)庫(kù)版本升級(jí)是只涉及到字段的增減、修改主鍵約束,所以下面要實(shí)現(xiàn)的方案也是從最基本的、最常用的業(yè)務(wù)場(chǎng)景去做一個(gè)實(shí)現(xiàn),至于更加復(fù)雜的場(chǎng)景,可以在此基礎(chǔ)上進(jìn)行擴(kuò)展,達(dá)到符合自己的預(yù)期的。
選型定型
網(wǎng)上搜索了下,并沒(méi)有數(shù)據(jù)庫(kù)升級(jí)數(shù)據(jù)遷移簡(jiǎn)單完整的解決方案,找到了一些思路
1.清除舊的數(shù)據(jù),重建表
優(yōu)點(diǎn):簡(jiǎn)單
缺點(diǎn):數(shù)據(jù)丟失
2.在已有表的基礎(chǔ)上對(duì)表結(jié)構(gòu)進(jìn)行修改
優(yōu)點(diǎn):能夠保留數(shù)據(jù)
缺點(diǎn):規(guī)則比較繁瑣,要建立一個(gè)數(shù)據(jù)庫(kù)的字段配置文件,然后讀取配置文件,執(zhí)行SQL修改表結(jié)構(gòu)、約束和主鍵等等,涉及到跨多個(gè)版本的數(shù)據(jù)庫(kù)升級(jí)就變得繁瑣并且麻煩了
3.創(chuàng)建臨時(shí)表,把舊的數(shù)據(jù)拷貝到臨時(shí)表,然后刪除舊的數(shù)據(jù)表并且把臨時(shí)表設(shè)置為數(shù)據(jù)表。
優(yōu)點(diǎn):能夠保留數(shù)據(jù),支持表結(jié)構(gòu)的修改,約束、主鍵的變更,實(shí)現(xiàn)起來(lái)比較簡(jiǎn)單
缺點(diǎn):實(shí)現(xiàn)的步驟比較多
綜合考慮,第三種方法是一個(gè)比較靠譜的方案。
主要步驟
根據(jù)這個(gè)思路,分析了一下數(shù)據(jù)庫(kù)升級(jí)了主要步驟大概如下:
- 獲取數(shù)據(jù)庫(kù)中舊的表
- 修改表名,添加后綴“_bak”,把舊的表當(dāng)做備份表
- 創(chuàng)建新的表
- 獲取新創(chuàng)建的表
- 遍歷舊的表和新表,對(duì)比取出需要遷移的表的字段
- 數(shù)據(jù)遷移處理
- 刪除備份表
使用到的SQL語(yǔ)句分析
這些操作都是和數(shù)據(jù)庫(kù)操作有關(guān)系的,所以問(wèn)題的關(guān)鍵是對(duì)應(yīng)步驟的SQL語(yǔ)句了,下面分析下用到的主要的SQL語(yǔ)句:
獲取數(shù)據(jù)庫(kù)中舊的表
SELECT * from sqlite_master WHERE type='table'
結(jié)果如下,可以看到有type | name | tbl_name | rootpage | sql 這些數(shù)據(jù)庫(kù)字段,我們只要用到name也就是數(shù)據(jù)庫(kù)名稱這個(gè)字段就行了
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 行于數(shù)據(jù)集 (0.03 秒)
修改表名,添加后綴“_bak”,把舊的表當(dāng)做備份表
-- 把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 | 這些數(shù)據(jù)庫(kù)字段,我們只要用到name也就是字段名稱這個(gè)字段就行了
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 行于數(shù)據(jù)集 (0.01 秒)
使用子查詢進(jìn)行數(shù)據(jù)遷移處理
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這些字段的值復(fù)制到t_message表中
代碼實(shí)現(xiàn)
接下來(lái)就到了代碼的實(shí)現(xiàn)步驟了
// 創(chuàng)建新的臨時(shí)表,把數(shù)據(jù)導(dǎo)入臨時(shí)表,然后用臨時(shí)表替換原表
- (void)baseDBVersionControl {
NSString * version_old = ValueOrEmpty(MMUserDefault.dbVersion);
NSString * version_new = [NSString stringWithFormat:@"%@", DB_Version];
NSLog(@"dbVersionControl before: %@ after: %@",version_old,version_new);
// 數(shù)據(jù)庫(kù)版本升級(jí)
if (version_old != nil && ![version_new isEqualToString:version_old]) {
// 獲取數(shù)據(jù)庫(kù)中舊的表
NSArray* existsTables = [self sqliteExistsTables];
NSMutableArray* tmpExistsTables = [NSMutableArray array];
// 修改表名,添加后綴“_bak”,把舊的表當(dāng)做備份表
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;
// 創(chuàng)建新的表
[self initTables];
// 獲取新創(chuàng)建的表
NSArray* newAddedTables = [self sqliteNewAddedTables];
// 遍歷舊的表和新表,對(duì)比取出需要遷移的表的字段
NSDictionary* migrationInfos = [self generateMigrationInfosWithOldTables:existsTables newTables:newAddedTables];
// 數(shù)據(jù)遷移處理
[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]) {
// 獲取表數(shù)據(jù)庫(kù)字段信息
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;
}
問(wèn)題
sqlite 刪除表文件的大小不變的問(wèn)題
如有疑問(wèn)請(qǐng)留言或者到本站社區(qū)交流討論,感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
- VUE中使用HTTP庫(kù)Axios方法詳解
- iOS系統(tǒng)的底層通知框架庫(kù)示例詳解
- 淺談強(qiáng)大易用支持URL Rewrite的iOS路由庫(kù)FFRouter
- iOS開(kāi)發(fā)筆記之鍵盤、靜態(tài)庫(kù)、動(dòng)畫(huà)和Crash定位
- 基于iOS Realm數(shù)據(jù)庫(kù)的使用實(shí)例詳解
- iOS開(kāi)發(fā)中如何優(yōu)雅的調(diào)試數(shù)據(jù)庫(kù)詳解
- iOS中.a和.framework靜態(tài)庫(kù)的創(chuàng)建與.bundle資源包的使用詳解
- IOS UIImagePickerController從拍照、圖庫(kù)、相冊(cè)獲取圖片
- iOS中FMDB數(shù)據(jù)庫(kù)之增刪改查使用實(shí)例
- ios動(dòng)態(tài)庫(kù)和靜態(tài)庫(kù)的區(qū)別
相關(guān)文章
mysql之key和index的區(qū)別及創(chuàng)建刪除索引方式
這篇文章主要介紹了mysql之key和index的區(qū)別及創(chuàng)建刪除索引方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12
一文學(xué)會(huì)Mysql數(shù)據(jù)庫(kù)備份與恢復(fù)
數(shù)據(jù)庫(kù)備份是在數(shù)據(jù)丟失的情況下能及時(shí)恢復(fù)重要數(shù)據(jù),防止數(shù)據(jù)丟失的一種重要手段,下面這篇文章主要給大家介紹了關(guān)于Mysql數(shù)據(jù)庫(kù)備份與恢復(fù)的相關(guān)資料,需要的朋友可以參考下2022-05-05
Mysql數(shù)據(jù)類型與CRUD操作詳細(xì)講解
這篇文章主要介紹了Mysql數(shù)據(jù)類型與CRUD操作,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2022-10-10
MySQL數(shù)據(jù)庫(kù)基礎(chǔ)命令大全(收藏)
今天小編給大家整理一下mysql數(shù)據(jù)庫(kù)的基礎(chǔ)命令,特此分享到腳本之家平臺(tái),供大家參考下2016-12-12
詳解如何對(duì)MySQL數(shù)據(jù)庫(kù)進(jìn)行授權(quán)管理
MySQL數(shù)據(jù)授權(quán)是指數(shù)據(jù)庫(kù)管理員通過(guò)設(shè)置權(quán)限,控制用戶對(duì)數(shù)據(jù)庫(kù)中的數(shù)據(jù)的訪問(wèn)和操作能力,在MySQL中,每個(gè)用戶賬戶都有特定的權(quán)限,本文給大家介紹了如何對(duì)MySQL數(shù)據(jù)庫(kù)進(jìn)行授權(quán)管理,需要的朋友可以參考下2024-11-11
mysql分表分庫(kù)的應(yīng)用場(chǎng)景和設(shè)計(jì)方式
為大家講述一下在mysql在什么到時(shí)候需要進(jìn)行分表分庫(kù),以及現(xiàn)實(shí)的設(shè)計(jì)方式。2017-11-11
mysql innodb的監(jiān)控(系統(tǒng)層,數(shù)據(jù)庫(kù)層)
這篇文章主要介紹了mysql innodb的監(jiān)控(系統(tǒng)層,數(shù)據(jù)庫(kù)層)的相關(guān)資料,需要的朋友可以參考下2017-04-04
MySQL 8.0 Online DDL快速加列的相關(guān)總結(jié)
在實(shí)際的MySQL運(yùn)維過(guò)程中,我們經(jīng)常會(huì)遇到業(yè)務(wù)需要給某張表添加字段的情況,本文將介紹幾種加字段的方法,感興趣的朋友可以參考下2021-06-06
xampp中修改mysql默認(rèn)空密碼(root密碼)的方法分享
以前開(kāi)發(fā)我一直都是用的phpnow做php開(kāi)發(fā)環(huán)境,phpnow的特點(diǎn)就是一鍵安裝,安裝的時(shí)候會(huì)要求用戶輸入mysql的root密碼。今天由于客戶機(jī)器使用的xampp作為開(kāi)發(fā)環(huán)境,所以碰到了修改mysql默認(rèn)空密碼的問(wèn)題2014-04-04

