SQLite 报 database is locked 怎么解决
日志里出现 database is locked 的时候,我第一反应是去调连接池参数,调了半天没用。SQLite 并发写入报 database is locked 怎么解决,真正的解法是承认它同一时刻只允许一个写入者,然后让代码配合这件事。
一、最贵的一课
那次我把最大连接数往下调了又往上调,试了好几轮,错误照旧。
后来才想明白:这不是资源不够,是规则问题。SQLite 的写入本来就是串行的,我开多少连接都不改变这一点,只是让更多连接一起排队等,然后一起超时。
真正解决的是一句话:写操作排到一起去,别让它们互相撞。
更具体一点说,我那次的问题出在把一个后台任务和接口请求放在了同时段。两边都在写,谁也没错,但就是会撞。后来我把后台任务的写入挪到了业务低谷,报错就少了一大截。
这类问题不需要改架构,需要的是想清楚「谁在什么时候写」。
二、坑一:把超时设成了零
现象是并发一上来立刻报错,几乎不给重试的机会。
原因是连接串里没有设忙等超时,遇到锁就直接返回错误,而不是等一会儿再试。
怎么避:在连接串里把忙等超时设成一个明确的值。这样短时间的锁冲突会自己过去,不会变成报错。这个改动通常比任何代码重构都立竿见影。
止损做法:报错频率高的时候,先检查超时设置,再看代码。顺序反了会白花很多时间。
三、坑二:每条写语句各自提交
现象是平时没事,一旦有个批量任务在跑,接口就开始报锁。
原因是每一次写入都当成一个独立事务提交,写一百条就抢一百次写锁。
怎么避:把一批相关的写操作放进同一个事务里。事务的边界应该按业务来定,不是按语句来定。
止损做法:找出代码里在循环里执行写语句的地方,这类几乎都能合并。
四、坑三:读写混在同一条连接上
现象是查询也变慢了,不只是写入报错。
原因是长事务里既读又写,写锁一直握着,读也就跟着等。日志模式设成默认的时候,这种影响会更明显。
怎么避:把长事务拆短,读和写分开处理。如果读的量确实大,考虑调整日志模式让读不被写阻塞。具体参数以 SQLite 官方文档为准,版本之间有差异。
止损做法:先找出执行时间最长的那几条 SQL,它们多半就是握着锁不放的。
五、SQLite 并发写入报 database is locked 怎么解决:止损清单
- 连接串里的忙等超时,先设上再看别的
- 循环里的写语句合并进事务
- 写操作收拢到少数几个函数,别散在业务里
- 把批量任务挪到业务低谷时段执行
- 数据库文件放本地盘,别放同步目录或网络盘
- 报错日志里带上是哪条语句触发的,不然只能猜
最后一条看着不起眼,但它能省掉大量时间。我最早没记语句,只能靠猜,猜错的次数不少。
清单前三项是代码层面的,改起来最快;第四项是排期层面的,要跟业务配合;最后两项是环境层面的,属于一次性投入。建议按这个顺序做,别一上来就怀疑是数据库不行了。
最后补一句我的判断:SQLite 并发写入报 database is locked 怎么解决,答案基本都在你自己的代码里,不在数据库配置里。它是把「同时只能一个人写」这件事如实告诉你,而不是出了故障。
常见问题
Q:database is locked 和数据库损坏是一回事吗?
A:不是。locked 表示有人正在写,是正常的并发冲突,不是文件坏了。先按并发处理,不要急着重修数据库。
Q:加了忙等超时是不是就永远不会报锁了?
A:不是。超时只是把短冲突吸收掉,如果确实有一个长事务一直握着写锁,还是会超时。这时候要去找那个长事务。
Q:多进程同时用同一个 SQLite 文件可以吗?
A:技术上可以,但写入一样要排队。进程一多,冲突概率就上去,建议尽量让写入集中在一个进程里。
相关阅读
本文作者西米,37 岁程序员,一个人加五个 AI 员工在跑一家公司。踩过的坑和真实账本都写在公众号「西米分享」。
完整代码、踩坑清单与最新进展,都在公众号。
📱 关注公众号「西米分享」