我可以在没有事务的情况下通过 sqlalchemy 执行查询吗

Posted

技术标签:

【中文标题】我可以在没有事务的情况下通过 sqlalchemy 执行查询吗【英文标题】:Can I execute query via sqlalchemy without transaction 【发布时间】:2019-12-02 19:58:25 【问题描述】:

我正在尝试使用 sqlalchemy 在 mysql 数据库上执行存储过程。

它在 shell 中运行良好,但抛出此错误:

OperationalError: (MySQLdb._exceptions.OperationalError) (1568, "Transaction characteristics can't be changed while a transaction is in progress")

原因似乎是 SQLAlchemy 在事务中运行查询。并且存储过程中的事务与之冲突。 以下是 sqlalchemy 日志:

2019-07-24 15:20:28,888 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'sql_mode'
2019-07-24 15:20:28,888 INFO sqlalchemy.engine.base.Engine ()
2019-07-24 15:20:28,900 INFO sqlalchemy.engine.base.Engine SELECT DATABASE()
2019-07-24 15:20:28,900 INFO sqlalchemy.engine.base.Engine ()
2019-07-24 15:20:28,910 INFO sqlalchemy.engine.base.Engine show collation where `Charset` = 'utf8mb4' and `Collation` = 'utf8mb4_bin'
2019-07-24 15:20:28,910 INFO sqlalchemy.engine.base.Engine ()
2019-07-24 15:20:28,916 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS CHAR(60)) AS anon_1
2019-07-24 15:20:28,917 INFO sqlalchemy.engine.base.Engine ()
2019-07-24 15:20:28,923 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS CHAR(60)) AS anon_1
2019-07-24 15:20:28,923 INFO sqlalchemy.engine.base.Engine ()
2019-07-24 15:20:28,928 INFO sqlalchemy.engine.base.Engine SELECT CAST('test collated returns' AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_bin AS anon_1
2019-07-24 15:20:28,928 INFO sqlalchemy.engine.base.Engine ()
2019-07-24 15:20:28,938 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2019-07-24 15:20:28,938 INFO sqlalchemy.engine.base.Engine CALL my_stored_procedure(params);
2019-07-24 15:20:28,938 INFO sqlalchemy.engine.base.Engine ()

我想知道的是我是否可以在没有事务的情况下从 sqlalchemy 运行查询。或者有没有其他方法可以解决问题。我尝试更改存储过程的隔离级别,但这会导致表锁定问题。

【问题讨论】:

【参考方案1】:

SQLAlchemy 总是尝试在事务中执行查询。但是,可以通过执行COMMIT 语句轻松结束交易。

首先,您需要一个连接。然后使用该连接发出COMMIT,这将结束新启动的事务。

这是一个尝试创建新数据库的示例代码,在事务内部运行时会抛出错误。我正在使用 postgres,但使用 MySQL 的相同逻辑也适用。尝试在事务中创建新数据库:

from sqlalchemy import create_engine

# replace URL with your MySQL instance
db_url = "postgresql://postgres:secure_pass@localhost:5432/template1"
engine = create_engine(db_url)

connection = engine.connect()

# running this query in transaction throws an error
result = connection.execute("CREATE DATABASE temp_db")

会抛出错误:

ERROR: CREATE DATABASE cannot run inside a transaction block

现在,添加 COMMIT 将结束由 sqlalchemy 启动的事务:

from sqlalchemy import create_engine

# replace url with your MySQL instance
db_url = "postgresql://postgres:secure_pass@localhost:5432/template1"
engine = create_engine(db_url)

connection = engine.connect()

# commiting will end a transaction
connection.execute("COMMIT")

# now this query runs fine
result = connection.execute("CREATE DATABASE temp_db")

并且没有引发错误。

【讨论】:

这太棒了——而且非常干净!请注意,对于使用Session 对象的任何人,它看起来就像session.connection().execute("COMMIT") 后跟session.connection().execute("CREATE ...") - 即使使用底层连接执行COMMIT,尝试执行session.execute("CREATE ...") 仍然会失败 在使用这种方法进行非事务性调用后,请务必添加session.begin()。如果你之后继续使用会话,我认为你可能会因为没有事务操作而遇到一些问题:) docs.sqlalchemy.org/en/13/orm/… 这个错误对我来说——即使紧跟在session.connection().execute("COMMIT") 之后,尝试session.begin() 也会导致sqlalchemy.exc.InvalidRequestError: A transaction is already begun. Use subtransactions=True to allow subtransactions.。我认为当他们说session 对象总是透明地使用事务时,这就是链接的文档的意思?不过,也许我做错了什么 - 我对 SQLAlchemy 很糟糕。 啊,你完全正确 - 我的错,我无法尝试我写的内容,由于我的误解而发布了这个。实际上,在conn.execute("COMMIT") 对该会话的任何影响事务状态的操作之后,都会根据需要悄悄地启动一个新事务。所以是的 - 在此类提交之后继续使用该会话可能是安全的。 完美!我开始认为这是我的用户错误哈哈

以上是关于我可以在没有事务的情况下通过 sqlalchemy 执行查询吗的主要内容,如果未能解决你的问题,请参考以下文章

SQLAlchemy多线程下事务隔离机制详解

SQLAlchemy 没有事务开始错误

在没有事务的情况下运行 EF 核心迁移?

如何在没有ORM的情况下使用SQLAlchemy查询从表中删除行?

python MySQLdb:在没有证书的情况下使用ssl

如何检查PostgreSQL事务中的挂起操作