最近写产品用到了sqlite3作为单机数据库,碰到一个挺有意思的问题。需求大体是两张表,查询需要连表查询,更新也需要连表更新;总共只有几百条数据,但运行过程中出现明显的超时和异常,以为是sqlite有问题,但想想觉得不可能,不至于扛不住几百条数据的并发查询,后来发现似乎是因为sqlite的锁是个库级别的完全单线程锁引起的。
代码是用go写的,model部分如下:

package maintype App struct {Id          int         `gorm:"id;primary_key"`Name         string      `gorm:"name"`UpdateAt   int64       `gorm:"updateAt"`
}type Task struct {Id           int         `gorm:"id;primary_key"`Name     string      `gorm:"name"`AppId      int         `gorm:"appId"`AppName   string      `gorm:"-"`updateAt  int64       `gorm:"updateAt"`
}

其中 task表的AppName字段需要去app表查询,通过appId作为外键。
为了对比,这里同时使用mysql和sqlite作为数据库。
如果单纯查询,两种数据库都没有问题:

package mainimport ("fmt""github.com/jinzhu/gorm"_ "github.com/jinzhu/gorm/dialects/mysql"_ "github.com/jinzhu/gorm/dialects/sqlite""time"
)func main(){count := 0flag := 3for i:=0;i < flag;i++ {go func(name string) {sqliteSearch(name)count++}("sqlite")}for ;count < flag; {time.Sleep(time.Second)}
}func mysqlSearch(name string){fmt.Println(name)conn,err := gorm.Open("mysql","root:root@tcp(localhost:3306)/test?charset=utf8&parseTime=True&loc=Local")if err != nil {fmt.Println(err)return}defer conn.Close()tasks,err := search(conn)if err != nil{fmt.Println(err)return}p(tasks)
}func sqliteSearch(name string){fmt.Println(name)conn,err := gorm.Open("sqlite3","D:\\code\\go\\learning\\src\\main\\db.db")if err != nil {fmt.Println(err)return}defer conn.Close()tasks,err := search(conn)if err != nil{fmt.Println(err)return}p(tasks)
}func p(tasks []*Task){for _,task := range tasks{fmt.Println(task.Id,task.Name,task.AppName)}
}func updateApp(conn *gorm.DB,task *Task) error {appId := task.AppIdapp := &App{}err := conn.Table("app").Where("id = ?",appId).Find(app).Errorif err != nil{return err}task.AppName = app.Namereturn nil
}func search(db *gorm.DB) ([]*Task,error){tasks := make([]*Task,0)rows,err := db.Table("app").Raw("select id,name,appId from task").Rows()if err != nil{return nil,err}defer rows.Close()for rows.Next(){task := &Task{}err = rows.Scan(&task.Id,&task.Name,&task.AppId)if err != nil{return nil, err}err = updateApp(db,task)if err != nil{return nil, err}tasks = append(tasks,task)}return tasks,err
}

同时开启多个协程去查询表格,数据都可以被正常查出来。
如果在查询过程中有更新或者更新查询同时有,问题就来了:

