Answers:
我维护一个应用程序,该应用程序需要定期更新sqlite数据库并将旧数据库迁移到新架构,这是我的工作:
为了跟踪数据库版本,我使用了sqlite提供的内置用户版本变量(sqlite对此变量不执行任何操作,您可以随意使用它)。它从0开始,您可以使用以下sqlite语句获取/设置此变量:
> PRAGMA user_version;
> PRAGMA user_version = 1;
应用启动时,我检查当前的用户版本,应用使架构更新所需的任何更改,然后更新用户版本。我将更新打包在事务中,以便如果出现任何问题,则不会提交更改。
为了进行模式更改,sqlite支持对某些操作(重命名表或添加列)使用“ ALTER TABLE”语法。这是就地更新现有表的简便方法。请参阅此处的文档:http : //www.sqlite.org/lang_altertable.html。要删除“ ALTER TABLE”语法不支持的列或其他更改,我创建一个新表,将日期迁移到其中,删除旧表,然后将新表重命名为原始名称。
Just Curious的答案是死定的(您明白我的意思!),这就是我们用来跟踪应用程序中当前数据库模式版本的方法。
为了进行迁移,以使user_version与应用程序的预期模式版本匹配,我们使用switch语句。这是我们的应用程序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];
}
toVersion
在代码中使用的位置?当您使用的是版本0时,如何处理,之后还有另外两个版本。这意味着您必须从0迁移到1,然后从1迁移到2。如何处理?
break
语句switch
,因此所有后续迁移也将发生。
让我与FMDB和MBProgressHUD共享一些迁移代码。
这是读取和写入模式版本号的方式(这大概是模型类的一部分,在我的情况下,这是一个称为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);
}
这[self database]
是延迟打开数据库的方法:
- (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;
}
这是从视图控制器调用的迁移方法:
- (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]);
}
这是调用迁移的根视图控制器代码,使用MBProgressHUD显示进度挡板:
- (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];
}];
}
}
schema_version
通常人们也不会处理杂物。
IMO最好的解决方案是建立一个SQLite升级框架。我在C#世界中遇到了同样的问题,并且建立了自己的框架。你可以阅读一下这里。它可以完美运行,并且使我(以前是噩梦般的)升级在我这方面的工作量最小。
尽管该库是用C#实现的,但在此情况下提出的想法也应能很好地工作。
1
。创建/migrations
带有基于SQL的迁移列表的文件夹,其中每个迁移看起来像这样:
/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
。创建包含已应用迁移列表的数据库表,例如:
CREATE TABLE Migration (name TEXT);
3
。更新应用程序引导程序逻辑,以便在它启动之前,它会从/migrations
文件夹中获取迁移列表并运行尚未应用的迁移。
这是使用JavaScript实现的示例:适用于Node.js应用程序的SQLite客户端
一些技巧...
1)我建议放置所有代码以将数据库迁移到NSOperation并在后台线程中运行它。在迁移数据库时,可以使用微调器显示自定义UIAlertView。
2)确保将捆绑包中的数据库复制到应用程序的文档中并从该位置使用它,否则,每次应用程序更新时,您都将覆盖整个数据库,然后迁移新的空数据库。
3)FMDB很棒,但是由于某些原因,它的executeQuery方法无法执行PRAGMA查询。如果要使用PRAGMA user_version检查架构版本,则需要编写自己的直接使用sqlite3的方法。
4)此代码结构将确保您的更新按顺序执行,并且无论用户两次更新应用之间有多长时间,所有更新都将执行。可以对其进行进一步的重构,但这是一种非常简单的查看方式。每次实例化数据单例时,都可以安全地运行此方法,并且只花费一个微小的数据库查询,如果正确设置数据单例,则每个会话仅发生一次。
- (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];
}
}