MySql数据库断连
在使用 MySql 数据库时,如果使用了长连接或连接池,可能会遇到数据库连接断开的情况。
收到的错误信息例如:
MySQLdb.OperationalError: (2013, 'Lost connection to MySQL server during query')
原因#
发生这种情况的原因是 MySql 客户端和服务器端设置的连接时常不一致导致。
服务器端超时#
在 MySql 的服务器端,可以设置连接的等待时长,即连接在长时间不活跃时,主动关闭该连接。
https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_wait_timeout https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_interactive_timeout
可以通过下面的语句来动态设置等待时长:
SET GLOBAL interactive_timeout=600;
SET GLOBAL wait_timeout=600;
其中 interactive_timeout 是针对交互式连接的等待时长,wait_timeout 是针对非交互式连接的等待时长。
客户端超时#
通常在长连接或连接池的设置中,可以设置连接的时长,即连接在创建后多长时间后就销毁重新建立新连接。
不同的客户端配置项不一样,需要参考对应的文档进行配置。
例如:Django 是通过 CONN_MAX_AGE 来设定连接的 https://docs.djangoproject.com/en/5.1/ref/settings/#conn-max-age
当客户端设定的时间比服务器端长的时候,服务器会先关闭连接,这时客户端还认为连接可用,仍然使用该连接进行查询,就会产生断连的异常。
解决方案#
SqlAlchemy 文档中很好的描述了应对数据库断连的几种方法 https://docs.sqlalchemy.org/en/20/core/pooling.html#dealing-with-disconnects
正确设置客户端超时时间#
将客户端超时时间设置为小于服务器端的超时时间,避免出现两端不一致的情况。
健康检测#
成熟的客户端会有配置,在连接复用之前,先检测一下是否可用。
Django 的连接健康检测默认是关闭的 https://docs.djangoproject.com/en/5.1/ref/settings/#conn-health-checks
SqlAlchemy 可用通过设置 Pool.pre_ping 参数开启健康检测 https://docs.sqlalchemy.org/en/20/core/pooling.html#disconnect-handling-pessimistic
失败重试#
另一种方案就是在遇到连接断开时,重新建立连接并重新进行请求。
下面代码是从网上拷贝的一个简单的 Django 进行失败重试的代码:
from django.db.backends.mysql import base
def check_mysql_gone_away(db_wrapper):
def decorate(func):
def wrapper(self, query, args=None):
try:
return func(self, query, args)
except (base.Database.OperationalError, base.Database.InterfaceError) as e:
if 'MySQL server has gone away' in str(e)\
or 'Lost connection' in str(e)\
or type(e) == base.Database.InterfaceError:
db_wrapper.connection.close()
db_wrapper.connect()
self.cursor = db_wrapper.connection.cursor()
return func(self, query, args)
# Map some error codes to IntegrityError, since they seem to be
# misclassified and Django would prefer the more logical place.
if e.args[0] in self.codes_for_integrityerror:
raise base.utils.IntegrityError(*tuple(e.args))
raise
return wrapper
return decorate
class DatabaseWrapper(base.DatabaseWrapper):
def create_cursor(self, name=None):
class CursorWrapper(base.CursorWrapper):
@check_mysql_gone_away(self)
def execute(self, query, args=None):
return self.cursor.execute(query, args)
@check_mysql_gone_away(self)
def executemany(self, query, args):
return self.cursor.executemany(query, args)
cursor = self.connection.cursor()
return CursorWrapper(cursor)
请求包过大#
MySql 有一个最大包的限制,当请求包超过这个值的时候,MySql 服务器端也会主动关闭连接。
官方文档:https://dev.mysql.com/doc/refman/5.7/en/packet-too-large.html
遇到这种情况需要调整客户端和服务器端的配置相匹配,健康检查和重试都无法解决这个问题。