我正在爲我的iPhone使用sqlite,並且我預計數據庫架構可能隨時間而改變。什麼是陷阱,命名慣例和需要注意的事情,以便每次成功進行遷移?適用於Sqlite的應用程序內數據庫遷移的最佳做法
例如,我曾想過爲數據庫名稱附加一個版本(例如Database_v1)。
我正在爲我的iPhone使用sqlite,並且我預計數據庫架構可能隨時間而改變。什麼是陷阱,命名慣例和需要注意的事情,以便每次成功進行遷移?適用於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」語法不支持的列或其他更改,我創建一個新表,將日期遷移到該表中,刪除舊錶並將新表重命名爲原始名稱。
如果您更改數據庫模式以及所有以鎖步方式使用它的代碼(在嵌入式和位於手機的應用程序中可能會出現這種情況),問題實際上已得到很好的控制(與模式遷移的噩夢毫無媲美在可能爲數百個應用程序提供服務的企業數據庫上 - 並非全部都在DBA的控制之下;-)。
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];
}
最好的解決辦法是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];
}
}
讓我與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];
}];
}
}
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
。創建包含施加遷移列表分貝表,例如:
CREATE TABLE Migration (name TEXT);
3
。更新應用程序引導程序邏輯,以便在啓動之前抓取/migrations
文件夾中的遷移列表,並運行尚未應用的遷移。
這裏是用JavaScript實現的一個例子:SQLite Client for Node.js Apps
我想有相同的邏輯,但由於某些原因,當我執行「編譯user_version =?」以編程方式,它失敗......任何想法? – Unicorn 2011-05-17 16:54:43
編譯指示設置不支持參數,您必須提供實際值:「pragma user_version = 1」。 – csgero 2011-12-21 09:42:15
我有一個問題。假設您的初始版本爲1.並且當前版本爲5.版本2,3,4中有一些更新。最終用戶只下載了您的版本1,現在升級到版本5.您應該怎麼做? – 2014-04-10 04:09:08