Django版本1.11,sqlite3版本3.11。
我正在使用WAL模式和长时间超时:
from django.apps import AppConfig
from django.db.backends.signals import connection_created
class SQLite3Config(AppConfig):
name = 'sqlite3_config'
def ready(self):
connection_created.connect(configure_sqlite)
# noinspection PyUnusedLocal
def configure_sqlite(sender, connection, **_):
if connection.vendor == 'sqlite':
cursor = connection.cursor()
cursor.execute('PRAGMA journal_mode=WAL;')
cursor.execute('PRAGMA busy_timeout=5000;')
我想保留sqlite3而不是移动到mysql或postgres,因为应用程序很小并且由多个服务器上的用户安装。
我相信WAL应该通过序列化来允许“并发”写入。当一起收到小爆发(半打左右)时,观察到“数据库被锁定”的问题。
我可以用shell重现shell中的问题。 django模型方法只是设置一个标志并保存模型:
def activate(self):
self.activate = True
self.save()
当我使用线程时,如果我启动了几个同时尝试它的线程,我发现它失败了。没有等待,因此不涉及超时。错误发生在5秒忙碌超时(不到两秒钟)之前:
In [2]: [NGThread(notifier_group.id).start() for notifier_group in NotifierGroup.objects.all()[:2]]
Out[2]: [None, None]
In [3]: [NGThread(notifier_group.id).start() for notifier_group in NotifierGroup.objects.all()[:3]]
Out[3]: [None, None, None]
In [4]: [NGThread(notifier_group.id).start() for notifier_group in NotifierGroup.objects.all()[:4]]
Out[4]: [None, None, None, None]
In [5]: Exception in thread Thread-97:
Traceback (most recent call last):
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/backends/utils.py", line 65, in execute
return self.cursor.execute(sql, params)
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/backends/sqlite3/base.py", line 328, in execute
return Database.Cursor.execute(self, query, params)
sqlite3.OperationalError: database is locked
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/usr/lib/python3.5/threading.py", line 914, in _bootstrap_inner
self.run()
File "/home/paul/wk/cliosoft/sosadmin/scratch.py", line 41, in run
toggle_active(notifier_group)
File "/home/paul/wk/cliosoft/sosadmin/scratch.py", line 30, in toggle_active
model.activate()
File "/home/paul/wk/cliosoft/sosadmin/notifications/models/notifier_group.py", line 67, in activate
self.save()
File "/home/paul/wk/cliosoft/sosadmin/notifications/models/notifier_group.py", line 33, in save
self.verify()
File "/home/paul/wk/cliosoft/sosadmin/notifications/models/notifier_group.py", line 46, in verify
self.create_notifier(base_spec, model_set, group_event_condition)
File "/home/paul/wk/cliosoft/sosadmin/notifications/models/notifier_group.py", line 57, in create_notifier
notifier.users = self.users.all()
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/models/fields/related_descriptors.py", line 534, in __set__
manager.set(value)
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/models/fields/related_descriptors.py", line 1004, in set
self.add(*new_objs)
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/models/fields/related_descriptors.py", line 931, in add
self._add_items(self.source_field_name, self.target_field_name, *objs)
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/models/fields/related_descriptors.py", line 1100, in _add_is
for obj_id in new_ids
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/models/query.py", line 442, in bulk_create
ids = self._batched_insert(objs_without_pk, fields, batch_size)
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/models/query.py", line 1083, in _batched_insert
self._insert(item, fields=fields, using=self.db)
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/models/query.py", line 1060, in _insert
return query.get_compiler(using=using).execute_sql(return_id)
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/models/sql/compiler.py", line 1099, in execute_sql
cursor.execute(sql, params)
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/backends/utils.py", line 80, in execute
return super(CursorDebugWrapper, self).execute(sql, params)
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/backends/utils.py", line 65, in execute
return self.cursor.execute(sql, params)
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/utils.py", line 94, in __exit__
six.reraise(dj_exc_type, dj_exc_value, traceback)
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/utils/six.py", line 685, in reraise
raise value.with_traceback(tb)
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/backends/utils.py", line 65, in execute
return self.cursor.execute(sql, params)
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/backends/sqlite3/base.py", line 328, in execute
return Database.Cursor.execute(self, query, params)
django.db.utils.OperationalError: database is locked
Exception in thread Thread-98:
Traceback (most recent call last):
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/backends/utils.py", line 65, in execute
return self.cursor.execute(sql, params)
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/backends/sqlite3/base.py", line 328, in execute
return Database.Cursor.execute(self, query, params)
sqlite3.OperationalError: database is locked
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/usr/lib/python3.5/threading.py", line 914, in _bootstrap_inner
self.run()
File "/home/paul/wk/cliosoft/sosadmin/scratch.py", line 41, in run
toggle_active(notifier_group)
File "/home/paul/wk/cliosoft/sosadmin/scratch.py", line 28, in toggle_active
model.deactivate()
File "/home/paul/wk/cliosoft/sosadmin/notifications/models/notifier_group.py", line 72, in deactivate
self.save()
File "/home/paul/wk/cliosoft/sosadmin/notifications/models/notifier_group.py", line 33, in save
self.verify()
File "/home/paul/wk/cliosoft/sosadmin/notifications/models/notifier_group.py", line 46, in verify
self.create_notifier(base_spec, model_set, group_event_condition)
File "/home/paul/wk/cliosoft/sosadmin/notifications/models/notifier_group.py", line 57, in create_notifier
notifier.users = self.users.all()
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/models/fields/related_descriptors.py", line 534, in __set__
manager.set(value)
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/models/fields/related_descriptors.py", line 1004, in set
self.add(*new_objs)
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/models/fields/related_descriptors.py", line 931, in add
self._add_items(self.source_field_name, self.target_field_name, *objs)
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/models/fields/related_descriptors.py", line 1100, in _add_is
for obj_id in new_ids
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/models/query.py", line 442, in bulk_create
ids = self._batched_insert(objs_without_pk, fields, batch_size)
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/models/query.py", line 1083, in _batched_insert
self._insert(item, fields=fields, using=self.db)
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/models/query.py", line 1060, in _insert
return query.get_compiler(using=using).execute_sql(return_id)
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/models/sql/compiler.py", line 1099, in execute_sql
cursor.execute(sql, params)
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/backends/utils.py", line 80, in execute
return super(CursorDebugWrapper, self).execute(sql, params)
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/backends/utils.py", line 65, in execute
return self.cursor.execute(sql, params)
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/utils.py", line 94, in __exit__
six.reraise(dj_exc_type, dj_exc_value, traceback)
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/utils/six.py", line 685, in reraise
raise value.with_traceback(tb)
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/backends/utils.py", line 65, in execute
return self.cursor.execute(sql, params)
File "/home/paul/.virtualenvs/sosadmin/lib/python3.5/site-packages/django/db/backends/sqlite3/base.py", line 328, in execute
return Database.Cursor.execute(self, query, params)
django.db.utils.OperationalError: database is locked
答案 0 :(得分:0)
我在发行说明中看不到任何内容,但我已升级到Django 2.0,此问题现已消失。随着在sqlite3中设置WAL,它现在运行得非常好。
答案 1 :(得分:0)
随着系统的扩展,此错误再次发生。由于某些原因,我被要求使用sqlite3。
Django中的timeout选项似乎被忽略了。我尝试使用sqlite周围的包装程序在db游标级别上自己实现它,并且遇到了相同的问题(在该级别上,您不能退出到足以放弃事务并尝试建立新连接的程度)。
我会喜欢上下文管理器,例如with db_locked_retries():...
,但是无法在with上下文中进行迭代,因此我这样创建了db_retry函数:
import time
import logging
import random
from django.conf import settings
import django.db
logger = logging.getLogger('django.db.backends')
def db_retry(fn, timeout=None):
"""Call fn with no arguments. If OperationalError exception, make retries until timeout has passed"""
timeout = timeout or settings.DATABASES['default'].get('OPTIONS', dict()).get('timeout', 5)
now = time.time()
give_up_time = now + timeout
retries = 0
while now < give_up_time:
now = time.time()
try:
result = fn()
if retries:
logger.warning(f'db_retry: Succeeded after {retries} retries')
return result
except django.db.OperationalError as exception:
msg = str(exception)
if 'locked' in msg: # pragma: no cover
retries += 1
wait_time = random.uniform(1, timeout / 10)
logger.warning(f'db_retry: {msg}: Retrying after {wait_time} seconds')
django.db.close_old_connections()
time.sleep(wait_time)
else: # pragma: no cover
logger.exception(f'db_retry: {msg}: Giving up')
raise
由于在模型方法等中定义传递给该函数的函数非常容易。这可以解决问题并成功使用超时重试访问。在我的特定用例中,当前每三十分钟处理一次数百次并发使用和重试。
我只需要“包装”到目前为止可能进行多次写操作的模型代码。