我正在为我的iPhone使用sqlite,并且我预计数据库架构可能随时间而改变。什么是陷阱,命名惯例和需要注意的事情,以便每次成功进行迁移?适用于Sqlite的应用程序内数据库迁移的最佳做法
例如,我曾想过为数据库名称附加一个版本(例如Database_v1)。
我正在为我的iPhone使用sqlite,并且我预计数据库架构可能随时间而改变。什么是陷阱,命名惯例和需要注意的事情,以便每次成功进行迁移?适用于Sqlite的应用程序内数据库迁移的最佳做法
例如,我曾想过为数据库名称附加一个版本(例如Database_v1)。
我认为,需要定期更新SQLite数据库和旧数据库迁移到新架构和这里的应用程序是我做的:
用于跟踪数据库的版本,我用的是内置的用户版本的变量sqlite提供了(sqlite对这个变量没有作用,你可以随意使用它)。它从0开始,你可以获取/设置有以下sqlite的语句这个变量:
> PRAGMA user_version;
> PRAGMA user_version = 1;
当应用程序启动时,我检查当前用户的版本,应用需要把模式的任何改变到目前为止,然后更新用户版本。我将更新包装在一个事务中,以便如果出现任何错误,则不会提交更改。
为了进行模式更改,sqlite支持某些操作(重命名表或添加列)的“ALTER TABLE”语法。这是现场更新现有表格的简单方法。请参阅此处的文档:http://www.sqlite.org/lang_altertable.html。要删除“ALTER TABLE”语法不支持的列或其他更改,我创建一个新表,将日期迁移到该表中,删除旧表并将新表重命名为原始名称。
如果您更改数据库模式以及所有以锁步方式使用它的代码(在嵌入式和位于手机的应用程序中可能会出现这种情况),问题实际上已得到很好的控制(与模式迁移的噩梦毫无媲美在可能为数百个应用程序提供服务的企业数据库上 - 并非全部都在DBA的控制之下;-)。
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];
}
最好的解决办法是IMO构建一个SQLite的升级框架。我有同样的问题(在C#世界),我建立了自己的这样的框架。你可以阅读关于它here。它的工作原理非常完美,使我的(以前噩梦般)升级工作在我身上花费最少的努力。
尽管库在C#中实现,但在那里提出的想法应该也适用于您的案例。
一些技巧...
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];
}
}
让我与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];
}];
}
}
1
。基于SQL的迁移的列表,其中每个迁移看起来像这样创建/migrations
文件夹:
/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实现的一个例子:SQLite Client for Node.js Apps
我想有相同的逻辑,但由于某些原因,当我执行“编译user_version =?”以编程方式,它失败......任何想法? – Unicorn 2011-05-17 16:54:43
编译指示设置不支持参数,您必须提供实际值:“pragma user_version = 1”。 – csgero 2011-12-21 09:42:15
我有一个问题。假设您的初始版本为1.并且当前版本为5.版本2,3,4中有一些更新。最终用户只下载了您的版本1,现在升级到版本5.您应该怎么做? – 2014-04-10 04:09:08