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

问题描述 投票:0回答:2

我正在尝试使用 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 运行查询。或者有没有其他方法可以解决问题。我尝试更改存储过程的隔离级别,但这导致了表锁定问题。

python mysql sqlalchemy flask-sqlalchemy
2个回答
13
投票

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")

并且不会出现任何错误。


0
投票

另一个答案不再适用于

sqlalchemy>=2
。同时适用于
sqlalchemy>=2
sqlalchemy>=1.4
的替代解决方案:

from sqlalchemy import create_engine

db_url = "postgresql://username:password@localhost:5432/database"
engine = create_engine(db_url)

with engine.connect().execution_options(isolation_level="AUTOCOMMIT") as connection
    connection.execute(sqlalchemy.text("VACUUM ANALYZE table_1"))
    connection.execute(sqlalchemy.text("VACUUM ANALYZE table_2"))

SQLalchemy 2 将不再接受原始字符串,因此必须用

sqlalchemy.text
包裹它。

vacuum
调用必须位于其自己的
execute
中。因此,不可能将多个查询放在同一个字符串中,或在其前面加上另一个查询。否则就会启动隐式事务,又回到最初的问题。

connection.execute(sqlalchemy.text("VACUUM ANALYZE table_1; VACUUM ANALYZE table_2;"))
# InternalError: (psycopg2.errors.ActiveSqlTransaction) VACUUM cannot run inside a transaction block

它之所以有效,是因为

isolation_level
更改为
AUTOCOMMIT
。这是在 DBAPI 中完成的,因此下面的库
sqlalchemy
。例如,
psycog2
代表
postgresql
发动机。

支持隔离级别的DBAPI通常也支持真正的“自动提交”概念,这意味着DBAPI连接本身将被置于非事务性自动提交模式。这通常意味着自动向数据库发出“BEGIN”的典型 DBAPI 行为不再发生,但它也可能包含其他指令。

https://docs.sqlalchemy.org/en/20/core/connections.html#setting-transaction-isolation-levels-include-dbapi-autocommit

因此,您必须使用与

MySQL
AUTOCOMMIT
模式兼容的 DBAPI。

您可以在那里找到此信息:https://docs.sqlalchemy.org/en/20/dialects/mysql.html

© www.soinside.com 2019 - 2024. All rights reserved.