Django测试“数据库表被锁定”

pgpifvop  于 2023-10-21  发布在  Go
关注(0)|答案(3)|浏览(232)

在我的Django项目中,我有一个视图,当用户发布一个zip文件时,它会立即响应,然后在线程的帮助下在后台处理数据。视图在正常测试中工作正常,但当我运行Django的测试时,它失败了,并出现database table is locked错误。目前,我正在使用默认的SQLite数据库,我知道如果我切换到另一个数据库,这个问题可能会得到解决,但我正在寻求当前设置的答案。为了简单起见,我修改了代码。
问题似乎是在DeviceReportModel表中写入。但我不知道为什么TestDeviceReport访问它。
Model.py

  1. class DeviceReportModel(models.Model):
  2. device_id = models.PositiveIntegerField(primary_key=True)
  3. ip = models.GenericIPAddressField()
  4. created_time = models.DateTimeField(default=timezone.now)
  5. report_file = models.FileField(upload_to="DeviceReport")
  6. device_datas = models.ManyToManyField(DeviceDataReportModel)
  7. def __str__(self):
  8. return str(self.id)

Serializers.py

  1. class DeviceReportSerializer(serializers.ModelSerializer):
  2. class Meta:
  3. model = DeviceReportModel
  4. fields = '__all__'
  5. read_only_fields = ('created_time', 'ip', 'device_datas')

views.py

  1. from django.utils import timezone
  2. from django.core.files.base import ContentFile
  3. from rest_framework.response import Response
  4. from rest_framework import status, generics
  5. import time
  6. import threading
  7. from queue import Queue
  8. class DeviceReportHandler:
  9. ReportQueue = Queue()
  10. @staticmethod
  11. def save_datas(device_object, request_ip, b64datas):
  12. device_data_models = []
  13. # ...
  14. # process device_data_models
  15. # this will take some time
  16. time.sleep(10)
  17. return device_data_models
  18. @classmethod
  19. def Check(cls):
  20. while(True):
  21. if not cls.ReportQueue.empty():
  22. report = cls.ReportQueue.get()
  23. # ...
  24. report_model = DeviceReportModel(
  25. device_id=report['device_object'], ip=report['request_ip'])
  26. # THIS LINE GIVES ERROR
  27. report_model.report_file.save(
  28. "Report_{}.txt.gz".format(timezone.now()), ContentFile(report['report_data']))
  29. device_data_models = cls.save_datas(
  30. report['device_object'], report['request_ip'], 'SomeData')
  31. report_model.device_datas.set(device_data_models)
  32. report_model.save()
  33. print("Report Handle Done")
  34. time.sleep(.1)
  35. @classmethod
  36. def run(cls):
  37. thr = threading.Thread(target=cls.Check)
  38. thr.daemon = True
  39. thr.start()
  40. class DeviceReportView(generics.ListCreateAPIView):
  41. queryset = DeviceReportModel.objects.all()
  42. serializer_class = DeviceReportSerializer
  43. DeviceReportHandler.run()
  44. def post(self, request):
  45. # ...
  46. report = {
  47. 'device_object': 1,
  48. 'request_ip': '0.0.0.0',
  49. 'report_data': b'Some report plain data',
  50. }
  51. # add request to ReportQueue
  52. DeviceReportHandler.ReportQueue.put(report)
  53. return Response("OK", status.HTTP_201_CREATED)

tests.py

  1. from rest_framework.test import APITestCase
  2. import gzip
  3. from io import BytesIO
  4. import base64
  5. import time
  6. class TestDeviceReport(APITestCase):
  7. @classmethod
  8. def setUpTestData(cls):
  9. # add a new test device for other tests
  10. pass
  11. def generate_device_data(self):
  12. # generate fake device data
  13. return ""
  14. def test_Report(self):
  15. # generate device data
  16. device_data = ''
  17. for i in range(10):
  18. device_data += self.generate_device_data() + '\n'
  19. buf = BytesIO()
  20. compressed = gzip.GzipFile(fileobj=buf, mode="wb")
  21. compressed.write(device_data.encode())
  22. compressed.close()
  23. b64data = base64.b64encode(buf.getvalue()).decode()
  24. data = {
  25. "device_id": 1,
  26. "report_data": b64data
  27. }
  28. response = self.client.post(
  29. '/device/reports/', data=data, format='json')
  30. print(response.status_code, response.content)
  31. def tearDown(self):
  32. # put some sleep to check whether the data has been processed
  33. # see "Report Handle Done"
  34. time.sleep(10)

