使用SQLite Swift4报错:数据库连接存在多线程非法访问问题
嘿,这个问题我之前踩过坑!错误提示已经把问题点得明明白白——你在多个线程里共用了同一个SQLite数据库连接,这直接违反了SQLite的线程安全规则。
问题根源
SQLite的sqlite3.dylib默认是单线程模式,这个模式下绝对不允许跨线程共享同一个连接对象。你的insert是静态方法,意味着多个线程调用这个方法时,很可能在复用同一个全局的数据库连接,这就触发了这个BUG提示。
快速解决方案
1. 每次操作创建独立连接(最简单直接)
修改你的插入函数,每次调用时都打开一个新连接,操作完成后立即关闭。这样从根源上避免了多线程共享连接的问题:
static func insert(product: [String:Any]) -> Bool { print("\nInsert: \(product["display_name"]!)\n") // 1. 每次操作打开新连接 var db: OpaquePointer? let dbPath = "你的数据库文件路径" guard sqlite3_open(dbPath, &db) == SQLITE_OK else { print("数据库打开失败: \(String(cString: sqlite3_errmsg(db)))") return false } // 确保操作完成后关闭连接 defer { sqlite3_close(db) } // 2. 构建INSERT语句(你的原有逻辑补全) var auxStr: String = "INSERT INTO product (" var auxValues: String = " VALUES (" for s in fieldsProducts { if fieldsProducts.last == s { auxStr += s + ")" auxValues += "?);" } else { auxStr += s + ", " auxValues += "?, " } } let query = auxStr + auxValues // 3. 准备并执行语句 var stmt: OpaquePointer? guard sqlite3_prepare_v2(db, query, -1, &stmt, nil) == SQLITE_OK else { print("语句准备失败: \(String(cString: sqlite3_errmsg(db)))") return false } defer { sqlite3_finalize(stmt) } // 4. 绑定参数(根据字段类型补充逻辑) for (index, field) in fieldsProducts.enumerated() { guard let value = product[field] else { continue } switch value { case let str as String: sqlite3_bind_text(stmt, index + 1, str, -1, SQLITE_TRANSIENT) case let int as Int: sqlite3_bind_int(stmt, index + 1, Int32(int)) case let double as Double: sqlite3_bind_double(stmt, index + 1, double) default: print("不支持的字段类型: \(field)") return false } } // 5. 执行插入 guard sqlite3_step(stmt) == SQLITE_DONE else { print("插入失败: \(String(cString: sqlite3_errmsg(db)))") return false } return true }
2. 启用SQLite线程安全模式(适合大量数据插入)
如果要插入大量数据,频繁创建/关闭连接会影响性能,这时候可以在应用启动时全局设置SQLite的线程安全模式:
// 在App启动时调用一次(必须在打开任何数据库连接之前) if sqlite3_config(SQLITE_CONFIG_SERIALIZED) != SQLITE_OK { print("无法设置序列化模式") }
SQLITE_CONFIG_SERIALIZED模式下,SQLite会自动处理连接的线程同步,允许你在多线程中安全复用同一个连接。搭配事务批量插入,性能会提升很多:
static func batchInsert(products: [[String:Any]]) -> Bool { var db: OpaquePointer? let dbPath = "你的数据库路径" guard sqlite3_open(dbPath, &db) == SQLITE_OK else { print("数据库打开失败") return false } defer { sqlite3_close(db) } // 开启事务 guard sqlite3_exec(db, "BEGIN TRANSACTION;", nil, nil, nil) == SQLITE_OK else { print("事务开启失败") return false } var isSuccess = true for product in products { if !insertSingleProduct(db: db, product: product) { isSuccess = false break } } // 提交或回滚事务 if isSuccess { sqlite3_exec(db, "COMMIT;", nil, nil, nil) } else { sqlite3_exec(db, "ROLLBACK;", nil, nil, nil) } return isSuccess } // 复用连接的单个插入逻辑 private static func insertSingleProduct(db: OpaquePointer, product: [String:Any]) -> Bool { // 这里复用之前的语句构建、参数绑定、执行逻辑,只是不用再打开/关闭连接 var auxStr: String = "INSERT INTO product (" var auxValues: String = " VALUES (" for s in fieldsProducts { if fieldsProducts.last == s { auxStr += s + ")" auxValues += "?);" } else { auxStr += s + ", " auxValues += "?, " } } let query = auxStr + auxValues var stmt: OpaquePointer? guard sqlite3_prepare_v2(db, query, -1, &stmt, nil) == SQLITE_OK else { print("语句准备失败: \(String(cString: sqlite3_errmsg(db)))") return false } defer { sqlite3_finalize(stmt) } for (index, field) in fieldsProducts.enumerated() { guard let value = product[field] else { continue } switch value { case let str as String: sqlite3_bind_text(stmt, index + 1, str, -1, SQLITE_TRANSIENT) case let int as Int: sqlite3_bind_int(stmt, index + 1, Int32(int)) case let double as Double: sqlite3_bind_double(stmt, index + 1, double) default: print("不支持的字段类型: \(field)") return false } } guard sqlite3_step(stmt) == SQLITE_DONE else { print("插入失败: \(String(cString: sqlite3_errmsg(db)))") return false } return true }
关键提醒
- 静态方法本身没问题,但如果静态方法里持有了全局的数据库连接,就会导致多线程冲突
- 批量插入时一定要用事务,否则每一条插入都触发一次磁盘写入,速度会慢到离谱
内容的提问来源于stack exchange,提问作者Augusto
相关产品推荐
相关产品推荐

