Go语言操作数据库报database is locked错误该如何解决?
报错原因
- 你使用的是SQLite数据库,它基于文件级锁实现并发控制,读锁未释放时无法获取写锁执行写入操作。
- 你代码中的
defer rows.Close()会延迟到整个函数执行结束时才运行,当你执行INSERT语句的时候,之前查询得到的rows结果集还没有被关闭,对应的数据库连接仍持有读锁,写入操作无法获取独占写锁,因此抛出database is locked错误。
解决方案
你可以选择以下任意一种方式修复问题:
方案1:使用QueryRow替代Query(推荐)
你只需要判断用户是否存在,不需要处理多行结果集,使用QueryRow执行查询会在Scan完成后自动释放资源,不会残留锁:
func createNewUser(w http.ResponseWriter, r *http.Request) { var user User err := decodeJSONBody(w, r, &user) if checkError(w, err) { return } var exists bool // 用EXISTS查询效率更高,只需要判断是否存在记录 err = mainDB.QueryRow("SELECT EXISTS(SELECT 1 FROM users WHERE SAM = ?)", user.Sam).Scan(&exists) if checkError(w, err) { return } if exists { http.Error(w, "User already exists", http.StatusConflict) return } _, err = mainDB.Exec("INSERT INTO users (SAM, Vorname, Nachname) VALUES (?, ?, ?)", user.Sam, user.Vorname, user.Nachname) if checkError(w, err) { return } json.NewEncoder(w).Encode(user) }
方案2:主动提前关闭rows
如果要保留原有Query写法,在完成查询判断后主动调用rows.Close()释放资源,不要等待defer执行:
rows, err := mainDB.Query("SELECT * FROM users WHERE SAM = ?", user.Sam) if checkError(w, err) { return } // 保留defer做兜底,防止异常分支漏关 defer rows.Close() if rows.Next() { rows.Close() // 主动提前关闭 http.Error(w, "User already exists", http.StatusConflict) return } rows.Close() // 查询判断完成后主动提前关闭,释放读锁 // 再执行INSERT操作
方案3:优化SQLite配置降低锁冲突概率
打开数据库时添加WAL模式和超时参数,大幅降低读写冲突的概率:
// 打开数据库时添加参数 mainDB, err := sql.Open("sqlite3", "你的数据库文件路径?_journal_mode=WAL&_busy_timeout=5000")
_journal_mode=WAL:开启预写日志模式,支持读写并发执行,不会出现读操作阻塞写操作的情况_busy_timeout=5000:设置锁等待超时时间为5秒,拿不到锁时会等待而不是立刻报错
内容的提问来源于stack exchange,提问作者Bαmbus
相关产品推荐
相关产品推荐

