使用session.query读取SQLAlchemy中未提交的数据

时间:2019-06-18 00:45:16

标签: python postgresql sqlalchemy

摘要

我正在尝试针对一系列数据库操作编写集成测试,并且希望能够将SQLAlchemy会话用作在其中验证和回滚事务的过渡环境。

是否可以使用session.query(Foo)而不是session.execute(text('select * from foo'))来检索未提交的数据?

背景与研究

使用SQLAlchemy 1.2.10,Python 2.7.13和Postgres 9.6.11观察到了这些结果。

我看过相关的StackOverflow帖子,但没有找到关于以下两个操作为何表现不同的解释。

可复制示例

1)我建立了到数据库的连接并定义了模型对象;到目前为止没有问题:

from sqlalchemy import text
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, ForeignKey

#####
# Prior DB setup:
# CREATE TABLE foo (id int PRIMARY KEY, label text);
#####

# from https://docs.sqlalchemy.org/en/13/orm/mapping_styles.html#declarative-mapping
Base = declarative_base()

class Foo(Base):
    __tablename__ = 'foo'
    id = Column(Integer, primary_key=True)
    label = Column(String)

# from https://docs.sqlalchemy.org/en/13/orm/session_basics.html#getting-a-session
some_engine = create_engine('postgresql://username:password@endpoint/database')
Session = sessionmaker(bind=some_engine)

2)我在不提交结果的情况下执行了一些更新,并且可以通过在会话中执行select语句来查看已暂存的数据:

session = Session()
sql_insert = text("INSERT INTO foo (id, label) VALUES (1, 'original')")
session.execute(sql_insert);
sql_read = text("SELECT * FROM foo WHERE id = 1");
res = session.execute(sql_read).first()
print res.label

sql_update = text("UPDATE foo SET label = 'updated' WHERE id = 1")
session.execute(sql_update)
res2 = session.execute(sql_read).first()
print res2.label

sql_update2 = text("""
INSERT INTO foo (id, label) VALUES (1, 'second_update')
ON CONFLICT (id) DO UPDATE
    SET (label) = (EXCLUDED.label)
""")
session.execute(sql_update2)
res3 = session.execute(sql_read).first()
print res3.label
session.rollback()

# prints expected values: 'original', 'updated', 'second_update'

3)我尝试将select语句替换为session.query,但看不到新数据:

session = Session()
sql_insert = text("INSERT INTO foo (id, label) VALUES (1, 'original')")
session.execute(sql_insert);
res = session.query(Foo).filter_by(id=1).first()
print res.label

sql_update = text("UPDATE foo SET label = 'updated' WHERE id = 1")
session.execute(sql_update)
res2 = session.query(Foo).filter_by(id=1).first()
print res2.label

sql_update2 = text("""
INSERT INTO foo (id, label) VALUES (1, 'second_update')
ON CONFLICT (id) DO UPDATE
    SET (label) = (EXCLUDED.label)
""")
session.execute(sql_update2)
res3 = session.query(Foo).filter_by(id=1).first()
print res3.label
session.rollback()
# prints: 'original', 'original', 'original'

我希望第3步的打印输出是“原始”,“更新”,“ second_update”。

1 个答案:

答案 0 :(得分:1)

根本原因是在这种情况下原始SQL查询和ORM不会自动混合。 the Session is not a cache表示不缓存查询,但确实根据对象的主键将对象存储在identity map中。当Query返回映射对象的行时,将返回现有对象。这就是为什么您不观察在第三步中所做的更改的原因。这似乎是处理情况的一种较差的方法,但是SQLAlchemy的运行基于关于transaction isolation的一些假设,如"When to Expire or Refresh"中所述:

  

交易隔离

     

... [So]作为最佳猜测,它假设在事务范围内,除非已知已经发出了SQL表达式来修改特定行,否则除非有明确说明,否则无需刷新行被告知要这样做。

关于事务隔离的整个注释值得一读。使这样的更改对SQLAlchemy已知的方法是,如果可能的话,使用Query API执行更新,如果所有其他操作均失败,则手动expire进行更改的对象。考虑到这一点,您的第三步应如下所示:

session = Session()
sql_insert = text("INSERT INTO foo (id, label) VALUES (1, 'original')")
session.execute(sql_insert);
res = session.query(Foo).filter_by(id=1).first()
print(res.label)

session.query(Foo).filter_by(id=1).update({Foo.label: 'updated'},
                                          synchronize_session='fetch')
# This query is actually redundant, `res` and `res2` are the same object
res2 = session.query(Foo).filter_by(id=1).first()
print(res2.label)

sql_update2 = text("""
INSERT INTO foo (id, label) VALUES (1, 'second_update')
ON CONFLICT (id) DO UPDATE
    SET label = EXCLUDED.label
""")
session.execute(sql_update2)
session.expire(res)
# Again, this query is redundant and fetches the same object that needs
# refreshing anyway
res3 = session.query(Foo).filter_by(id=1).first()
print(res3.label)
session.rollback()