package mainimport ("fmt""github.com/jinzhu/gorm"_ "github.com/jinzhu/gorm/dialects/mysql"_ "github.com/jinzhu/gorm/dialects/sqlite""time"
)func main(){count := 0flag := 3for i:=0;i < flag;i++ {go func(name string) {sqliteSearch(name)count++}("sqlite")}for ;count < flag; {time.Sleep(time.Second)}
}func mysqlSearch(name string){fmt.Println(name)conn,err := gorm.Open("mysql","root:root@tcp(localhost:3306)/test?charset=utf8&parseTime=True&loc=Local")if err != nil {fmt.Println(err)return}defer conn.Close()tasks,err := search(conn)if err != nil{fmt.Println(err)return}p(tasks)
}func sqliteSearch(name string){fmt.Println(name)conn,err := gorm.Open("sqlite3","D:\\code\\go\\learning\\src\\main\\db.db")if err != nil {fmt.Println(err)return}defer conn.Close()tasks,err := search(conn)if err != nil{fmt.Println(err)return}p(tasks)
}func p(tasks []*Task){for _,task := range tasks{fmt.Println(task.Id,task.Name,task.AppName)}
}func updateApp(conn *gorm.DB,task *Task) error {appId := task.AppIdapp := &App{}err := conn.Table("app").Where("id = ?",appId).Find(app).Errorif err != nil{return err}task.AppName = app.Namereturn nil
}func search(db *gorm.DB) ([]*Task,error){tasks := make([]*Task,0)rows,err := db.Table("app").Raw("select id,name,appId from task").Rows()if err != nil{return nil,err}defer rows.Close()for rows.Next(){task := &Task{}err = rows.Scan(&task.Id,&task.Name,&task.AppId)if err != nil{return nil, err}now := time.Now().Unix() // 这里在查询时更新表格err = db.Table("task").Where("id = ?",task.Id).Update("updateAt",now).Errorif err != nil{return nil, err}err = updateApp(db,task)if err != nil{return nil, err}tasks = append(tasks,task)}return tasks,err
}

上面代码在查询时,更新了目标表的updateAt字段,此时就会出现如下报错:

先声明,这个报错mysql是不会出现的。
sqlite只有一把库级别的锁,当多个请求同时执行查询请求时,请求不会被阻塞;但是如果多个写请求发生,就会涉及到对锁的抢夺;如果各个请求都是很明确的读或者写区分开,那么也没什么问题,写请求被sqlite串行执行即可;但如果一个请求中有读有写,就会有问题;上面的代码中,查表时读多条数据,因此需要迭代,我怀疑这里虽然是读,极可能对表加了锁;而在通过appId查询appName时,进行了写操作,此时就造成了死锁;这种情况甚至不是因为多个请求并发引起的,将上面的flag变量改为1,使整个请求只有一条,依然会有锁死的问题。
解决这个问题的方案是访问sqlite时,每次都只进行读或者写操作,不允许同时进行,上面代码改为:

func search(db *gorm.DB) ([]*Task,error){lock.Lock()defer lock.Unlock()tasks := make([]*Task,0)rows,err := db.Table("app").Raw("select id,name,appId from task").Rows()if err != nil{return nil,err}defer rows.Close()for rows.Next(){task := &Task{}err = rows.Scan(&task.Id,&task.Name,&task.AppId)if err != nil{return nil, err}err = updateApp(db,task)if err != nil{return nil, err}tasks = append(tasks,task)}now := time.Now().Unix()for _,task := range tasks {err = db.Table("task").Where("id = ?",task.Id).Update("updateAt",now).Errorif err != nil{return nil, err}}return tasks,err
}

上面有两点注意:
1 引入了锁,限制请求的并发数,这个似乎是必须的,如果不加锁依然会有锁超时的问题;
2 都查询完毕后,再循环对每一条数据进行更新;
这样操作,表锁死的问题就没有了;但代价有点儿高,没法多路写库。
另外查到其它一些方案,但并没有什么用,比如这里 https://blog.csdn.net/weixin_43851310/article/details/100709797 提到的建立连接时设置&_busy_timeout=9999999,使等待锁的时间无限长(默认为5秒,5秒后如果没有轮到锁就会报database is locked错误),但并没什么用,这个仅针对表非常大确实需要比较长时间等待才有用,这里的场景下几百条数据都要等待很久是不科学的;其它方案中还提到,比如忘记Close等,也确实可能引起这个问题,如上面循环查询时,一定要defer rows.Close().

sqlite中的 database is locked 问题相关推荐

  1. hue集成mysql报错_hue集成hive访问报database is locked

    这个问题这应该是hue默认的SQLite数据库出现错误,你可以使用mysql postgresql等来替换 hue默认使用sqlite作为元数据库,不推荐在生产环境中使用.会经常出现database ...

  2. golang sqlite数据库 rows.Close()造成 错误database is locked

    在最近的一个程序中,使用的是sqlite数据库.涉及到多线程对数据库的读写.因为sqlite本身有五个锁状态:unlocked,shared,reserved,pending,exclusive.每个 ...

  3. sqlite database is locked 问题解决方案

    这两天在项目中用大强度大频率的方法测试时遇到sqlite报database is locked的问题, 分析下来原因是sqlite对数据库做修改操作时会做(文件)锁使得其它进程同一时间使用时会报该错误 ...

  4. sqlite遇到database is locked问题的完美解决

    这两天在项目中用大强度大频率的方法测试时遇到sqlite报database is locked的问题, 分析下来原因是sqlite对数据库做修改操作时会做(文件)锁使得其它进程同一时间使用时会报该错误 ...

  5. delphi xe “[FIREDAC][PHYS][SQLITE] DATABASE IS LOCKED”错误

    在对sqlite数据进行插入操作时,提示 "[FIREDAC][PHYS][SQLITE] DATABASE IS LOCKED"错误 在FDConnection1中的 Locki ...

  6. SQLite数据库database is locked解决

    开发语言C# 主要通过配置数据库连接字符串解决 关键语句:Journal Mode=WAL: /// <summary> /// 数据库连接字符串 /// </summary> ...

  7. 使用FMDB多线程訪问数据库,及database is locked的问题

    今天最终攻克了多线程同一时候訪问数据库时,报数据库锁定的问题.错误信息是: Unknown error finalizing or resetting statement (5: database i ...

  8. python database is locked_sqlite遇到database is locked问题的完美解决

    这两天在项目中用大强度大频率的方法测试时遇到sqlite报database is locked的问题, 分析下来原因是sqlite对数据库做修改操作时会做(文件)锁使得其它进程同一时间使用时会报该错误 ...

  9. close() was never explicitly called on database 和 database is locked 错误原因

    1.cursor 使用完没有关闭. 2.数据库用完没有关闭. 3.数据库重复新建(new了多个对象). 我的错误是new了多个对象. LogCat 报错信息: [java] view plaincop ...

  10. MUSH中的Database

    1.long DatabaseOpen(BSTR DbName, BSTR Filename, long Flags) DbName    给数据库取得名字 Filename     数据库文件路径 ...

最新文章

  1. 【大数据】大数据思维的十大核心原理
  2. BZOJ 4720 [Noip2016]换教室
  3. VMware提示此主机支持Intel VT-x,但Intel VT-x处于禁用状态怎么解决
  4. spring cloud互联网分布式微服务云平台规划分析--spring cloud系统管理平台
  5. 使用Notepad++正则提取数据,然后进行替换
  6. 【PL/SQL】测试函数时,日期参数的输入格式
  7. PyTorch数据加载器
  8. android+mid播放器,手机midi播放器下载
  9. WebLogic部署项目成功后,访问Error 404
  10. M/M/m排队模型 (单队列多服务台并联服务模型)数学建模: 基于生灭过程的理论计算和基于事件推进的Matlab模拟仿真思路
  11. HTML5 canvas元素绘制花朵等
  12. 阿里云通过链接下载附件
  13. php直播推流rtmp,直播推流nginx-rtmp-module集成
  14. 基于Cookie-Editor与curl实现跨设备的文件下载
  15. 二叉树 Binary Tree
  16. java有哪些注解_JAVA常用注解
  17. 【数据结构】递归斐波那契数列的时间复杂度、空间复杂度
  18. 求1!+2!+...+10!的值
  19. 本月,我最推荐的意外保险排行榜
  20. 淘宝视频的跨模态检索

热门文章

  1. 完整方法:摄像头打不开,驱动无法安装成功,设备状态显示由于其配置信息(注册表中的)不完整或已损坏,windows无法启动这个硬件设备。(代码19)
  2. 零售的哲学前三章读后感
  3. 【合宙GSM模块Air202 烧录iRTU固件连接阿里云】
  4. 计算机突然找不到u盘了,win10系统u盘文件突然不见了怎么恢复
  5. 企业微信好不好,OA软件何去何从?
  6. 笔记本插拔电源屏闪问题
  7. 【逼你学习】让自制力提升300%的时间管理方法、学习方法分享
  8. 删除文档中的中文字符---只保留英文字符
  9. UEditor之实现配置简单的图片上传示例
  10. 一台计算机数据丢失与恢复,数据丢失后的六种计算机恢复方法