多文件 Python 项目,一个文件一段时间后无法连接到 SQL 服务器
Multi-file Python project, one file unable to connect to SQL Server after a while
我有一个多文件 Python 项目,其中许多文件都连接到 Azure SQL 数据库。该项目运行良好,但由于某种原因,其中一个文件在应用程序 运行 一段时间后无法连接到数据库,我看不出原因;特别是当其他连接尝试正常时。
所有连接(所有文件)的连接字符串定义如下:
SQLServer = os.getenv('SQL_SERVER')
SQLDatabase = os.getenv('SQL_DATABASE')
SQLLogin = os.getenv('SQL_LOGIN')
SQLPassword = os.getenv('SQL_PASSWORD')
SQLConnString = 'Driver={ODBC Driver 17 for SQL Server};Server=' + SQLServer + ';Database=' + SQLDatabase + ';UID='+ SQLLogin +';PWD=' + SQLPassword
sqlConn = pyodbc.connect(SQLConnString,timeout=20)
当错误发生时,我正在调用的函数如下:
def iscaptain(guild,user):
userRoles = user.roles
roleParam = ""
for role in userRoles:
roleParam = roleParam + "," + str(role.id)
cursor = sqlConn.cursor()
roleParam = roleParam[1:]
cursor.execute('EXEC corgi.GetUserAccess ?, ?;',guild.id,roleParam)
for row in cursor:
if row[1] == "Team Captain":
cursor.close()
return True
cursor.close()
return False
错误特别发生在cursor.execute
。我目前收到错误
pyodbc.OperationalError: ('08S01', '[08S01] [Microsoft][ODBC Driver 17 for SQL Server]TCP Provider: Error code 0x68 (104) (SQLExecDirectW)')
以前我没有在有问题的特定文件的连接中超时,我确实得到了一个不同的错误:
Communication link failure
抱歉,我没有完整的之前的错误。
其他连接,在同一项目的其他文件中,工作正常,所以问题是不是网络问题;如果是 none 连接就可以了。该问题仅发生在一个文件中,所有连接尝试均失败。
谷歌搜索最新的错误并没有让我走得太远。例如,有一个 Github issue that gets nowhere, and this question 与其他文件的连接作品无关。
另请注意,这会在一段时间后发生;我真的不知道 这段时间有多长,但肯定是几个小时。重新启动项目也解决了这个问题;上述功能将正常工作。但这并不是真正的解决方案,我不能一直临时重启应用程序。
错误也是立即发生的;好像 Python/PyODBC 不是 尝试 连接。进入 cursor.execute
后立即产生错误;这不像当您超时时要等待几秒钟或更长时间才能发生超时。
我在这里不知所措。为什么文件(只有那个文件)以后无法再连接了?数据库也没有锁,所以我没有挂起的事务;尽管我希望再次出现超时错误,因为该过程将无法锁定数据。
另请注意,如果我手动执行该过程,在 sqlcmd
/SSMS/ADS 中,数据也会正常返回,因此该过程可以正常工作。而且,如果我重新启动该应用程序,它将可以正常运行数小时。
编辑:我尝试了下面 Sabik 的回答,但是,不幸的是,这只破坏了应用程序。他们提供的解决方案在函数 validate_conn
上有参数 self
,因此调用 validate_conn()
失败,因为我没有这个“self
”的参数。他们说的方法,只是 validate_conn
没有做任何事情;它不调用该函数(这是我所期望的)。删除参数和对 self
的引用也破坏了应用程序,声明 sqlConn
没有声明,即使它声明了;请看下图,您可以清楚地看到 sqlConn
有一个值:
然而在那一行之后我立即收到以下错误:
UnboundLocalError: local variable 'sqlConn' referenced before assignment
所以他们的代码似乎有问题,但我不知道什么。
评论中讨论的一种可能性是数据库端有30分钟的空闲超时,在这种情况下,一种解决方案是记录连接打开的时间,如果超过则重新连接25分钟。
这将是这样一种方法:
def validate_conn(self):
if self.sqlConn is None or datetime.datetime.now() > self.conn_expiry:
try:
self.sqlConn.close()
except: # pylint: disable=broad-except
# suppress all exceptions; we're in any case about to reconnect,
# which will either resolve the situation or raise its own error
pass
self.sqlConn = pyodbc.connect(...)
self.conn_expiry = datetime.datetime.now() + datetime.timedelta(minutes=25)
(如果sqlConn
是全局的,请适当调整。)
在每个使用sqlConn
的函数的开头,先调用validate_conn
,然后自由使用连接
注意:这是抑制所有异常是合理的罕见情况之一;在任何情况下,我们都将重新连接到数据库,这要么会圆满解决问题,要么会引发自己的错误。
编辑: 如果 sqlConn
是全局变量,则需要在函数中这样声明:
def validate_conn():
global sqlConn, conn_expiry
if sqlConn is None or datetime.datetime.now() > conn_expiry:
try:
sqlConn.close()
except: # pylint: disable=broad-except
# suppress all exceptions; we're in any case about to reconnect,
# which will either resolve the situation or raise its own error
pass
sqlConn = pyodbc.connect(...)
conn_expiry = datetime.datetime.now() + datetime.timedelta(minutes=25)
作为不相关的样式注释,编写函数的更短方法是使用 (a) with
语句和 (b) any
运算符,如下所示:
with sqlConn.cursor() as cursor:
roleParam = roleParam[1:]
cursor.execute('EXEC corgi.GetUserAccess ?, ?;', guild.id, roleParam)
return any(row[1] == "Team Captain" for row in cursor)
with
语句的优点是无论代码如何退出,游标都保证关闭,即使出现意外异常或以后的修改添加了更多分支。
尽管 Sabik 的解决方案对我不起作用,但答案确实将我推向了正确的方向以找到解决方案。具体来说,就是使用 with
子句。
我没有像我被告知的那样拥有持久的连接,而是现在将这些连接更改为短暂的连接,我用 with
打开,然后还将光标更改为with
还有。因此,对于 iscaptain
函数,我现在的代码如下所示:
def iscaptain(guild,user):
userRoles = user.roles
roleParam = ""
for role in userRoles:
roleParam = roleParam + "," + str(role.id)
#sqlConn = pyodbc.connect(SQLConnString,timeout=20)
with pyodbc.connect(SQLConnString,timeout=20) as sqlConn:
with sqlConn.cursor() as cursor:
roleParam = roleParam[1:]
cursor.execute('EXEC corgi.GetUserAccess ?, ?;', guild.id, roleParam)
return any(row[1] == "Team Captain" for row in cursor)
return False
似乎 Azure 在一段时间后关闭了连接,因此当尝试重新使用连接时连接失败。然而,由于两台主机都在 Azure 中,但服务器 运行 Python 应用程序和 SQL 数据库,我很乐意根据需要重新连接,因为速度应该不是一个大问题;当然在过去 48 小时的测试中还没有。
这确实意味着我有很多代码需要重构,但为了稳定性,这是必须的。
我有一个多文件 Python 项目,其中许多文件都连接到 Azure SQL 数据库。该项目运行良好,但由于某种原因,其中一个文件在应用程序 运行 一段时间后无法连接到数据库,我看不出原因;特别是当其他连接尝试正常时。
所有连接(所有文件)的连接字符串定义如下:
SQLServer = os.getenv('SQL_SERVER')
SQLDatabase = os.getenv('SQL_DATABASE')
SQLLogin = os.getenv('SQL_LOGIN')
SQLPassword = os.getenv('SQL_PASSWORD')
SQLConnString = 'Driver={ODBC Driver 17 for SQL Server};Server=' + SQLServer + ';Database=' + SQLDatabase + ';UID='+ SQLLogin +';PWD=' + SQLPassword
sqlConn = pyodbc.connect(SQLConnString,timeout=20)
当错误发生时,我正在调用的函数如下:
def iscaptain(guild,user):
userRoles = user.roles
roleParam = ""
for role in userRoles:
roleParam = roleParam + "," + str(role.id)
cursor = sqlConn.cursor()
roleParam = roleParam[1:]
cursor.execute('EXEC corgi.GetUserAccess ?, ?;',guild.id,roleParam)
for row in cursor:
if row[1] == "Team Captain":
cursor.close()
return True
cursor.close()
return False
错误特别发生在cursor.execute
。我目前收到错误
pyodbc.OperationalError: ('08S01', '[08S01] [Microsoft][ODBC Driver 17 for SQL Server]TCP Provider: Error code 0x68 (104) (SQLExecDirectW)')
以前我没有在有问题的特定文件的连接中超时,我确实得到了一个不同的错误:
Communication link failure
抱歉,我没有完整的之前的错误。
其他连接,在同一项目的其他文件中,工作正常,所以问题是不是网络问题;如果是 none 连接就可以了。该问题仅发生在一个文件中,所有连接尝试均失败。
谷歌搜索最新的错误并没有让我走得太远。例如,有一个 Github issue that gets nowhere, and this question 与其他文件的连接作品无关。
另请注意,这会在一段时间后发生;我真的不知道 这段时间有多长,但肯定是几个小时。重新启动项目也解决了这个问题;上述功能将正常工作。但这并不是真正的解决方案,我不能一直临时重启应用程序。
错误也是立即发生的;好像 Python/PyODBC 不是 尝试 连接。进入 cursor.execute
后立即产生错误;这不像当您超时时要等待几秒钟或更长时间才能发生超时。
我在这里不知所措。为什么文件(只有那个文件)以后无法再连接了?数据库也没有锁,所以我没有挂起的事务;尽管我希望再次出现超时错误,因为该过程将无法锁定数据。
另请注意,如果我手动执行该过程,在 sqlcmd
/SSMS/ADS 中,数据也会正常返回,因此该过程可以正常工作。而且,如果我重新启动该应用程序,它将可以正常运行数小时。
编辑:我尝试了下面 Sabik 的回答,但是,不幸的是,这只破坏了应用程序。他们提供的解决方案在函数 validate_conn
上有参数 self
,因此调用 validate_conn()
失败,因为我没有这个“self
”的参数。他们说的方法,只是 validate_conn
没有做任何事情;它不调用该函数(这是我所期望的)。删除参数和对 self
的引用也破坏了应用程序,声明 sqlConn
没有声明,即使它声明了;请看下图,您可以清楚地看到 sqlConn
有一个值:
UnboundLocalError: local variable 'sqlConn' referenced before assignment
所以他们的代码似乎有问题,但我不知道什么。
评论中讨论的一种可能性是数据库端有30分钟的空闲超时,在这种情况下,一种解决方案是记录连接打开的时间,如果超过则重新连接25分钟。
这将是这样一种方法:
def validate_conn(self):
if self.sqlConn is None or datetime.datetime.now() > self.conn_expiry:
try:
self.sqlConn.close()
except: # pylint: disable=broad-except
# suppress all exceptions; we're in any case about to reconnect,
# which will either resolve the situation or raise its own error
pass
self.sqlConn = pyodbc.connect(...)
self.conn_expiry = datetime.datetime.now() + datetime.timedelta(minutes=25)
(如果sqlConn
是全局的,请适当调整。)
在每个使用sqlConn
的函数的开头,先调用validate_conn
,然后自由使用连接
注意:这是抑制所有异常是合理的罕见情况之一;在任何情况下,我们都将重新连接到数据库,这要么会圆满解决问题,要么会引发自己的错误。
编辑: 如果 sqlConn
是全局变量,则需要在函数中这样声明:
def validate_conn():
global sqlConn, conn_expiry
if sqlConn is None or datetime.datetime.now() > conn_expiry:
try:
sqlConn.close()
except: # pylint: disable=broad-except
# suppress all exceptions; we're in any case about to reconnect,
# which will either resolve the situation or raise its own error
pass
sqlConn = pyodbc.connect(...)
conn_expiry = datetime.datetime.now() + datetime.timedelta(minutes=25)
作为不相关的样式注释,编写函数的更短方法是使用 (a) with
语句和 (b) any
运算符,如下所示:
with sqlConn.cursor() as cursor:
roleParam = roleParam[1:]
cursor.execute('EXEC corgi.GetUserAccess ?, ?;', guild.id, roleParam)
return any(row[1] == "Team Captain" for row in cursor)
with
语句的优点是无论代码如何退出,游标都保证关闭,即使出现意外异常或以后的修改添加了更多分支。
尽管 Sabik 的解决方案对我不起作用,但答案确实将我推向了正确的方向以找到解决方案。具体来说,就是使用 with
子句。
我没有像我被告知的那样拥有持久的连接,而是现在将这些连接更改为短暂的连接,我用 with
打开,然后还将光标更改为with
还有。因此,对于 iscaptain
函数,我现在的代码如下所示:
def iscaptain(guild,user):
userRoles = user.roles
roleParam = ""
for role in userRoles:
roleParam = roleParam + "," + str(role.id)
#sqlConn = pyodbc.connect(SQLConnString,timeout=20)
with pyodbc.connect(SQLConnString,timeout=20) as sqlConn:
with sqlConn.cursor() as cursor:
roleParam = roleParam[1:]
cursor.execute('EXEC corgi.GetUserAccess ?, ?;', guild.id, roleParam)
return any(row[1] == "Team Captain" for row in cursor)
return False
似乎 Azure 在一段时间后关闭了连接,因此当尝试重新使用连接时连接失败。然而,由于两台主机都在 Azure 中,但服务器 运行 Python 应用程序和 SQL 数据库,我很乐意根据需要重新连接,因为速度应该不是一个大问题;当然在过去 48 小时的测试中还没有。
这确实意味着我有很多代码需要重构,但为了稳定性,这是必须的。