【发布时间】:2010-11-02 15:19:14
【问题描述】:
我正在为我的 iphone 使用 sqlite,我预计数据库架构可能会随着时间而改变。每次成功迁移时需要注意哪些问题、命名约定和注意事项?
例如,我曾考虑将版本附加到数据库名称(例如 Database_v1)。
【问题讨论】:
我正在为我的 iphone 使用 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”语法不支持的列或其他更改,我创建了一个新表,将日期迁移到其中,删除旧表,并将新表重命名为原始名称。
【讨论】:
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。您如何处理?
switch 中没有break 语句,因此所有后续迁移也会发生。
让我分享一些 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 pragma 通常也不是人们处理的东西。
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。创建包含应用迁移列表的 db 表,例如:
CREATE TABLE Migration (name TEXT);
3。更新应用程序引导逻辑,以便在启动之前从/migrations 文件夹中获取迁移列表并运行尚未应用的迁移。
这是一个使用 JavaScript 实现的示例:SQLite Client for Node.js Apps
【讨论】:
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];
}
}
【讨论】:
【讨论】:
如果您以同步方式更改数据库架构和所有使用它的代码,嵌入式和手机定位的应用程序很可能就是这种情况,那么问题实际上已经得到了很好的控制(没有什么能比得上架构迁移的噩梦了)一个可能为数百个应用程序提供服务的企业数据库——也不是所有应用程序都在 DBA 的控制之下;-)。
【讨论】: