SQLite磁盘I/O错误排查及手动SQL与GRDB查询结果不一致问题
一、手动SQL查询与GRDB fetchAll结果不一致的原因
你直接打印db.execute(sql: ...)的结果是错误的——execute方法返回的是Statement对象(SQL语句的执行句柄),而非实际查询结果集。打印这个对象只会输出它的内存地址或类描述,自然和User.all().fetchAll(db)返回的User对象数组呈现不同内容。
要正确获取手动SQL的查询结果,需要调用fetchAll方法:
try await db.writer.write { db in // 获取原始行数据 let rawRows = try db.fetchAll(sql: "SELECT * from \"user\"") print("Pre save raw rows: \(rawRows)") // 映射为User对象,与GRDB的all()结果一致 let users = try User.fetchAll(db, sql: "SELECT * from \"user\"") print("Pre save users: \(users)") try model.save(db) }
另外需要确认:GRDB默认会将结构体名(User)转为小写表名(user),你的手动SQL里用\"user\"是正确的;但若通过TableRecord协议自定义过表名,需确保手动SQL的表名与GRDB使用的一致。
二、SQLite Error 10 (Disk I/O Error) 排查方向
结合你的登录/登出流程,从以下几点排查:
- 错误被隐藏:你的
save和delete方法的catch块是空的,导致操作失败的错误无法被捕获和查看。先补上错误打印,确认具体触发时机:
func save(_ model: Model) { Task { do { try await db.writer.write { db in try model.save(db) } } catch { print("Save failed: \(error)") // 新增错误打印 } } } func delete() { Task { do { _ = try await db.writer.write { db in try Model.deleteAll(db) } } catch { print("Delete failed: \(error)") // 新增错误打印 } } }
并发操作冲突:登出的
delete和登录的save都是异步执行,可能出现delete未完成、save就启动的情况,导致数据库文件锁定或操作冲突。可通过串行化操作解决:- 用串行
Actor包裹数据库操作,或确保GRDB的writer实例正确管理(GRDB的writer本身是串行的,但若db实例管理不当会失效)。
- 用串行
数据库文件权限/路径问题:检查数据库所在目录的读写权限(比如iOS的Documents目录是否正确访问、macOS沙箱权限是否配置);磁盘空间不足也会触发disk I/O error,需确认设备剩余空间。
GRDB连接生命周期问题:确保
db实例(包括db.writer)是全局单例或正确管理的,避免重复初始化或提前释放导致的连接异常。save操作的行为差异:GRDB的
save方法会根据记录是否存在自动选择INSERT或UPDATE。第二次登录时,若delete未执行完成,User记录仍存在,save会执行UPDATE操作,此时若数据库文件存在I/O问题,就会触发错误。
内容的提问来源于stack exchange,提问作者CalebK

