Лучшие практики для миграции базы данных в приложении для Sqlite - PullRequest
84 голосов
/ 13 июня 2009

Я использую sqlite для своего iphone, и я ожидаю, что схема базы данных может со временем измениться. На какие ошибки, соглашения об именах и на что нужно обращать внимание при успешной миграции каждый раз?

Например, я думал о добавлении версии к имени базы данных (например, Database_v1).

Ответы [ 8 ]

100 голосов
/ 16 июня 2009

Я поддерживаю приложение, которому периодически требуется обновлять базу данных sqlite и переносить старые базы данных в новую схему, и вот что я делаю:

Для отслеживания версии базы данных я использую встроенную переменную user-version, которую предоставляет sqlite (sqlite ничего не делает с этой переменной, вы можете использовать ее, как вам угодно). Он начинается с 0, и вы можете получить / установить эту переменную с помощью следующих операторов sqlite:

> PRAGMA user_version;  
> PRAGMA user_version = 1;

Когда приложение запускается, я проверяю текущую версию пользователя, применяю любые изменения, необходимые для обновления схемы, а затем обновляю версию пользователя. Я обертываю обновления в транзакции, чтобы, если что-то пошло не так, изменения не были зафиксированы.

Для внесения изменений в схему sqlite поддерживает синтаксис «ALTER TABLE» для определенных операций (переименование таблицы или добавление столбца). Это простой способ обновить существующие таблицы на месте. См. Документацию здесь: http://www.sqlite.org/lang_altertable.html. Для удаления столбцов или других изменений, которые не поддерживаются синтаксисом «ALTER TABLE», я создаю новую таблицу, переносу в нее дату, удаляю старую таблицу и переименую новую таблица с оригинальным названием.

28 голосов
/ 29 июня 2009

Ответ от 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];
}
19 голосов
/ 18 сентября 2012

Позвольте мне поделиться кодом миграции с 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];
        }];
    }
}
4 голосов
/ 30 июля 2009

Лучшим решением IMO является создание инфраструктуры обновления SQLite. У меня была такая же проблема (в мире C #), и я создал свой собственный такой фреймворк. Вы можете прочитать об этом здесь . Он отлично работает и делает мои (ранее кошмарные) обновления с минимальными усилиями на моей стороне.

Несмотря на то, что библиотека реализована на C #, идеи, представленные там, должны работать и в вашем случае.

2 голосов
/ 20 мая 2016

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: Клиент SQLite для приложений Node.js

2 голосов
/ 28 сентября 2011

Несколько советов ...

1) Я рекомендую поместить весь код для переноса вашей базы данных в NSOperation и запустить его в фоновом потоке. Вы можете показать пользовательский UIAlertView с помощью счетчика во время переноса базы данных.

2) Убедитесь, что вы копируете свою базу данных из комплекта в документы приложения и используете ее из этого места, в противном случае вы просто перезапишете всю базу данных при каждом обновлении приложения, а затем перенесете новую пустую базу данных.

3) FMDB великолепен, но его метод executeQuery по какой-то причине не может выполнять запросы PRAGMA. Вам нужно написать свой собственный метод, который напрямую использует sqlite3, если вы хотите проверить версию схемы с помощью PRAGMA user_version.

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];
    }
}
1 голос
/ 13 июня 2009

Если вы изменяете схему базы данных и весь код, использующий ее, в режиме блокировки, как это может быть в случае встроенных приложений и приложений, расположенных на телефоне, проблема на самом деле хорошо контролируется (ничто не сравнимо с кошмарным переносом схемы корпоративная БД, которая может обслуживать сотни приложений - не все тоже под контролем БД; -).

0 голосов
/ 10 января 2019

Для .net вы можете использовать lib:

EntityFrameworkCore.Sqlite.Migrations

Это просто, поэтому для любой другой платформы вы можете легко реализовать то же поведение, что и в lib.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...