在 iOS 中使用 FMDB 操作多个 .db
数据库文件是可行的。以下是具体实现方法和注意事项:
一、核心实现步骤
1. 创建多个数据库实例
可以同时创建多个 FMDatabase
对象,分别指向不同的 .db
文件:
// 创建第一个数据库实例
let dbPath1 = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
.appending("/database1.db")
let database1 = FMDatabase(path: dbPath1)
// 创建第二个数据库实例
let dbPath2 = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
.appending("/database2.db")
let database2 = FMDatabase(path: dbPath2)
2. 分别打开数据库连接
// 打开第一个数据库
guard database1.open() else {
print("无法打开 database1: \(database1.lastErrorMessage())")
return
}
// 打开第二个数据库
guard database2.open() else {
print("无法打开 database2: \(database2.lastErrorMessage())")
return
}
3. 执行跨数据库操作
可以通过 ATTACH DATABASE
命令实现跨库查询:
do {
// 将 database2 附加到 database1 的连接中(别名 db2)
try database1.executeUpdate("ATTACH DATABASE ? AS db2", values: [dbPath2])
// 执行跨库联合查询
let sql = """
SELECT * FROM main.table1
UNION
SELECT * FROM db2.table2
"""
let resultSet = try database1.executeQuery(sql, values: nil)
while resultSet.next() {
// 处理查询结果
}
} catch {
print("操作失败: \(error.localizedDescription)")
}
4. 分离数据库(可选)
try database1.executeUpdate("DETACH DATABASE db2", values: nil)
二、完整示例代码
// 创建并操作两个数据库
func handleMultipleDatabases() {
// 1. 初始化两个数据库
let db1 = FMDatabase(path: "path/to/database1.db")
let db2 = FMDatabase(path: "path/to/database2.db")
// 2. 打开连接
guard db1.open() && db2.open() else {
print("数据库打开失败")
return
}
// 3. 操作数据库1
do {
try db1.executeUpdate("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)", values: nil)
try db1.executeUpdate("INSERT INTO users (name) VALUES (?)", values: ["Alice"])
} catch {
print("db1 操作失败: \(error)")
}
// 4. 操作数据库2
do {
try db2.executeUpdate("CREATE TABLE IF NOT EXISTS products (id INTEGER PRIMARY KEY, title TEXT)", values: nil)
try db2.executeUpdate("INSERT INTO products (title) VALUES (?)", values: ["iPhone"])
} catch {
print("db2 操作失败: \(error)")
}
// 5. 跨数据库查询(附加方式)
do {
try db1.executeUpdate("ATTACH DATABASE ? AS db2", values: [db2.databasePath])
let result = try db1.executeQuery("SELECT name FROM users UNION SELECT title FROM db2.products", values: nil)
while result.next() {
print("跨库数据: \(result.string(forColumnIndex: 0) ?? "")")
}
} catch {
print("跨库查询失败: \(error)")
}
// 6. 关闭连接
db1.close()
db2.close()
}
三、关键注意事项
1. 线程安全
- 单连接单线程:每个
FMDatabase
对象在同一时间只能在一个线程中使用 - 多线程方案:使用
FMDatabaseQueue
为每个数据库创建独立队列let queue1 = FMDatabaseQueue(path: dbPath1) let queue2 = FMDatabaseQueue(path: dbPath2) queue1.inDatabase { db in // 操作 database1 } queue2.inDatabase { db in // 操作 database2 }
2. 事务处理
- 跨数据库事务需手动管理:
db1.beginTransaction() db2.beginTransaction() do { // 执行操作 try db1.executeUpdate(...) try db2.executeUpdate(...) db1.commit() db2.commit() } catch { db1.rollback() db2.rollback() }
3. 性能优化
- 避免频繁附加/分离数据库
- 使用
WAL
模式提升并发性能:try db.executeUpdate("PRAGMA journal_mode=WAL", values: nil)
四、常见问题解决方案
问题场景 | 解决方案 |
---|---|
数据库文件路径错误 | 使用 NSFileManager 检查文件是否存在 |
跨库查询结果为空 | 确认已正确附加数据库且表名/字段名无误 |
多线程操作崩溃 | 改用 FMDatabaseQueue 或确保每个线程使用独立连接 |
数据库被锁定 | 检查是否有未关闭的 FMResultSet 或未提交的事务 |
五、扩展应用场景
- 数据分库:将用户数据与系统配置存储在不同库中
- 读写分离:主库负责写入,附加只读从库用于查询
- 数据迁移:通过附加旧数据库实现平滑迁移