SQLAlchemy,scoped_session - 原始SQL插入未写入数据库

6 投票
1 回答
5328 浏览
提问于 2025-04-18 18:26

我有一个用Pyramid和SQLAlchemy做的Python应用,数据库是MySQL。 当我执行一个原始的SQL插入查询时,数据库里什么都没有写进去。 但当我使用ORM(对象关系映射)时,就可以写入数据库。我看了文档,也了解了一下ZopeTransactionExtension,还查了很多StackOverflow上的问题,但都没有解决我的问题。 到目前为止,以下方法都没有成功:

  • transaction.commit() - 数据库里没有任何内容。我知道这个语句在使用ZopeTransactionExtension时是必要的,但在这里并没有起到作用。
  • dbsession().commit - 由于我在使用ZopeTransactionExtension,所以这个方法不管用。
  • dbsession().close() - 还是没有写入任何内容。
  • dbsession().flush() - 也没有写入。
  • mark_changed(session) - 这个也没用。

在文件"/home/dev/.virtualenvs/sc/local/lib/python2.7/site-packages/zope/sqlalchemy/datamanager.py"的第198行,出现了这个错误: if session.twophase: AttributeError: 'scoped_session'对象没有'twophase'这个属性。

还有一些方法是有效的,但不被接受,因为它们没有使用scoped_session:

  • engine.execute(...)

我想知道如何在scoped_session(在我的代码中是dbsession())上执行原始SQL。

这是我的SQLAlchemy设置(models/__init__.py

def dbsession():
    assert (_dbsession is not None)
    return _dbsession

def init_engines(settings, _testing_workarounds=False):
    import zope.sqlalchemy
    extension = zope.sqlalchemy.ZopeTransactionExtension()
    global _dbsession

    _dbsession = scoped_session(
        sessionmaker(
            autoflush=True,
            expire_on_commit=False,
            extension=extension,
        )
    )

    engine = engine_from_config(settings, 'sqlalchemy.')
    _dbsession.configure(bind=engine)

我写了一个Python脚本来隔离这个问题。这个脚本模拟了问题发生的真实环境。我只想让下面的脚本把数据插入到数据库中:

# -*- coding: utf-8 -*-
import sys
import transaction

from pyramid.paster import setup_logging, get_appsettings
from sc.models import init_engines, dbsession
from sqlalchemy.sql.expression import text


def __main__():

if len(sys.argv) < 2:
    raise RuntimeError()
config_uri = sys.argv[1]
setup_logging(config_uri)
aa = init_engines(get_appsettings(config_uri))

session = dbsession()
session.execute(text("""INSERT INTO
         operations (description, generated_description)
         VALUES ('hello2', 'world');"""))

print list(session.execute("""SELECT * from operations""").fetchall()) # prints inserted data
transaction.commit()

print list(session.execute("""SELECT * from operations""").fetchall()) # doesn't print inserted data

if __name__ == '__main__':
    __main__()

有趣的是,如果我这样做:

session = dbsession()
session.execute(text("""INSERT INTO
         operations (description, generated_description)
         VALUES ('hello2', 'world');"""))
op = Operation(generated_description='aa', description='oo')
session.add(op)

那么第一个打印输出的是插入的原始SQL行('hello2' 'world'),第二个打印输出的是两个行,实际上这两行都被插入到数据库里

我无法理解为什么在使用ORM插入的同时,原始SQL的使用会“修复”这个问题。

我真的需要能够在scoped_session上调用execute(),以便使用原始SQL将数据插入数据库。有什么建议吗?

1 个回答

5

已经有一段时间没把原始SQL和SQLAlchemy混合使用了,但每当你这样做时,需要了解ORM背后发生了什么。首先,检查一下自动提交的设置。如果zope事务没有正确配置,ORM的插入操作可能会触发一个提交。

实际上,查看了zope的文档后,发现手动执行语句需要额外的步骤。从他们的说明文件中可以看到:

默认情况下,zope.sqlalchemy在第一次使用会话时将其置于“活动”状态。ORM的写操作会自动将会话移到“已更改”状态。这可以避免不必要的数据库提交。有时需要通过SQL直接与数据库交互。我们无法猜测这样的操作是读取还是写入。因此,当手动SQL语句写入数据库时,我们必须手动将会话标记为已更改。

>>> session = Session()
>>> conn = session.connection()
>>> users = Base.metadata.tables['test_users']
>>> conn.execute(users.update(users.c.name=='bob'), name='ben')
<sqlalchemy.engine...ResultProxy object at ...>
>>> from zope.sqlalchemy import mark_changed
>>> mark_changed(session)
>>> transaction.commit()
>>> session = Session()
>>> str(session.query(User).all()[0].name)
'ben'
>>> transaction.abort()

看起来你没有这样做,所以transaction.commit没有任何作用。

撰写回答