Sqlite için uygulama içi veritabanı geçişine yönelik en iyi uygulamalar


97

İPhone'um için sqlite kullanıyorum ve veritabanı şemasının zamanla değişebileceğini tahmin ediyorum. Her seferinde başarılı bir geçiş yapmak için gerekenler, adlandırma kuralları ve dikkat edilmesi gerekenler nelerdir?

Örneğin, veritabanı adına bir sürüm eklemeyi düşündüm (örn. Database_v1).

Yanıtlar:


116

Bir sqlite veritabanını periyodik olarak güncellemesi ve eski veritabanlarını yeni şemaya taşıması gereken bir uygulama kullanıyorum ve işte yaptığım şey:

Veritabanı versiyonunu takip etmek için sqlite'ın sağladığı dahili kullanıcı versiyonu değişkenini kullanıyorum (sqlite bu değişkenle hiçbir şey yapmaz, istediğiniz gibi kullanabilirsiniz) 0'dan başlar ve bu değişkeni aşağıdaki sqlite ifadeleri ile alabilir / ayarlayabilirsiniz:

> PRAGMA user_version;  
> PRAGMA user_version = 1;

Uygulama başladığında, mevcut kullanıcı sürümünü kontrol ediyorum, şemayı güncellemek için gereken değişiklikleri uyguluyorum ve ardından kullanıcı sürümünü güncelliyorum. Güncellemeleri bir işlemde paketliyorum, böylece bir şeyler ters giderse, değişiklikler yapılmaz.

Şema değişiklikleri yapmak için, sqlite belirli işlemler için "ALTER TABLE" sözdizimini destekler (tabloyu yeniden adlandırmak veya bir sütun eklemek). Bu, mevcut tabloları yerinde güncellemenin kolay bir yoludur. Buradaki belgelere bakın: http://www.sqlite.org/lang_altertable.html . "ALTER TABLE" sözdizimi tarafından desteklenmeyen sütunları veya diğer değişiklikleri silmek için, yeni bir tablo oluşturuyorum, içine tarihi geçiriyorum, eski tabloyu bırakıyorum ve yeni tabloyu orijinal adıyla yeniden adlandırıyorum.


2
Aynı mantığa sahip olmaya çalışıyorum, ancak bazı nedenlerden dolayı "pragma user_version =?" programlı olarak başarısız oluyor ... herhangi bir fikir?
Unicorn

7
pragma ayarları parametreleri desteklemez, gerçek değeri sağlamanız gerekir: "pragma user_version = 1".
csgero

2
Bir sorum var. Diyelim ki ilk sürüm 1 iseniz ve mevcut sürüm 5 ise. 2,3,4 sürümünde bazı güncellemeler var. Son kullanıcı yalnızca sizin sürüm 1'i indirdi ve şimdi sürüm 5'e yükseltin. Ne yapmalısınız?
Bagusflyer

6
Veritabanını birkaç adımda güncelleyin, sürüm 1'den sürüm 2'ye, ardından sürüm 2'den sürüm 3'e vb. Gitmek için gerekli değişiklikleri güncel olana kadar uygulayın. Bunu yapmanın kolay bir yolu, her "case" ifadesinin veritabanını bir sürümle güncellediği bir switch deyimine sahip olmaktır. Geçerli veritabanı sürümüne "geçiş yaparsınız" ve güncelleme tamamlanana kadar durum ifadeleri devam eder. Veritabanını her güncellediğinizde, yeni bir vaka ifadesi eklemeniz yeterlidir. Bunun ayrıntılı bir örneği için Billy Gray'in aşağıdaki cevabına bakın.
Rngbus

1
@KonstantinTarkus, belgelere göre, örneğin veritabanı sürümleri için değil, yardımcı programla application_iddosya biçimini tanımlamak için ekstra bir bittir file.
xaizek

30

Just Curious'un cevabı kesin değil (benim fikrimi anladınız!) Ve şu anda uygulamada bulunan veritabanı şemasının sürümünü izlemek için kullandığımız şey bu.

Uygulamanın beklenen şema sürümüyle eşleşen user_version elde etmek için gerçekleşmesi gereken geçişleri çalıştırmak için bir switch ifadesi kullanıyoruz. Strip uygulamamızda bunun nasıl göründüğüne dair bir örnek :

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

1
Peki, toVersionkodunuzun neresinde kullandığınızı görmedim mi? 0 sürümündeyken nasıl işlenir ve bundan sonra iki sürüm daha vardır. Bu, 0'dan 1'e ve 1'den 2'ye geçiş yapmanız gerektiği anlamına gelir. Bununla nasıl başa çıkıyorsunuz?
yapılandırma dosyasındaki

1
@confile breakiçinde ifade yok switch, bu nedenle sonraki tüm geçişler de gerçekleşecek.
mat

Şerit bağlantısı yok
Pedro Luz

20

FMDB ve MBProgressHUD ile bazı geçiş kodunu paylaşmama izin verin.

Şema sürüm numarasını şu şekilde okuyup yazarsınız (bu muhtemelen bir model sınıfının bir parçasıdır, benim durumumda Veritabanı adı verilen tek bir sınıftır):

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

İşte [self database]tembel veritabanını açar yöntemi:

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

