sqlite中的 database is locked 问题

article/2025/9/20 13:14:48

最近写产品用到了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().


http://chatgpt.dhexx.cn/article/0LKG0lIL.shtml

相关文章

PCIe锁定事务(Locked Transactions)介绍

✨ 1. 锁定事务背景介绍 有些CPU在执行指令的过程中有可能误触发锁定而进入锁定访问模式。比如&#xff0c;一些传统的软件并不需要exclusive访问&#xff0c;但由于错误地使用了现如今会导致锁定的事务而对PCIe链路产生了锁定。由于锁定访问IO设备会潜在引入死锁、恶化传输性能…

Lock(锁)

Lock(锁) 从JDK5.0开始&#xff0c;Java提供了更强大的线程同步机制——通过显示定义同步锁对象来实现同步&#xff0c;同步锁使用Lock对象充当java.util.concurrent.locks.Lock接口是控制多个线程对共享线程进行了访问的工具。锁提供了对共享资源的独占访问&#xff0c;每次只…

lock的使用

1、获取lock锁对象 2、通过lock锁对象创建Condition实例绑定到lock锁对象上 3、上锁&#xff1a;lock.lock() 4、try…catch执行业务 5、finally中释放锁&#xff1a;lock.unlock() package com.han.demo01;import java.util.concurrent.locks.Condition; import java.util.c…

如何理解Lock

显示锁 JDK层面提供了Lock锁都是通过Java提供的接口来手动解锁和释放锁的&#xff0c;所以在某种程度上&#xff0c;JDK中提供的Lock锁也叫显示锁、JDK提供的显示锁位于java.util.concurrent.locks包下&#xff0c;Lock接口的源码如下&#xff1a; public interface Lock {vo…

锁(Locks)

锁(Locks) 1 ReentrantLock 应用demo 可重入锁&#xff0c;是一种使用递归无堵塞的同步机制 比 synchronized 更强大、更灵活的锁机制&#xff0c;可以减少死锁发生的概率 默认为非公平锁&#xff0c;可以自定义为公平锁 底层采用 AQS 实现&#xff0c;通过内部 Sync 集成…

lock锁

