iOS 中使用 FMDB可以同时操作多个 `.db` 数据库文件吗?

19 阅读3分钟

在 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 或未提交的事务

五、扩展应用场景

  1. 数据分库:将用户数据与系统配置存储在不同库中
  2. 读写分离:主库负责写入,附加只读从库用于查询
  3. 数据迁移:通过附加旧数据库实现平滑迁移