Python SQLAlchemy不会使用外部更改更新/过期模型实例

Python SQLAlchemy不会使用外部更改更新/过期模型实例,python,database,caching,orm,sqlalchemy,Python,Database,Caching,Orm,Sqlalchemy,最近,我遇到了SQLAlchemy的奇怪行为,即使用当前会话之外的更改刷新/填充模型实例。我创建了以下最小的工作示例,并能够用它重现问题 从时间上导入睡眠 从sqlalchemy导入orm,创建_引擎、列、BigInteger、整数 从sqlalchemy.ext.declarative导入声明性基础 DATABASE_URI=“postgresql://{user}:{password}@{host}:{port}/{name}”。格式( user=“postgres”, password=

最近,我遇到了SQLAlchemy的奇怪行为,即使用当前会话之外的更改刷新/填充模型实例。我创建了以下最小的工作示例,并能够用它重现问题


从时间上导入睡眠
从sqlalchemy导入orm,创建_引擎、列、BigInteger、整数
从sqlalchemy.ext.declarative导入声明性基础
DATABASE_URI=“postgresql://{user}:{password}@{host}:{port}/{name}”。格式(
user=“postgres”,
password=“postgres”,
host=“127.0.0.1”,
name=“sou sqlalchemy”,
port=“5432”,
)
类炼金术:
定义初始化(self,db\u url,autocommit=False,autoflush=True):
self.engine=创建引擎(db\U url)
self.session=None
self.autocommit=自动提交
self.autoflush=自动刷新
def连接(自):
session\u maker=orm.sessionmaker(
绑定=自引擎,
自动提交=self.autocommit,
autoflush=self.autoflush,
在提交时过期=真
)
self.session=orm.scoped_session(session_maker)
def断开(自):
self.session.flush()
self.session.close()
self.session.remove()
self.session=None
BaseModel=声明性_base()
类TestModel(基本模型):
__tablename=“测试模型”
id=列(BigInteger,主键=True,可空=False)
字段=列(整数,可空=False)
def回路(db):
尽管如此:
使用db.session.begin():
t=db.session.query(TestModel).with_for_update().get(1)
如果t为无:
打印(“数据库中无条目,正在创建…”)
t=TestModel(id=1,字段=0)
db.session.add(t)
db.session.flush()
打印(f“t.field值为{t.field}”)
t、 字段+=1
打印(f“刷新前的t.field值为{t.field}”)
db.session.flush()
打印(f“刷新后的t.field值为{t.field}”)
打印(f“事务后的t.field值为{t.field}”)
打印(“睡眠2秒”)
睡眠(2.0)
def main():
db=SQLAlchemy(数据库URI,自动提交=True,自动刷新=True)
db.connect()
尝试:
环路(db)
除键盘中断外:
打印(“已取消”)
如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu':
main()
我的
requirements.txt
文件如下所示:

alembic==1.0.10
psycopg2-binary==2.8.2
sqlalchemy==1.3.3
如果我运行该脚本(我在运行Ubuntu 16.04的笔记本电脑上使用Python 3.7.3),它会像预期的那样每两秒增加一个值:

t.field value is 0
t.field value before flush is 1
t.field value after flush is 1
t.field value after transaction is 1
Sleeping for 2 seconds.
t.field value is 1
t.field value before flush is 2
t.field value after flush is 2
t.field value after transaction is 2
Sleeping for 2 seconds.
...
现在,我打开postgres数据库shell并开始另一个事务:

so_sqlalchemy=# BEGIN;
BEGIN
so_sqlalchemy=# UPDATE test_models SET field=100 WHERE id=1;
UPDATE 1
so_sqlalchemy=# COMMIT;
COMMIT
在执行
UPDATE
查询后,只要我按
Enter
,脚本就会按预期阻塞,因为我正在发出
SELECT。。。如需更新,请在此处查询。但是,当我在数据库shell中提交事务时,脚本将从上一个值(例如,
27
)继续,并且未检测到外部事务已将数据库中
字段的值更改为
100

我的问题是,为什么会发生这种情况?有几个因素似乎与当前的行为相矛盾:

  • 我正在使用设置为
    True
    expire\u on\u commit
    设置,这似乎意味着事务提交后,事务中使用的每个模型实例都将标记为
    expired
    。(引用“如果为True,则在每次提交()之后,所有实例都将完全过期,以便完成事务之后的所有属性/对象访问都将从最近的数据库状态加载。”)
  • 我不是访问一些旧的模型实例,而是每次都发出全新的查询。据我所知,这将导致直接查询数据库,而不是访问缓存的实例。如果打开sqlalchemy调试日志,我可以确认情况确实如此

  • 这个问题的快速而肮脏的解决方法是在事务开始后立即调用
    db.session.expire\u all()
    ,但这似乎非常不雅观且违反直觉。我很高兴了解我在这里使用sqlalchemy的方式有什么问题。

    我在使用MySQL时遇到了非常类似的情况。我需要“看到”对来自我的代码的数据库操作中间的外部源的表的更改。最后,我不得不在会话调用中设置并使用会话的begin()/commit()方法来“查看”外部更新的数据

    SQLAlchemy文档称这是一种传统配置:

    警告

    “自动提交”模式是一种传统的使用模式,不应考虑用于新项目

    但也要在下一段说:

    “自动提交模式”的现代用法倾向于用于希望在“开始”状态发生时具体控制的框架集成

    因此,似乎不清楚哪种说法是正确的