Pertanyaan Praktik terbaik untuk migrasi database dalam aplikasi untuk Sqlite


Saya menggunakan sqlite untuk iphone saya dan saya mengantisipasi skema database mungkin berubah seiring waktu. Apa yang dimaksud dengan gotcha, konvensi penamaan dan hal-hal yang harus diperhatikan untuk melakukan migrasi yang sukses setiap saat?

Sebagai contoh, saya berpikir untuk menambahkan versi ke nama database (misalnya, Database_v1).


75
2018-06-13 00:02


asal


Jawaban:


Saya memelihara aplikasi yang secara berkala perlu memperbarui database sqlite dan memigrasikan database lama ke skema baru dan inilah yang saya lakukan:

Untuk melacak versi database, saya menggunakan variabel versi pengguna yang disediakan oleh sqlite (sqlite tidak melakukan apa-apa dengan variabel ini, Anda bebas menggunakannya semau Anda). Ini dimulai pada 0, dan Anda bisa mendapatkan / mengatur variabel ini dengan pernyataan sqlite berikut:

> PRAGMA user_version;  
> PRAGMA user_version = 1;

Saat aplikasi dimulai, saya memeriksa versi pengguna saat ini, menerapkan perubahan apa pun yang diperlukan untuk memperbarui skema, dan kemudian memperbarui versi pengguna. Saya membungkus pembaruan dalam transaksi sehingga jika ada yang salah, perubahan tidak dilakukan.

Untuk membuat perubahan skema, sqlite mendukung sintaks "ALTER TABLE" untuk operasi tertentu (mengganti nama tabel atau menambahkan kolom). Ini adalah cara mudah untuk memperbarui tabel yang sudah ada di tempat. Lihat dokumentasi di sini: http://www.sqlite.org/lang_altertable.html. Untuk menghapus kolom atau perubahan lain yang tidak didukung oleh sintaks "ALTER TABLE", saya membuat tabel baru, memigrasikan tanggal ke dalamnya, meletakkan tabel lama, dan mengganti nama tabel baru ke nama aslinya.


89
2018-06-15 22:04



Jawaban dari Just Curious adalah dead-on (Anda mendapatkan poin saya!), Dan itu yang kami gunakan untuk melacak versi skema database yang saat ini ada di dalam aplikasi.

Untuk menjalankan melalui migrasi yang perlu dilakukan untuk mendapatkan user_version yang sesuai dengan versi skema yang diharapkan dari aplikasi, kami menggunakan pernyataan switch. Berikut contoh cut-up dari apa yang terlihat seperti ini di aplikasi kami Mengupas:

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

28
2018-06-29 14:15



Biarkan saya berbagi beberapa kode migrasi dengan FMDB dan MBProgressHUD.

Beginilah cara Anda membaca dan menulis nomor versi skema (ini mungkin bagian dari kelas model, dalam kasus saya itu adalah kelas tunggal yang disebut 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);
}

Ini [self database] metode yang malas membuka 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;
}

Dan berikut adalah metode migrasi yang disebut dari pengontrol tampilan:

- (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]);
}

Dan inilah kode pengontrol tampilan akar yang memanggil migrasi, menggunakan MBProgressHUD untuk menampilkan bezel kemajuan:

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

18
2017-09-17 23:24



Solusi terbaik IMO adalah membangun kerangka kerja peningkatan SQLite. Saya memiliki masalah yang sama (di dunia C #) dan saya membangun kerangka kerja saya sendiri. Anda bisa membaca tentang itu sini. Ini bekerja dengan sempurna dan membuat saya (yang sebelumnya mimpi buruk) bekerja dengan sedikit usaha di pihak saya.

Meskipun perpustakaan diimplementasikan dalam C #, ide yang disajikan di sana harus bekerja dengan baik dalam kasus Anda juga.


4
2017-07-30 07:30



Jika Anda mengubah skema basis data dan semua kode yang menggunakannya secara berurutan, seperti yang mungkin terjadi pada aplikasi tertanam dan aplikasi yang ditempatkan di ponsel, masalahnya sebenarnya sudah terkendali (tidak ada yang sebanding dengan mimpi buruk yang migrasi skema pada DB perusahaan yang mungkin melayani ratusan aplikasi - tidak semua di bawah kendali DBA ;-).


1
2018-06-13 00:08



Beberapa tips ...

1) Saya sarankan untuk memasukkan semua kode untuk memigrasi database Anda ke NSOperation dan menjalankannya di thread latar belakang. Anda dapat menampilkan UIAlertView khusus dengan spinner saat database sedang dimigrasikan.

2) Pastikan Anda menyalin database Anda dari bundel ke dalam dokumen aplikasi dan menggunakannya dari lokasi itu, jika tidak Anda hanya akan menimpa seluruh basis data dengan setiap pembaruan aplikasi, dan kemudian memigrasi database kosong yang baru.

3) FMDB sangat bagus, tetapi metode executeQuery tidak dapat melakukan query PRAGMA untuk beberapa alasan. Anda harus menulis metode Anda sendiri yang menggunakan sqlite3 secara langsung jika Anda ingin memeriksa versi skema menggunakan PRAGMA user_version.

4) Struktur kode ini akan memastikan bahwa pembaruan Anda dijalankan secara berurutan, dan semua pembaruan dijalankan, tidak peduli berapa lama pengguna pergi di antara pembaruan aplikasi. Ini bisa direfaktor lebih lanjut, tetapi ini adalah cara yang sangat sederhana untuk melihatnya. Metode ini dapat dengan aman dijalankan setiap kali data tunggal Anda dipakai, dan hanya biaya satu permintaan db kecil yang hanya terjadi sekali per sesi jika Anda menyiapkan data Anda sendiri dengan benar.

- (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
2017-09-28 19:54



1. Membuat /migrations folder dengan daftar migrasi berbasis SQL, di mana setiap migrasi terlihat seperti ini:

/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. Buat tabel db yang berisi daftar migrasi yang diterapkan, misalnya:

CREATE TABLE Migration (name TEXT);

3. Perbarui logika bootstrap aplikasi sehingga sebelum dimulai, ia mengambil daftar migrasi dari /migrations folder dan menjalankan migrasi yang belum diterapkan.

Berikut ini contoh yang diterapkan dengan JavaScript: Klien SQLite untuk Aplikasi Node.js


1
2018-05-20 18:45