Ve işte görünüm denetleyicisinden çağrılan taşıma yöntemleri:

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

Ve işte bir ilerleme çerçevesi görüntülemek için MBProgressHUD kullanarak geçişi başlatan kök görünüm denetleyici kodu:

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

Not: Kodun nasıl düzenlendiğinden tam olarak memnun değilim (açma ve geçişin tek bir işlemin parçası olmasını tercih ederim, tercihen uygulama temsilcisi tarafından çağrılır), ancak işe yarıyor ve yine de paylaşacağımı düşündüm .
Andrey Tarantsov

"User_version" döndürmek için neden "setDatabaseSchemaVersion" yöntemini kullanıyorsunuz? "user_version" ve "schema_version" bence iki farklı pragma.
Paul Brewczynski

@PaulBrewczynski Çünkü yaygın olarak kullanılan terimleri tercih ediyorum, SQLite terimlerini değil ve ayrıca ne olduğu ile (veritabanı şemamın sürümü) çağırıyorum. Bu durumda schema_versionSQLite'ye özgü terimler umurumda değil ve pragma normalde insanların da uğraştığı bir şey değil.
Andrey Tarantsov

Yazdınız: // FMDB bu sorguyu çalıştıramıyor çünkü FMDB hazırlanmış deyimleri kullanmaya çalışıyor. Bununla ne demek istiyorsun? Bu çalışmalıdır: NSString * query = [NSString stringWithFormat: @ "PRAGMA USER_VERSION =% i", userVersion]; [_db executeUpdate: sorgu]; Burada belirtildiği gibi: stackoverflow.com/a/21244261/1364174
Paul Brewczynski

1
(yukarıdaki yorumumla ilgili) NOT: FMDB kitaplığı artık şu özellikleri içeriyor: userVersion ve setUserVersion: yöntemler! Yani ayrıntılı @Andrey Tarantsov'un yöntemlerini kullanmak zorunda değilsiniz: - (int) databaseSchemaVersion! ve (void) setDatabaseSchemaVersion: (int) version. FMDB belgeleri: ccgus.github.io/fmdb/html/Categories/… :
Paul Brewczynski

4

En iyi çözüm IMO, bir SQLite yükseltme çerçevesi oluşturmaktır. Aynı problemi yaşadım (C # dünyasında) ve kendi çerçevemi oluşturdum. Buradan okuyabilirsiniz . Mükemmel çalışıyor ve (önceden kabus gibi olan) yükseltmelerimin benim tarafımda minimum çabayla çalışmasını sağlıyor.

Kitaplık C # ile uygulanmasına rağmen, orada sunulan fikirler sizin durumunuzda da iyi çalışmalıdır.


Bu güzel bir araç; çok kötü, bedava değil
Mihai Damian

4

1. /migrationsHer taşıma işleminin aşağıdaki gibi göründüğü SQL tabanlı geçişlerin listesiyle klasör oluşturun :

/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. Uygulanan geçişlerin listesini içeren bir db tablosu oluşturun, örneğin:

CREATE TABLE Migration (name TEXT);

3. Uygulama önyükleme mantığını güncelleyin, böylece başlamadan önce /migrationsklasörden geçişlerin listesini alır ve henüz uygulanmamış taşıma işlemlerini çalıştırır.

İşte JavaScript ile uygulanan bir örnek: Node.js Uygulamaları için SQLite İstemcisi


2

Bazı ipuçları...

1) Veritabanınızı bir NSOperation'a taşımak için tüm kodu koymanızı ve arka planda çalıştırmanızı öneririm. Veritabanı taşınırken bir döndürücü ile özel bir UIAlertView gösterebilirsiniz.

2) Veritabanınızı paketten uygulamanın belgelerine kopyaladığınızdan ve bu konumdan kullandığınızdan emin olun, aksi takdirde her uygulama güncellemesiyle tüm veritabanının üzerine yazacak ve ardından yeni boş veritabanını taşıyacaksınız.

3) FMDB harika, ancak executeQuery yöntemi herhangi bir nedenle PRAGMA sorguları yapamıyor. PRAGMA user_version kullanarak şema versiyonunu kontrol etmek istiyorsanız, sqlite3'ü kullanan kendi metodunuzu doğrudan yazmanız gerekecektir.

4) Bu kod yapısı, kullanıcı uygulama güncellemeleri arasında ne kadar zaman geçerse geçsin, güncellemelerinizin sırayla yürütülmesini ve tüm güncellemelerin yürütülmesini sağlar. Daha fazla yeniden düzenlenebilir, ancak bu ona bakmanın çok basit bir yoludur. Bu yöntem, veri tekliğinizin somutlaştırıldığı her seferde güvenle çalıştırılabilir ve veri tekliğinizi doğru bir şekilde ayarlarsanız oturum başına yalnızca bir kez gerçekleşen küçük bir db sorgusuna mal olur.

- (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

Veritabanı şemasını ve onu kullanan tüm kodu, gömülü ve telefonda bulunan uygulamalarda olduğu gibi, kilitli adımda değiştirirseniz, sorun aslında iyi kontrol altındadır (bir kurumsal DB'de şema geçişi olan kabusla karşılaştırılamaz yüzlerce uygulamaya hizmet ediyor olabilir - hepsi DBA'nın kontrolü altında değil ;-).


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.