我正在构建一个 python 应用程序,其目的是循环轮询 Microsoft SQL Server 数据库(每 100-200 毫秒)以获取 STATUS = 10 的新行。 当查询有结果时,应立即设置STATUS = 15,并根据查询到的数据进行处理。数据处理后,它应该将状态更新为 20。第二个描述的更新发生在另一个线程中,该线程与同一 SQL Server 有单独的连接。
选择并更新至状态 15:
def _pop_from_db(self):
if self.db_connection: # connection was established with self.db_connection = pyodbc.connect("connection_string)
oldest_update_datum_allowed = self.start_up_time_from_db_server + timedelta(minutes=0)
try:
with self.db_connection.cursor() as cursor:
cursor.execute(
"""
SELECT TOP 1 Id, Number, Status FROM table
WHERE UpdateDatum > ? AND Status = ? ORDER BY Id
""",
[oldest_update_datum_allowed, 10])
row = cursor.fetchone()
columns = [column[0] for column in cursor.description]
if row:
m = dict(zip(columns, row))
cursor.execute(
"""
UPDATE table SET Status=? WHERE Id=?
""",
[15, m["Id"]]
)
self.db_connection.commit()
rowcount = cursor.rowcount
logging.debug(f"Successfully updated Status for id {m['Id']} to {15} (rowcount {rowcount}).")
return m
return None
except pyodbc.Error as e:
logging.error(traceback.format_exc())
logging.error("Error while querying db. Wait 1 second and try to reconnect")
sleep(1)
self._connect_db_server()
return None
else:
logging.error(f"No db connection. Wait 1 second and try to reconnect")
sleep(1)
self._connect_db_server()
return None
更新到状态20(不同类和不同线程):
try:
with self.db_connection2.cursor() as cursor:
cursor.execute(
"""
UPDATE table SET Status=?, WHERE Id=?;
""",
[20, id]
)
self.db_connection2.commit()
rowcount = cursor.rowcount
logging.debug(f"Successfully updated Status for id {id} to {20} (rowcount {rowcount}).")
except pyodbc.OperationalError as e:
logging.error(f"Exception while updating status for id {id} to {20}.")
self._connect_db_server()
更新状态并不总是有效。
大约有二百次之一的情况下,两种状态更新之一都不起作用。我的应用程序代码中没有收到任何错误消息或异常。而且返回的cursor.rowcount 始终为1。但在数据库中没有任何变化。状态保持更新声明之前的状态。
有什么想法吗,这种奇怪的行为可能来自哪里?关于如何改进实施有什么建议吗?
数据库表定义了一些触发器。当状态发生变化时,这些会更新更新行的时间戳字段。
我尝试了不同的方法,例如在中央线程上同步所有查询,并在更新到 15 时仅更新状态为 10 的情况,在更新到 20 时更新状态为 15 的情况......但没有任何效果。
然后我使用了 SQL Server Management Studio 中的 SQL Server Profiler。我在那里过滤了给定表中的更新。即使发生错误,我也可以看到所有更新都正常工作。
因此,从分析器视图来看,更新似乎有效。
我明白了
SQL:BatchCompleted
。问题:SQL Profiler 有没有办法查看是否有回滚发生?
因此,经过一番艰苦的研究,我在这里找到了有关情况的更多详细信息以及我希望的(最终)解决方案:
前面几句话:
SQL Server 实例不在我的控制之下。该表不是由以下人员创建和配置的。它由另一家公司控制。这是另一家公司提出的接口,我必须将其实现到我的软件中。
为了获得有关正在发生的事情的更多详细信息,我决定使用 SQL Profiler(我猜更现代的方法是 SQL Server 中的扩展事件。但是我必须处理的 SQL Server 实例是没有扩展的旧版本)活动)。
在弄清楚如何过滤服务器上发生的大量消息(这是控制生产的工厂中非常繁忙的 SQL Server 实例)之后,我找到了一个设置,它为我提供了更多信息。
在 SQL Profiler 中,每个更新查询均使用
SQL:BatchStarting
和 SQL:BatchCompleted
进行记录。他们之间总是发生事务(开始和提交)。在 UPDATE 查询未按预期工作的情况下,我注意到这些事务的事务回滚。pyodbc中设置了
autocommit = True
。交易量减少了,我感觉错误发生的次数比以前少了。但有时在 SQL:BatchStarting
和 SQL:BatchCompleted
之间的事务回滚中仍然会发生这种情况。我禁用了插入日志表并删除旧日志消息的触发器。这似乎解决了问题。系统现已运行超过 36 小时,更新状态超过 30,000 次,没有出现任何错误。
我不知道为什么
SQL:BatchStarting
和SQL:BatchCompleted
之间仍然存在SQL事务。但我对结果很满意。
最终这不是 pyodbc、python 或我糟糕的实现的问题。错误发生在服务器端。