我正在尝试使用 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 运行查询。或者有没有其他方法可以解决问题。我尝试更改存储过程的隔离级别,但这导致了表锁定问题。
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")
并且不会出现任何错误。
另一个答案不再适用于
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 行为不再发生,但它也可能包含其他指令。
因此,您必须使用与
MySQL
和 AUTOCOMMIT
模式兼容的 DBAPI。
您可以在那里找到此信息:https://docs.sqlalchemy.org/en/20/dialects/mysql.html