下面是错误日志:

  1. (myDjangoEnv) python manage.py test deviceApp.tests.tests.TestDeviceReport
  2. Creating test database for alias 'default'...
  3. System check identified no issues (0 silenced).
  4. 201 b'"OK"'
  5. Exception in thread Thread-1:
  6. Traceback (most recent call last):
  7. File "C:\Users\Masoud\Anaconda3\envs\myDjangoEnv\lib\site-packages\django\db\backends\utils.py", line 84, in _execute
  8. return self.cursor.execute(sql, params)
  9. File "C:\Users\Masoud\Anaconda3\envs\myDjangoEnv\lib\site-packages\django\db\backends\sqlite3\base.py", line 383, in execute
  10. return Database.Cursor.execute(self, query, params)
  11. sqlite3.OperationalError: database table is locked
  12. The above exception was the direct cause of the following exception:
  13. Traceback (most recent call last):
  14. File "C:\Users\Masoud\Anaconda3\envs\myDjangoEnv\lib\threading.py", line 917, in _bootstrap_inner
  15. self.run()
  16. File "C:\Users\Masoud\Anaconda3\envs\myDjangoEnv\lib\threading.py", line 865, in run
  17. self._target(*self._args, **self._kwargs)
  18. File "<project_path>\deviceApp\views.py", line 303, in Check
  19. "Report_{}.txt.gz".format(timezone.now()), ContentFile(report['report_data']))
  20. File "C:\Users\Masoud\Anaconda3\envs\myDjangoEnv\lib\site-packages\django\db\models\fields\files.py", line 93, in save
  21. self.instance.save()
  22. File "C:\Users\Masoud\Anaconda3\envs\myDjangoEnv\lib\site-packages\django\db\models\base.py", line 741, in save
  23. force_update=force_update, update_fields=update_fields)
  24. File "C:\Users\Masoud\Anaconda3\envs\myDjangoEnv\lib\site-packages\django\db\models\base.py", line 779, in save_base
  25. force_update, using, update_fields,
  26. File "C:\Users\Masoud\Anaconda3\envs\myDjangoEnv\lib\site-packages\django\db\models\base.py", line 870, in _save_table
  27. result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
  28. File "C:\Users\Masoud\Anaconda3\envs\myDjangoEnv\lib\site-packages\django\db\models\base.py", line 908, in _do_insert
  29. using=using, raw=raw)
  30. File "C:\Users\Masoud\Anaconda3\envs\myDjangoEnv\lib\site-packages\django\db\models\manager.py", line 82, in manager_method
  31. return getattr(self.get_queryset(), name)(*args, **kwargs)
  32. File "C:\Users\Masoud\Anaconda3\envs\myDjangoEnv\lib\site-packages\django\db\models\query.py", line 1186, in _insert
  33. return query.get_compiler(using=using).execute_sql(return_id)
  34. File "C:\Users\Masoud\Anaconda3\envs\myDjangoEnv\lib\site-packages\django\db\models\sql\compiler.py", line 1335, in execute_sql
  35. cursor.execute(sql, params)
  36. File "C:\Users\Masoud\Anaconda3\envs\myDjangoEnv\lib\site-packages\django\db\backends\utils.py", line 67, in execute
  37. return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  38. File "C:\Users\Masoud\Anaconda3\envs\myDjangoEnv\lib\site-packages\django\db\backends\utils.py", line 76, in _execute_with_wrappers
  39. return executor(sql, params, many, context)
  40. File "C:\Users\Masoud\Anaconda3\envs\myDjangoEnv\lib\site-packages\django\db\backends\utils.py", line 84, in _execute
  41. return self.cursor.execute(sql, params)
  42. File "C:\Users\Masoud\Anaconda3\envs\myDjangoEnv\lib\site-packages\django\db\utils.py", line 89, in __exit__
  43. raise dj_exc_value.with_traceback(traceback) from exc_value
  44. File "C:\Users\Masoud\Anaconda3\envs\myDjangoEnv\lib\site-packages\django\db\backends\utils.py", line 84, in _execute
  45. return self.cursor.execute(sql, params)
  46. File "C:\Users\Masoud\Anaconda3\envs\myDjangoEnv\lib\site-packages\django\db\backends\sqlite3\base.py", line 383, in execute
  47. return Database.Cursor.execute(self, query, params)
  48. django.db.utils.OperationalError: database table is locked
  49. .
  50. ----------------------------------------------------------------------
  51. Ran 1 test in 10.023s
  52. OK
  53. Destroying test database for alias 'default'...
mwg9r5ms

mwg9r5ms1#

数据库被锁定错误

SQLite是一个轻量级数据库,因此不能支持高级别的并发。操作错误:databaseislocked错误指示您的应用程序正在经历比sqlite在默认配置中可以处理的更多的并发。此错误意味着一个线程或进程在数据库连接上具有独占锁,而另一个线程在等待释放该锁时超时。
Python的SQLite Package 器有一个默认的超时值,它决定了第二个线程在锁超时并引发OperationalError之前可以等待多长时间:数据库被锁定错误。
如果你得到这个错误,你可以通过以下方式解决:
切换到另一个数据库后端。到了某个时候,SQLite对于现实世界的应用程序来说变得太“精简”了,而这些并发错误表明您已经达到了这一点。
重写代码以减少并发性并确保数据库事务是短暂的。
通过设置timeout数据库选项来增加默认超时值:

  1. 'OPTIONS': {
  2. # ...
  3. 'timeout': 20,
  4. # ...
  5. }

这将使SQLite在抛出“数据库被锁定”错误之前等待更长的时间;它并不能真正解决问题
https://docs.djangoproject.com/en/3.0/ref/databases/#database-is-locked-errorsoption

展开查看全部
k75qkfdt

k75qkfdt2#

尝试使用django.test.TransactionTestCase而不是TestCase

blpfk2vs

blpfk2vs3#

TestCase使用事务来 Package 单元测试,确保所有测试的数据库配置一致。
TransactionTestCase(名字很奇怪)的意思是“用事务进行测试的测试用例”。没有事务,所以其他线程/进程不必担心被锁定。当然,数据库状态不容易回滚,但Django使用“serialized_rollback”来序列化数据库并恢复数据库(通过JSON),这显然比事务慢。

相关问题