Code前端首页关于Code前端联系我们

Django ORM处理数据库连接超时的最佳方案

terry 2年前 (2023-09-24) 阅读数 55 #后端开发

Django ORM数据库连接方式是:每个请求独立数据库连接。

这意味着Django将为每个请求创建一个数据库连接,并在响应后关闭数据库连接。每个请求的数据库连接都是独立的!


众所周知,为了节省资源,服务器会在客户端空闲一段时间后主动断开连接。现在如果我们继续使用服务器关闭的连接会报什么错误呢?

当前访问数据库但服务器已主动断开连接时会报什么错误?

错误类型

mysql当客户端管理数据库时,打开终端窗口。如果一段时间没有任何操作,重启时经常会报如下错误:

  • ERROR 2013 (HY000): The connection to the MySQL server was gone during the query
  • ERROR 2006 (HY000): The MySQL服务器离线。正在尝试重新连接...

好吧,让我们看看这两个错误如何匹配pymysql

2013 中的异常类型 错误代码对应什么异常类型?

首先看第一个2013
constants/CR.py

CR_SERVER_LOST = 2013

connections.py

raise err.OperationalError(
    CR.CR_SERVER_LOST,
    "Lost connection to MySQL server during query",
)

可以看到对应的错误类型为OperationalErr 或者

错误代码2006对应什么类型的异常?

查看2006
constants/CR.py

CR_SERVER_GONE_ERROR = 2006

connections.py

raise err.OperationalError(
    CR.CR_SERVER_GONE_ERROR, "MySQL server has gone away (%r)" % (e,)
)

可以看到对应的错误类型为OperationalError 操作上意味着什么?

看有道翻译的解释

操作性
英语 [ˌɒpəˈreɪʃənl] US [ˌɑːpəˈreɪʃənl]
adj. (机器、设备等)正常运转、可用;经营、业务、业务;军事行动;操作理论,操作主义

从这个翻译来看,这一类异常是一类非常广泛的异常


尝试一下

实验环境如下:

(twitter) ╭─bot@mbp13m1.local ~/Desktop/code/python/twitter  ‹main*› 
╰─➤  python --version                                                   
Python 3.9.7
(twitter) ╭─bot@mbp13m1.local ~/Desktop/code/python/twitter  ‹main*› 
╰─➤  pip show django                   
Name: Django
Version: 3.2.9
Summary: A high-level Python Web framework that encourages rapid development and clean, pragmatic design.
Home-page: https://www.djangoproject.com/
Author: Django Software Foundation
Author-email: foundation@djangoproject.com
License: BSD-3-Clause
Location: /Users/bot/.local/share/virtualenvs/twitter-73pfN55g/lib/python3.9/site-packages
Requires: asgiref, pytz, sqlparse
Required-by: django-filter, django-oss-storage, djangorestframework

查看和更改默认的 time_out 参数

Mysql主动关闭非活动连接的阈值由变量time_out决定。? :服务器在关闭交互式连接之前等待活动的秒数,这是您在本地计算机上打开 mysql 客户端所使用的 cmd 类型

要更改 time_out 的值你可以执行以下语句:

set global wait_timeout=10;

要查看 Django 会抛出什么错误,请将其设置为较短的 10 秒。
启动Django,然后先执行查询(确保连接到数据库),然后等待10秒再执行

Django什么时候连接到数据库?
通过抓包 Wireshark 数据包并观察会话管理可以确认以下情况:
python manage.py shell 使用 python manage.py

shell 时,不会连接数据库,导入模型时也不会连接到数据库。只有真正的CRUD完成后,才会连接数据库,而且是长连接
如何查看会话管理:?

SELECT * FROM `information_schema`.`PROCESSLIST` P

连接管理

Tweet.objects.all()

正如预期,报告错误

~/.local/share/virtualenvs/twitter-jSQKCUr2/lib/python3.9/site-packages/pymysql/connections.py in _read_query_result(self, unbuffered)
    773         else:
    774             result = MySQLResult(self)
--> 775             result.read()
    776         self._result = result
    777         if result.server_status is not None:

~/.local/share/virtualenvs/twitter-jSQKCUr2/lib/python3.9/site-packages/pymysql/connections.py in read(self)
   1154     def read(self):
   1155         try:
-> 1156             first_packet = self.connection._read_packet()
   1157 
   1158             if first_packet.is_ok_packet():

~/.local/share/virtualenvs/twitter-jSQKCUr2/lib/python3.9/site-packages/pymysql/connections.py in _read_packet(self, packet_type)
    699                 if packet_number == 0:
    700                     # MariaDB sends error packet with seqno==0 when shutdown
--> 701                     raise err.OperationalError(
    702                         CR.CR_SERVER_LOST,
    703                         "Lost connection to MySQL server during query",

OperationalError: (2013, 'Lost connection to MySQL server during query')

重试并查看重新连接是否有效。

~/.local/share/virtualenvs/twitter-jSQKCUr2/lib/python3.9/site-packages/django/db/backends/mysql/base.py in execute(self, query, args)
     71         try:
     72             # args is None means no string interpolation
---> 73             return self.cursor.execute(query, args)
     74         except Database.OperationalError as e:
     75             # Map some error codes to IntegrityError, since they seem to be

~/.local/share/virtualenvs/twitter-jSQKCUr2/lib/python3.9/site-packages/pymysql/cursors.py in execute(self, query, args)
    146         query = self.mogrify(query, args)
    147 
--> 148         result = self._query(query)
    149         self._executed = query
    150         return result

~/.local/share/virtualenvs/twitter-jSQKCUr2/lib/python3.9/site-packages/pymysql/cursors.py in _query(self, q)
    308         self._last_executed = q
    309         self._clear_result()
--> 310         conn.query(q)
    311         self._do_get_result()
    312         return self.rowcount

~/.local/share/virtualenvs/twitter-jSQKCUr2/lib/python3.9/site-packages/pymysql/connections.py in query(self, sql, unbuffered)
    545         if isinstance(sql, str):
    546             sql = sql.encode(self.encoding, "surrogateescape")
--> 547         self._execute_command(COMMAND.COM_QUERY, sql)
    548         self._affected_rows = self._read_query_result(unbuffered=unbuffered)
    549         return self._affected_rows

~/.local/share/virtualenvs/twitter-jSQKCUr2/lib/python3.9/site-packages/pymysql/connections.py in _execute_command(self, command, sql)
    791         """
    792         if not self._sock:
--> 793             raise err.InterfaceError(0, "")
    794 
    795         # If the last query was unbuffered, make sure it finishes before

InterfaceError: (0, '')

好吧,不,Django没有帮我们重新连接,只是错误类型不同而已。无论执行多少次Tweet.objects.all(),结果都是上面的InterfaceError: (0, '')

解决方案-重新连接

Django提供close_old_connections方法

from django.db import close_old_connections
from django.db.utils import OperationalError

def job(self):
    try:
        something()
    except OperationalError as error:
        self.logger.error(f'job error: {error}', exc_info=True)
        close_old_connections()
    except Exception as error:
        self.logger.error(f'job error: {error}', exc_info=True)

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门