Sqlite应用程序内数据库迁移的最佳实践

83qze16e  于 2023-01-09  发布在  SQLite
关注(0)|答案(9)|浏览(260)

我在我的iPhone上使用sqlite,我预计数据库模式可能会随着时间的推移而改变。每次成功迁移都需要注意什么?
例如,我曾考虑在数据库名称后面附加一个版本(例如Database_v1)。

xqkwcwgp

xqkwcwgp1#

我维护了一个应用程序,它需要定期更新sqlite数据库并将旧数据库迁移到新模式,下面是我所做的:
为了跟踪数据库版本,我使用了sqlite提供的内置user-version变量(sqlite对这个变量不做任何处理,你可以随意使用它),它从0开始,你可以用下面的sqlite语句来获取/设置这个变量:

> PRAGMA user_version;  
> PRAGMA user_version = 1;

当应用程序启动时,我检查当前的用户版本,应用使模式最新所需的任何更改,然后更新用户版本。我将更新 Package 在一个事务中,这样如果出现任何错误,更改都不会提交。
对于模式更改,sqlite支持使用“ALTER TABLE”语法进行某些操作(重命名表或添加列)。这是就地更新现有表的简单方法。请参阅此处的文档:为了删除“ALTER TABLE”语法不支持的列或其他更改,我创建了一个新表,将日期迁移到其中,删除旧表,并将新表重命名为原来的名称。

bvpmtnay

bvpmtnay2#

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];
}
eulz3vhy

eulz3vhy3#

让我分享一些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];
        }];
    }
}
a64a0gku

a64a0gku4#

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 Category;

/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

pgccezyw

pgccezyw5#

IMO最好的解决方案是建立一个SQLite升级框架。我也有同样的问题(在C#世界),我建立了自己的这样的框架。你可以阅读here。它完美地工作,使我(以前噩梦般的)升级工作在我这边最小的努力。
尽管这个库是用C#实现的,但是这里介绍的思想在您的情况下也应该可以很好地工作。

iklwldmw

iklwldmw6#

一些提示...
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];
    }
}
f45qwnt8

f45qwnt87#

对于.net,您可以使用lib:
EntityFrameworkCore.Sqlite.Migrations
它很简单,所以对于任何其他平台,您都可以轻松实现与lib中相同的行为。

hfwmuf9z

hfwmuf9z8#

如果您更改数据库模式和所有使用该模式的代码,就像嵌入式应用程序和手机应用程序中的情况一样,问题实际上可以得到很好的控制(没有什么比得上企业数据库上的模式迁移噩梦,该数据库可能服务于数百个应用程序--也不是所有应用程序都在DBA的控制之下; -).

iyr7buue

iyr7buue9#

在我的文章Simple declarative schema migration for SQLite中,我们通过创建一个原始的内存数据库,并通过查询“sqlite_schema”表将该模式与当前数据库进行比较来自动地计算模式更改,然后我们按照SQLite文档中的12步过程来安全地修改表。
您可以按照自己的喜好定义模式(ORM或纯SQL“CREATE TABLE”语句等),只要您可以使用它来创建新的内存数据库。这意味着您只需在一个位置维护模式,并且在应用程序启动时自动应用更改。
当然也有局限性--特别是它不能处理数据迁移,只能处理模式迁移;并且新列必须允许空值或指定默认值,但总的来说,使用它是一件愉快的事情。

相关问题