在函数间共享MySQLdb事务

3 投票
3 回答
2053 浏览
提问于 2025-04-17 16:25

我正在用Python通过MySQLdb连接MySQL。我的所有表都是InnoDB类型,并且我在使用事务。

我在想怎么才能在不同的函数之间“共享”事务。考虑下面这个伪代码:

def foo():
    db = connect()
    cur = db.cursor()
    try:
        cur.execute(...)
        conn.commit()
    except:
        conn.rollback()

def bar():
    db = connect()
    cur = db.cursor()
    try:
        cur.execute(...)
        foo()  # note this call
        conn.commit()
    except:
        conn.rollback()

在我的代码中,有些地方我需要调用foo(),而有些地方我需要调用bar()。在这种情况下,最佳实践是什么呢?我该怎么告诉调用foo()的时候,如果是在bar()外面就执行commit(),但如果是在bar()里面就不执行呢?如果有多个线程同时调用foo()bar(),而且调用connect()时返回的连接对象不一样,那就更复杂了。

更新

我找到了一种适合我的解决方案。我对connect()进行了封装,每次调用时就增加一个计数值。调用commit()时会减少这个计数值。如果commit()被调用时这个计数值大于0,就不会执行提交,计数值会减少。这样你就得到了:

def foo():
    db = connect()  # internal counter = 1
    ...
    db.commit()  # internal counter = 0, so commit


def bar():
    db = connect()  # internal counter = 1
    ...
    foo()  # internal counter goes to 2, then to 1 when commit() is called, so no commit happens
    db.commit() # internal counter = 0, so commit

3 个回答

0

我觉得最简单的方法就是把连接对象直接传给 foobar 这两个函数。

0

在函数外部声明你的连接,然后把它们作为参数传递给函数。

foo(cur, conn)
bar(cur, conn)
0

在这种情况下,你可以利用Python的默认函数参数:

def foo(cur=None):
    inside_larger_transaction = False
    if cursor is None:
        db = connect()
        cur = db.cursor()
        inside_larger_transaction = True
    try:
        cur.execute(...)
        if not inside_larger_transaction:
             conn.commit()
    except:

        conn.rollback()

所以,如果bar在调用foo,它只需要把光标对象作为参数传进去。

我觉得为每个小函数创建一个不同的光标对象没什么意义——你可以把多个函数写成一个对象的方法,并且让这个对象有一个光标属性;或者总是明确地传递光标(在这种情况下,可以使用另一个命名参数来指示当前函数是否属于一个主要事务)。

另一种选择是创建一个上下文管理器类来处理你的提交,并把所有事务封装在里面——这样,你的函数就不需要执行事务提交了——你可以把transaction.commit和transaction.rollback的调用放在这个对象的__exit__方法里。

class Transaction(object):
    def __enter__(self):
       self.db = connect()
       cursor = self.db.cursor()
       return cursor
   def __exit__(self, exc_type, exc_value, traceback):
       if exc_type is None:
           self.db.commit()
       else:
           self.db.rollback()

然后就可以这样使用它:

def foo(cursor):
    ...

def foo(cur):
        cur.execute(...)


def bar(cur):
    cur.execute(...)
    foo(cur)

with Transaction() as cursor:
    foo(cursor)


with Transaction() as cursor:
    bar(cursor)

撰写回答