操作错误:数据库被锁定

156

我在应用程序中进行了一些重复操作(测试它),突然出现了一个奇怪的错误:

OperationalError: database is locked

我已经重启了服务器,但错误仍然存在。这可能是什么原因?

30个回答

149
Django的文档中:

SQLite旨在成为一个轻量级数据库,因此无法支持高并发。"OperationalError: database is locked"错误表示您的应用程序正在经历比sqlite默认配置所能处理的并发更多的情况。这个错误意味着一个线程或进程对数据库连接拥有独占锁,而另一个线程在等待锁被释放时超时了。
Python的SQLite封装器有一个默认的超时值,确定第二个线程在等待锁之前允许的时间长度,超过这个时间将引发"OperationalError: database is locked"错误。
如果您遇到这个错误,可以通过以下方式解决:
1. 切换到另一个数据库后端。在某个时刻,SQLite对于真实世界的应用程序来说变得太"轻"了,而这些并发错误表明您已经达到了这个点。
2. 重写代码以减少并发性,并确保数据库事务的生命周期短暂。
3. 通过设置timeout数据库选项来增加默认超时值: "OPTIONS": { # ... "timeout": 20, # ... } 这将使SQLite在抛出"database is locked"错误之前等待更长的时间;它并不能真正解决这些错误。

11
指定比默认超时时间更长的时间可能有助于缓解问题:create_engine('sqlite:///{}'.format(xxx), connect_args={'timeout': 15}) - kawing-chiu
3
你好,如何在Django中运行测试? - Frederick Nord
2
同一进程中来自不同线程的两个并发事务,如果都尝试向数据库写入数据,这比SQLite能够处理的并发性要高。我的下面的答案对此有更多的细节说明。 - Evan
8
这个答案如果没有额外的说明就很糟糕。对于绝大多数本地存储或者即使是有数百个访问者的小型网站,Sqlite非常强大。Basj的回答对于大多数人更为相关。 - j riv
1
@FrederickNord Django文档现在描述了如何增加SQLite数据库后端的超时时间:https://docs.djangoproject.com/en/4.0/ref/databases/#database-is-locked-errors - OlivierM
显示剩余4条评论

79

在我的情况下,问题是因为我从SQLite浏览器中打开了数据库。当我从浏览器中关闭它时,问题就解决了。


4
这对我也奇妙地起作用了。我想DB浏览器可能会建立额外的连接导致它崩溃。 - aaaakshat
当我使用DB浏览器时,我看到相同的行为。 - Iman Hosseini Pour
1
我通过DB Browser for SQLite向表中添加了一列,结果导致数据库被锁定。关闭它后问题得到解决。 - Mandeep Singh
确实,这也帮助了我。不确定原因是什么。 - undefined

66
我略微不同意接受的答案,因为引用这篇文档会隐含地将OP的问题(数据库已锁定)与以下问题相关联:

切换到另一个数据库后端。在某个时候,SQLite对于实际应用程序来说变得太“轻”,而这些并发错误表明您已经达到了这一点。

这有点“过于简单”地指责SQlite造成了这个问题(当正确使用时,它是非常强大的;这不仅仅是一个小型数据库的玩具,有趣的事实是:SQLite数据库大小限制为140TB)。
除非你有一个非常繁忙的服务器,在同一秒内有数千个连接,否则数据库已锁定错误的原因可能更多地是API的错误使用,而不是SQlite固有的问题,因为它会变得“太轻”。这里有更多关于SQLite的实现限制的信息。
现在的解决方案是:
当我同时使用两个脚本访问同一个数据库时,也遇到了类似的问题:
一个脚本进行写入操作,另一个脚本只读访问该数据库。
解决方法:无论是读取操作还是写入操作,在查询后尽快执行cursor.close()以下是更多详细信息。

2
@evan SQLite有一个“繁忙超时”功能。如果将其设置为非零值,则即使许多线程正在访问数据库,您也永远不会看到此消息...除非这些线程未能关闭事务。保持事务和连接处于打开状态会影响SQLite的“并发性”。 - Erik Aronesty
1
@python_user 不尽快关闭(即使是只读)游标将是一个典型的例子。请查看答案末尾的“更多细节”链接以获得完整的说明。 - Basj
3
谢谢:顶部答案没有额外的澄清,绝对是非常糟糕的。你回答的第一部分已经很好地解决了这个问题。对于绝大多数本地存储使用情况来说,SQlite非常强大。即使对于每天只有数百名访问者的小型网站,也可能不值得进一步深入研究。 - j riv
2
非常高兴你写了这个答案,我正要写但发现你已经提供了这个反馈。我来到这里是因为我遇到了这个错误,我有一种预感是我的代码有问题而不是sqlite,结果证明是这样的(已修复)。我现在正在一个非常繁忙的关键任务仓库上运行单个sqlite数据库,它位于我的自定义REST基础的.net应用程序服务器后面已经4年了,从未出现过问题(甚至有一个表有大约一百万行)。人们太快地对sqlite进行了解雇,如果可以的话,我会在超级计算机上运行这个该死的数据库。 - user734028
3
嘿,我在Django中遇到了这个错误,其中Django处理所有的数据库查询。有没有办法在Django中手动关闭游标? - Meet Gondaliya
显示剩余4条评论

49

这通常是由于Python或Django shells打开了一个对数据库的请求,但没有正确关闭,关闭终端访问通常可以释放它。今天我在运行命令行测试时遇到了这个错误。

编辑:我经常得到此类点赞。如果您想在不重启终端的情况下关闭访问权限,则可以从命令行执行以下操作:

from django import db
db.connections.close_all()

1
如何在不关闭终端的情况下修复它?有什么想法吗? - eric
@neuronet 在 shell 中如何关闭你的连接? - almost a beginner
2
在调用数据库函数之前,我必须设置DJANGO_SETTINGS_MODULE:os.environ.setdefault("DJANGO_SETTINGS_MODULE", "<subfolder_with_setings.json>.settings") 否则,在我看来,这是最佳答案。 - Oliver Zendel
1
+1 给 db.connections.close_all() 的提示。我一直在寻找一些能够在 tearDown() 中调用清理脚本之前解锁数据库的方法,这个方法解决了我的问题。谢谢。 - fbicknel
我不确定这段代码的作用,并且它没有解决我的问题,但是为了能够在不出错的情况下运行它,我必须从这里运行from django.conf import settings settings.configure() - Foad S. Farimani

10

正如其他人所说,有另一个进程正在使用 SQLite 文件并未关闭连接。如果您正在使用 Linux,则可以使用以下命令 fuser 查看使用文件(例如db.sqlite3)的进程:

$ sudo fuser -v db.sqlite3
                     USER        PID ACCESS COMMAND
/path/to/db.sqlite3:
                     user        955 F....  apache2

如果您想停止进程以释放锁定,请使用fuser -k,该命令向访问文件的所有进程发送KILL信号:

sudo fuser -k db.sqlite3

请注意,这样做很危险,因为它可能会在生产服务器上停止web服务器进程。
感谢@cz-game指出!

3
这个很好用,谢谢 :) 在我的情况下,是 sudo fuser -k app.db - Tri
1
sudo fuser -k app.db 在我的情况下起作用。非常感谢。 - Christopher Marlowe

