面试题答案
一键面试1. 版本号管理
- 数据库版本号存储:在SQLite数据库中创建一个专门的表(例如
version_info
),用于存储当前数据库的版本号。表结构可以简单设计为:
CREATE TABLE version_info (
id INTEGER PRIMARY KEY,
version_number INTEGER NOT NULL
);
在应用首次创建数据库时,插入初始版本号(例如 1
)。
- 应用内版本号同步:在iOS应用代码中,维护一个与数据库版本号对应的变量。每次启动应用时,从数据库的
version_info
表中读取当前版本号,并与应用内记录的版本号进行比较,判断是否需要进行数据迁移。
2. SQLite数据迁移脚本的编写与执行机制
- 迁移脚本编写:
- 按照版本号顺序编写数据迁移脚本。每个脚本负责将数据库从一个版本升级到下一个版本。例如,从版本
1
升级到版本2
的脚本可能是:
- 按照版本号顺序编写数据迁移脚本。每个脚本负责将数据库从一个版本升级到下一个版本。例如,从版本
-- 从版本1升级到版本2
-- 添加新表
CREATE TABLE new_table (
id INTEGER PRIMARY KEY,
data TEXT
);
-- 修改字段
ALTER TABLE existing_table RENAME COLUMN old_column TO new_column;
-- 更新版本号
UPDATE version_info SET version_number = 2;
- 脚本应具备幂等性,即多次执行同一个脚本不应导致数据不一致或错误。例如,在创建表的语句中,可以使用 `CREATE TABLE IF NOT EXISTS` 来避免重复创建表的错误。
- 执行机制:
- 在iOS应用启动时,比较应用内记录的版本号和数据库中的版本号。如果数据库版本号低于应用内版本号,则按照版本号顺序依次执行相应的迁移脚本。
- 使用
FMDatabase
(或其他SQLite操作库)来执行SQL语句。例如,在Objective - C中:
FMDatabase *database = [FMDatabase databaseWithPath:databasePath];
if ([database open]) {
FMResultSet *rs = [database executeQuery:@"SELECT version_number FROM version_info"];
if ([rs next]) {
NSInteger currentVersion = [rs intForColumn:@"version_number"];
NSInteger appVersion = [self getAppDatabaseVersion]; // 应用内版本号获取方法
while (currentVersion < appVersion) {
NSString *scriptPath = [self getMigrationScriptPathForVersion:currentVersion + 1]; // 获取迁移脚本路径方法
NSString *script = [NSString stringWithContentsOfFile:scriptPath encoding:NSUTF8StringEncoding error:nil];
[database executeStatements:script];
currentVersion++;
}
}
[database close];
}
在Swift中:
let db = try? Connection(databasePath)
if let db = db {
if let rs = try? db.prepare("SELECT version_number FROM version_info") {
if let row = rs.first {
let currentVersion = row[0] as! Int
let appVersion = self.getAppDatabaseVersion() // 应用内版本号获取方法
var versionToMigrate = currentVersion + 1
while versionToMigrate <= appVersion {
if let scriptPath = self.getMigrationScriptPathForVersion(versionToMigrate),
let script = try? String(contentsOfFile:scriptPath, encoding:.utf8) {
try? db.execute(script)
}
versionToMigrate += 1
}
}
}
}
3. 应对迁移失败的回滚策略
- 事务处理:将每个迁移脚本放在一个事务中执行。例如,在SQLite中:
BEGIN TRANSACTION;
-- 迁移脚本内容
CREATE TABLE new_table (
id INTEGER PRIMARY KEY,
data TEXT
);
UPDATE version_info SET version_number = 2;
COMMIT;
如果在事务执行过程中出现错误,SQLite会自动回滚整个事务,确保数据库状态不变。
- 错误捕获与处理:在应用代码中,捕获执行迁移脚本时可能出现的错误。例如,在Objective - C中:
if (![database executeStatements:script]) {
NSLog(@"Migration failed: %@", [database lastErrorMessage]);
// 这里可以进行更详细的错误处理,如提示用户、记录日志等
// 由于事务会自动回滚,无需额外手动回滚操作
}
在Swift中:
do {
try db.execute(script)
} catch {
print("Migration failed: \(error)")
// 同样可以进行详细错误处理
}
4. 测试和验证策略的有效性
- 单元测试:
- 编写单元测试用例,模拟不同版本的数据库状态,测试迁移脚本的正确性。例如,使用XCTest框架(Objective - C或Swift)创建一个测试类:
class DatabaseMigrationTests: XCTestCase {
func testMigrationFromVersion1ToVersion2() {
// 创建版本1的数据库状态
let db = try? Connection(createVersion1DatabasePath())
// 执行迁移脚本
if let db = db {
if let scriptPath = self.getMigrationScriptPathForVersion(2),
let script = try? String(contentsOfFile:scriptPath, encoding:.utf8) {
try? db.execute(script)
}
}
// 验证数据库状态是否符合版本2的预期
let dbAfterMigration = try? Connection(createVersion1DatabasePath())
if let dbAfterMigration = dbAfterMigration {
let count = try? dbAfterMigration.scalar("SELECT COUNT(*) FROM new_table") as? Int
XCTAssertNotNil(count, "new_table should exist after migration")
}
}
}
- 集成测试:
- 在集成测试环境中,模拟应用的完整升级流程,包括不同版本的安装、升级等操作,验证数据迁移和版本控制策略在实际场景中的有效性。可以使用模拟器或真机进行测试,确保在不同设备和系统版本上都能正确迁移数据。
- 对迁移失败的情况进行模拟测试,验证回滚策略是否有效。例如,故意修改迁移脚本中的语句使其出错,检查数据库是否回滚到迁移前的状态。
- 灰度发布:在正式发布应用新版本之前,进行灰度发布,先对部分用户(如少量beta测试用户或特定用户组)发布包含新数据迁移策略的版本。收集这些用户在升级过程中的反馈和数据,确保数据迁移策略在大规模用户使用前没有问题。