python - SQLAlchemy 不会更新/过期具有外部更改的模型实例

标签 python database caching orm sqlalchemy

最近,我发现 SQLAlchemy 的奇怪行为涉及使用在当前 session 之外所做的更改来刷新/填充模型实例。我创建了以下最小工作示例并能够重现问题。


from time import sleep

from sqlalchemy import orm, create_engine, Column, BigInteger, Integer
from sqlalchemy.ext.declarative import declarative_base

DATABASE_URI = "postgresql://{user}:{password}@{host}:{port}/{name}".format(
    user="postgres",
    password="postgres",
    host="127.0.0.1",
    name="so_sqlalchemy",
    port="5432",
)


class SQLAlchemy:
    def __init__(self, db_url, autocommit=False, autoflush=True):
        self.engine = create_engine(db_url)
        self.session = None

        self.autocommit = autocommit
        self.autoflush = autoflush

    def connect(self):
        session_maker = orm.sessionmaker(
            bind=self.engine,
            autocommit=self.autocommit,
            autoflush=self.autoflush,
            expire_on_commit=True
        )
        self.session = orm.scoped_session(session_maker)

    def disconnect(self):
        self.session.flush()
        self.session.close()
        self.session.remove()
        self.session = None


BaseModel = declarative_base()


class TestModel(BaseModel):
    __tablename__ = "test_models"

    id = Column(BigInteger, primary_key=True, nullable=False)
    field = Column(Integer, nullable=False)


def loop(db):
    while True:
        with db.session.begin():
            t = db.session.query(TestModel).with_for_update().get(1)
            if t is None:
                print("No entry in db, creating...")
                t = TestModel(id=1, field=0)
                db.session.add(t)
                db.session.flush()

            print(f"t.field value is {t.field}")
            t.field += 1
            print(f"t.field value before flush is {t.field}")
            db.session.flush()
            print(f"t.field value after flush is {t.field}")

        print(f"t.field value after transaction is {t.field}")
        print("Sleeping for 2 seconds.")
        sleep(2.0)


def main():
    db = SQLAlchemy(DATABASE_URI, autocommit=True, autoflush=True)
    db.connect()
    try:
        loop(db)
    except KeyboardInterrupt:
        print("Canceled")


if __name__ == '__main__':
    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 ... FOR UPDATE在那里查询。但是,当我在数据库 shell 中提交事务时,脚本从以前的值继续(例如,27)并且没有检测到那个外部事务已将数据库中字段的值更改为100

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

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

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

最佳答案

我遇到了与 MySQL 非常相似的情况。我需要在代码的数据库操作过程中“查看”来自外部源的表更改。我最终不得不设置 autocommit=True在我的 session 调用中使用 session 的 begin()/commit() 方法来“查看”外部更新的数据。

SQLAlchemy 文档说这是遗留配置:

Warning

“autocommit” mode is a legacy mode of use and should not be considered for new projects.

还要在下一段说:

Modern usage of “autocommit mode” tends to be for framework integrations that wish to control specifically when the “begin” state occurs

所以目前还不清楚到底哪种说法是正确的。

关于python - SQLAlchemy 不会更新/过期具有外部更改的模型实例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56317578/

相关文章:

python - 如何将 CSV 变量中的行读入 python 中的多维数组?

html - 如何强制网络浏览器不缓存图像

caching - 是否有必要在 HTTP/2 中缓存 bust?

python - Pysys 与 Apama - 在将事件与引用进行比较时如何仅对某些参数进行验证?

python - 在 PyQt 中创建带有行号的文本区域(textEdit)

mongodb - 如何存储大量用户特定数据

php - 如何将用户输入与自定义 WordPress 数据库表进行比较

java - 从 mySQL Java 中的 INSERT INTO SELECT 语句获取内部 SELECT 结果作为 ResultSet

c# - NHibernate 3 Linq 查询缓存

python - 在 Python 中循环 zipfile 时,我怎么知道一个项目是一个目录?