如果我使用带有 pyodbc 游标的上下文管理器,它会在出现错误时回滚吗?

If I use a context manager with a pyodbc cursor, will it rollback on an error?

我尝试阅读 pyodbc 源代码,但它全是 C++ 代码(我不擅长 C++)。我需要知道如下语句的行为:

 with connection.cursor() as cursor:
     cursor.execute(query_1) #inserts some stuff into table A
     cursor.execute(query_2) #inserts some stuff into table B, but throws an error

 with connection.cursor() as cursor2:
     cursor.execute(select_query_1) #selects from table A
     cursor.execute(select_query_2) #selects from table B

这与我们尚未提交的连接相同 - 我很好奇从 table A 中选择是否会给出插入第一个游标的新值 - 或者 [= 中的错误是否15=] 导致第一个游标的工作回滚 table A.

查看 source, and the ODBC Documentation 行为部分取决于 autocommit 是启用还是禁用。

  • 如果 autocommitFalse,则对 cursor.execute() 的第一次调用隐含地打开一个新事务。 (请参阅注释 1)此游标对 execute() 的每个后续调用都使用相同的事务,除非调用 commit()rollback()
  • 当 Python 离开 with 块并调用 __exit__ 时:
    • 如果autocommitFalse,并且没有错误,游标会自动提交事务。此操作的来源见注释2。
    • 如果autocommitTrue,或者出现错误,则游标退出而不结束事务。
  • 当使用 cursor.close() 或显式或隐式调用 __del__ 关闭游标时,释放游标的内部句柄时,挂起的语句结果也会自动删除。 (注3)

如果cursor.execute() 失败,事务仍处于打开状态,但未提交。在这种情况下,由您决定是提交还是回滚。

尽管如此,您仍然应该在目标环境中测试行为。不同的ODBC数据源有不同级别的事务支持。


注1:(source)

If the data source is in manual-commit mode (requiring explicit transaction initiation) and a transaction has not already been initiated, the driver initiates a transaction before it sends the SQL statement.

注2:(pyodbc/cursor.cpp @ 2151)

// If an error has occurred, `args` will be a tuple of 3 values.  
// Otherwise it will be a tuple of 3 `None`s.
I(PyTuple_Check(args));
if (cursor->cnxn->nAutoCommit == SQL_AUTOCOMMIT_OFF && PyTuple_GetItem(args, 0) == Py_None)
    ...
    ret = SQLEndTran(SQL_HANDLE_DBC, cursor->cnxn->hdbc, SQL_COMMIT);

注3:(source

When an application calls SQLFreeHandle to free a statement that has pending results, the pending results are deleted.

pyodbc 不会自动为您处理事务。

这意味着即使 query_2 失败,select_query_1 也会看到由 query_1 插入的记录。 (我假设 try/catch 在第一个代码块周围,所以第二个代码块将被执行)。 但是,某些 RDBMS,即 PostgreSQL,如果同一事务中的先前语句之一失败,则不允许执行任何其他语句(回滚除外)。对于 PostrgreSQL RDBMS(例如)且没有自动提交,如果 query_2 失败,select_query_1 将失败。

根据我使用 pyodbc 连接(使用 Microsoft Access 驱动程序)的经验:

假设自动提交是False(这似乎是默认的)

不提交:

with pyodbc.connect(connectionString) as con:

    with con.cursor() as cursor:

        cursor.execute(query)
        raise Exception('failed')

是否提交:

with pyodbc.connect(connectionString) as con:

    with con.cursor() as cursor:

        cursor.execute(query)

    raise Exception('failed')

不提交:

with pyodbc.connect(connectionString) as con:

    cursor = con.cursor()
    cursor.execute(query)
    cursor.close()

    raise Exception('failed')

是否提交:

with pyodbc.connect(connectionString) as con:

    cursor = con.cursor()
    cursor.execute(query)
    cursor.close()

raise Exception('failed')