目录 1. lock 基本用法 2. lock公平锁与非公平锁 3. lock注意事项 4. synchronized 与 lock区别 1. lock 基本用法 lock.lock(); try {} finally {lock.unlock() }或者try {lock.lock(); } finally {lock.unlock() }public class ThreadLock1 {public static void main(S…

LOCKED勒索病毒解密 数据恢复

什么是LOCKED勒索病毒 LOCKED勒索病毒是由Michael Gillespie发现的。该恶意程序旨在通过加密来阻止对存储在计算机上的文件的访问。为了解密他们的文件&#xff0c;鼓励受害者购买解密工具。与大多数此类程序一样&#xff0c;[LOCKED] 重命名所有加密文件&#xff0c;在本例中…

服务器数据中了locked勒索病毒,有关locked勒索病毒的介绍与预防建议

随着网络的普及和科技技术的发展&#xff0c;网络安全问题日益突出。而其中&#xff0c;勒索病毒就是一种常见的网络安全威胁。一旦企业的服务器数据库被勒索病毒攻击&#xff0c;会导致企业内部的重要数据被加密&#xff0c;给工作和生产生活带了极大的困扰。下面就为大家介绍…

locked 勒索软件

1.Locked介绍 Locked病毒属于Void Crypt 勒索软件家族。该勒索软件会加密 PC 上的所有用户数据&#xff08;照片、文档、Excel 表格、音乐、视频等&#xff09;&#xff0c;将其特定扩展名添加到每个文件&#xff0c;并在每个包含加密文件的文件夹中创建文件。 2.我是如何在我…

MEID

MEID简介 Mobile Equipment IDentifier&#xff08;MEID&#xff09;是全球唯一的56bit移动终端标识号。标识号会被烧入终端里&#xff0c;并且不能被修改。可用来对移动式设备进行身份识别和跟踪。由于ESN号段是有限的资源&#xff0c;基本上耗尽&#xff0c;可能还有少量回收…

科谱|MEID表格如何填写,99开头,MEID怎么申请,MEID申请表填写

作为通信产品最常见的三大主流号码之一MEID最近这些年的存在感确实偏低了点&#xff0c;尽管如此还是有大量需要使用MEID号码的产品&#xff0c;及时快速准确的申请到号码还是必不可少的。 文化的差异和语言的不通会造成不大不小的麻烦&#xff0c;今天我们结合最新版的申请表…

关于安卓系统4.0/5.0/6.0获取单卡手机,双卡手机的imei1,imei2,meid(用反射来实现,史上最详细,最全面获取)--binbinyang

有的人问我要代码跟例子&#xff0c;上次在GITHUB上弄了一个&#xff0c;提供地址 给大家 https://github.com/binbinyYang/GetPhoneInfo https://github.com/binbinyYang/GetPhoneInfo -------------------------------------------------------- 最近这3天&#xff0c;一直在…

MEID 的构成

MEID 的构成如下&#xff0c;针对 Hex 格式&#xff1a; 最后一位是 CD&#xff0c;这个 CD 不是 MEID 的组成部分&#xff0c;真正的 MEID 是前 14 位。在手机与基站进行 MEID 检查时&#xff0c;手机提交的 MEID 不能包含 CD 位&#xff0c;否则就会出错。当初设计此 CD 位主…

Mina MEID/GSM Activator 1.0 三网信号激活,支持iOS12.0~14.8.1

Mina团队已经更新工具&#xff0c;现在支持MEID/GSM三网和两网解锁信号&#xff01;支持iOS14.8系统&#xff0c;两网价格和三网价格一样。 Mina MEID/GSM Activator可以激活所有MEID/GSM二网、三网恢复信号&#xff0c;并且支持打电话、短信、4G流量上网&#xff0c;支持iPhon…

高通芯片联机读取修改串码 meid ESN wifi 蓝牙 sn等参数的操作解析{二}

上次我发了几个相关联机读写参数的帖子。很多友友询问有没有其他相关软件来解读参数的教程。今天就来个续集来解析参数读写 关于安卓机型写串码 改串码 端口开启和基带qcn等一些经验 高通联机修改IMEI等参数的相关解析 高通芯片基带相关的软件 QPST QXDM DFS等等&#xff0c…

说说移动设备的各种标识码(DeviceID, IMEI, UUID, UDID, OAID, IDFA, GAID)

转战广告行业&#xff0c;收集整理一波移动设备各种标识码的含义当做基础知识储备 一、名词解释 Device ID&#xff1a;设备ID。IMEI&#xff1a;&#xff08;International Mobile Equipment Identity&#xff09;国际移动设备标识的缩写。是由15位数字组成的“电子串号”&a…

Mina MEID/GSM Activator 1.0 三网信号激活,支持12.5.3~14.7

Mina团队已经更新工具&#xff0c;现在支持MEID/GSM三网和两网解锁信号&#xff01;支持iOS14.7系统&#xff0c;两网价格和三网价格一样。 Mina MEID/GSM Activator可以激活所有MEID/GSM二网、三网恢复信号&#xff0c;并且支持打电话、短信、4G流量上网&#xff0c;支持iPhon…

Mina MEID Activator 2.120210512更新使用说明支持三网(移动、联通、电信)国行版手机解锁打电话4G苹果手机激活锁停用Hello密码锁绕ID屏幕锁密码

Mina MEID Activator 是由Minacriss开发的新工具。可以激活所有MEID三网的信号恢复&#xff0c;并且支持打电话、短信、4G流量上网。支持iPhone5sX直接所有型号&#xff0c;支持iOS12.5.2iOS14.4.2。MEID三网指&#xff1a;不小心忘记ID密码&#xff0c;而且已经刷机并且是激活…

关于IMEI、MEID、IMSI

关于IMEI、MEID、IMSI 简介 IMEI、MEID都是用于标识一台物理设备的ID信息。在Android 8.0以下系统提供的API中&#xff0c;会根据不同条件返回二者之一的信息。 IMEI&#xff1a; 国际移动设备识别码&#xff0c;是区别移动设备的标志&#xff0c;一般用于标识某一台独立的设…

2022最新手机设备标识码(IMEI、MEID、UDID、UUID、ANDROID_ID、GAID、IDFA等)教程

Android篇 1 IMEI和MEID (1) IMEI (International Mobile Equipment Identity) 是国际移动设备身份码的缩写&#xff0c;国际移动装备辨识码&#xff0c;只有Android手机才获取的到&#xff0c;是由15位数字组成的"电子串号"&#xff0c;比如像这样 35988103031435…