返回

Peewee 升级后自动提交异常及解决

mysql

Peewee 版本升级引发的自动提交异常行为

在应用程序中,数据库操作的正确事务处理至关重要。当使用 Peewee 这样的 ORM 框架时,其版本更新有时会引入行为上的改变,特别是在事务和自动提交处理方面。本文将探讨 Peewee 版本升级,尤其是从 2.x 迁移到 3.17.x 后,遇到的自动提交行为异常问题。我们会分析其根本原因,并提供相应的解决方案。

问题分析

旧版 Peewee (2.x) 数据库连接类中,可能存在类似下面的自定义 begin 方法,明确设置了 autocommit=0

from peewee import MySQLDatabase

class SQLDb(MySQLDatabase):
    def connect(self, *args, **kwargs):
        attempts = 5
        while attempts > 0:
            # 尝试建立数据库连接的逻辑

    def begin(self):   
        self.execute_sql('set autocommit=0')
        self.execute_sql('begin')

这段代码显式地将数据库的自动提交模式关闭,然后在开启事务时执行 begin 语句。这样的实现依赖于 MySQL 的原生行为:在事务外执行的任何命令,如果自动提交模式关闭,都需要显式的 commit 操作才能生效。

升级到 Peewee 3.17.x 后,开发者可能观察到,在显式事务(如通过 DB.transaction() 创建)完成后,未在事务中进行的数据库更改却不会被提交,必须显式执行commit命令。这个现象的根源在于 Peewee 3.16 对自动提交处理方式的修改,旧的设置可能与新的默认行为发生冲突。特别是在 Peewee 的连接和事务处理中可能隐藏了自动提交的行为。Peewee 的事务管理可能不会按用户预期的关闭/打开 autocommit 功能。因此,显式的 autocommit 设置在新的版本中不再有预期的效果,或者与其他自动提交逻辑冲突,反而导致意想不到的结果。

解决方案

方案一:移除显式 autocommit=0 设置

最直接的解决方案是从自定义的 begin 方法中移除 self.execute_sql('set autocommit=0') 语句,并让 Peewee 完全控制 autocommit 的行为:

from peewee import MySQLDatabase

class SQLDb(MySQLDatabase):
    def connect(self, *args, **kwargs):
        attempts = 5
        while attempts > 0:
            # 尝试建立数据库连接的逻辑

    def begin(self):   
        # 移除显式 autocommit 设置
        self.execute_sql('begin')

移除后,Peewee 会在其内部处理 autocommit 模式。显式的事务(如 with database.transaction() as txn: ) 会确保在一个事务中的所有操作最终执行commit或者rollback,而非在所有命令上都强制使用提交操作。在非事务操作中,MySQL的行为又恢复了默认模式。这通常足以解决大多数场景下的问题。

操作步骤:

  1. 修改 SQLDb 类,移除 self.execute_sql('set autocommit=0') 这行代码。
  2. 重新部署应用程序。

方案二:手动控制 autocommit(仅限特殊场景)

如果出于某些特定需求,你需要在应用程序中显式控制 autocommit 行为(通常不推荐),你可以在数据库连接时手动设置初始 autocommit 值,并且需要手动在非事务性 SQL 语句之后提交数据更改。这在复杂的数据库操作模式,或者一些需要直接干预自动提交的特殊情况下可能有其应用场景。例如, 在程序执行大量只读操作的时候关闭 autocommit 功能来获得更高的性能。但这需要开发人员更加清晰的理解autocommit模式的工作原理以及清楚需要执行commit的地方,所以风险相对更高。

代码示例:

在自定义连接方法中,设置初始化 autocommit 值。

from peewee import MySQLDatabase
class SQLDb(MySQLDatabase):
    def connect(self, *args, **kwargs):
        attempts = 5
        while attempts > 0:
           # Logic to retry db connection
        # Set default autocommit behavior when connection first created. 
        # Remember that connection has transaction manager logic now 
        # You might need to explictly do commit even for a SQL cmd with no transactions
        # Default for all transactions are as default database configurations.

        # set autocommit = 1 for operations which run out of tranasctions.

        self.execute_sql('set autocommit=1') # for non transaction usage we would commit on every cmd

    def begin(self):
        # Here just keep what is required. As set autocommit has moved up 
       self.execute_sql('begin')

对于非事务性的数据库修改操作, 还需要添加显示 commit 操作:

def non_transaction_function(db,sql_string):
   with db.get_conn() as conn:
      db.execute_sql(sql_string) # use it inside connection context or it would rollback for peewee
      db.execute_sql('commit')

操作步骤:

  1. 修改 SQLDb 类中的 connect 方法,添加 autocommit 设置的逻辑。
  2. 在进行事务以外的数据变更后,调用显式的 commit() 或者类似的方法。
  3. 重新部署应用程序,仔细测试各项数据变更功能。

额外建议

  1. 阅读版本更新说明: 在升级任何软件或库时,都应该仔细阅读其更新日志和发布说明,尤其要注意关于行为改变或弃用警告的声明。这样可以提前预判可能的问题。
  2. 单元测试: 对于关键的数据库操作部分编写详细的单元测试,覆盖各种场景,以便尽早发现自动提交异常问题。
  3. 最小化更改: 在版本升级时,尽量保持原有代码逻辑,除非新版本有强制更改。不必要的更改可能会导致其他问题。

通过仔细分析问题根源,结合提供的解决方案,可以有效地解决 Peewee 版本升级带来的自动提交异常问题,并保证应用程序的稳定性和可靠性。