Python数据库游标异常时机

1 投票
2 回答
3110 浏览
提问于 2025-04-16 18:51

我正在创建一个类,用来管理与Firebird数据库的连接。为了方便多个连接到数据库,Firebird服务会被安装。不过,我的软件可能会在一些不稳定的环境中运行,我不能总是保证在我尝试连接时,Firebird服务是运行着的,或者在我建立连接后,它会继续运行。

为了集中处理错误,我决定我的代码的不同部分不会直接与数据库游标打交道。相反,我会通过我的连接管理器提供 query()dml() 方法。这样做在一定程度上是有效的,下面的代码展示了这一点(为了简洁,有些代码没有包含)。

class DBConnection(object):
  # self._conn is an instance of kinterbasdb.connect()
  def query(self, query, params = None):
    cursor = self._conn.cursor()

    if params:
      cursor.execute(query, params)
    else:
      cursor.execute(query)

    return [[x[0].title() for x in cursor.description]] + [r for r in cursor.fetchall()]

  def dml(self, query, params = None):
    cursor = self._conn.cursor()

    if params:
      cursor.execute(query, params)
    else:
      cursor.execute(query)

    self._conn.commit()

问题出现在Firebird服务停止或因为某种原因无法访问时。我本以为 self._conn.cursor() 会抛出一个异常,这样我就可以简单地处理错误,比如这样:

class DBConnection(object):
  # self._conn is an instance of kinterbasdb.connect()
  def cursor(self):
    try:
      return self._conn.cursor()
    except:
      # Error handling code here, possibly reconnect, display alert.

  def query(self, query, params = None):
    cursor = self.cursor()

  def dml(self, query, params = None):
    cursor = self.cursor()

不幸的是,当我请求游标时,并没有抛出异常。直到我调用 cursor.execute() 时,我才意识到出了问题。这意味着,如果我想正确集中处理错误,我必须这样做:

class DBConnection(object):
  # self._conn is an instance of kinterbasdb.connect()
  def cursor(self):
    try:
      cursor = self._conn.cursor()

      cursor.execute("Select NULL From <sometable>")

      return cursor
    except:
      # Error handling code here, possibly reconnect, display alert.

这需要额外一次与数据库的交互,浪费了一次事务(Firebird数据库对数据库生命周期内的总事务有严格的上限),而且总的来说感觉不太对。我在想,是否有人在其他Python数据库API的实现中遇到过类似的问题,如果有,他们是如何解决的?

2 个回答

0

Firebird数据库的连接在检测连接状态时和普通的TCP/IP连接一样,会遇到一些问题。也就是说,连接在没有使用的时候,很难判断它是否已经“死掉”了。在TCP/IP的世界里,解决这个问题的方法包括使用KeepAlives(这仍然需要大约15分钟来检测连接状态)和明确的心跳信号。你发送查询来检测连接状态,其实就像是在发一个心跳信号,看看连接是否还活着。

当你在一个游标上执行SQL语句时,如果此时连接已经断开,你会在那一刻收到异常错误。检测连接失败的合适时机是在使用连接的时候。虽然集中处理错误是个不错的目标,但如果按照上面的方式实现,你仍然可能在返回一个有效的游标对象和后续执行之间失去与Firebird的连接(也就是说,你在dml()函数中的execute()调用时仍然可能会失败)。

1

我正在测试对我的类进行的一些修改,我觉得这样可以实现我想要的集中处理,同时代码重复的部分也会减少。这些修改还稍微简化了查询和数据操作的方法,并且消除了我想避免的额外查询(心跳查询)。

class DBConnection(object):
  # self._conn is an instance of kinterbasdb.connect()
  def query(self, query, params = None):
    cursor = self._conn.cursor()

    self.execute(cursor, query, params)

    return [[x[0].title() for x in cursor.description]] + 
            [r for r in cursor.fetchall()]

  def dml(self, query, params = None):
    cursor = self._conn.cursor()

    self.execute(cursor, query, params)

    self._conn.commit()

  def execute(self, cursor, query, params = None):
    try:
      if params:
        cursor.execute(query, params)
      else:
        cursor.execute(query)
    except Exception, e:
      # Handling

撰写回答