7

我在使用保存在WSL文件树(\\wsl$ ...)下的数据库文件,并在Windows上运行Python解释器时遇到了这个错误。

你可以选择不在WSL-tree中保存数据库,或者在你的Linux分发版中使用基于Linux的解释器。


2
这对我也起作用了,将sqlite文件从WSL复制到Windows目录中,它就开始工作了。 - prs44

6

在一个情况下,我遇到了这个错误信息,但是帮助文档中没有(明确)提到如何解决,这个情况是在使用transaction.atomic()来包装一个对FooModel.objects.get_or_create()的调用,并同时从两个不同的线程中调用该代码时,只有一个线程能成功,而另一个则会出现“数据库被锁定”的错误。更改超时数据库选项对行为没有影响。

我认为这是由于sqlite 无法处理多个同时写入者,所以应用程序必须自己序列化写操作。当我的Django应用程序运行在sqlite后端时,我使用了threading.RLock对象来替换transaction.atomic(),虽然这并不完全等效,因此您可能需要在您的应用程序中做一些其他的事情。

以下是我同时从两个不同的线程中运行FooModel.objects.get_or_create的代码,如果有用的话:

from concurrent.futures import ThreadPoolExecutor

import configurations
configurations.setup()

from django.db import transaction
from submissions.models import ExerciseCollectionSubmission

def makeSubmission(user_id):
    try:
        with transaction.atomic():
            e, _ = ExerciseCollectionSubmission.objects.get_or_create(
                student_id=user_id, exercise_collection_id=172)
    except Exception as e:
        return f'failed: {e}'

    e.delete()

    return 'success'


futures = []

with ThreadPoolExecutor(max_workers=2) as executor:
    futures.append(executor.submit(makeSubmission, 296))
    futures.append(executor.submit(makeSubmission, 297))

for future in futures:
    print(future.result())

SQLite有一个“繁忙超时”功能。如果将其设置为非零值,则即使许多线程正在访问数据库,您也永远不会看到此消息...除非这些线程未能关闭事务。保持事务和连接处于打开状态会影响SQLite的“并发性”。 - Erik Aronesty
我有同样的问题:我使用transaction.atomic()。500秒超时。但是在我的测试中,我在2秒后遇到了数据库锁定错误。我不明白为什么?如果我进行几个请求,我可以看到transaction.atomic()确实起作用... - EinEsellesEniE

6

我在我的flask应用程序中遇到了这个问题,因为我在SQLite浏览器中打开了数据库,但忘记写入更改。

如果您在SQLite浏览器中也进行了任何更改,则点击“写入更改”即可解决问题。

输入图像描述


3

对我而言,问题解决的方法是关闭使用 python manage.py shell 命令打开的 Django shell。


2

我曾经遇到过类似的错误,在第一次实例化Django(v3.0.3)之后。这里所有的建议都没有起作用,除了:

  • 删除db.sqlite3文件并丢失其中的数据(如果有的话)
  • python manage.py makemigrations
  • python manage.py migrate

顺便说一下,如果你只是想测试PostgreSQL:

docker run --rm --name django-postgres \
  -e POSTGRES_PASSWORD=mypassword \
  -e PGPORT=5432 \
  -e POSTGRES_DB=myproject \
  -p 5432:5432 \
  postgres:9.6.17-alpine

更改settings.py,添加以下DATABASES设置:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'myproject',
        'USER': 'postgres',
        'PASSWORD': 'mypassword',
        'HOST': 'localhost',
        'PORT': '5432',
    }
}

...并添加数据库适配器:

pip install psycopg2-binary

然后是通常的步骤:
python manage.py makemigrations
python manage.py migrate

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接