【问题标题】:SQLAlchemy: How do I avoid this inconsistency between ORM cache and DBSQLAlchemy:如何避免 ORM 缓存和 DB 之间的这种不一致
【发布时间】:2021-08-23 18:09:26
【问题描述】:

这是我的问题的简化版本。我有一个程序query.py

import time
from models import Ball, session

time.sleep(1)
r = session.query(Ball).filter(Ball.color=='red').first()
print(f'Red ball color is {r.color}')
time.sleep(2)
b = session.query(Ball).filter(Ball.color=='blue').first()
print(f'Blue ball color is {b.color}')
print(f'Red ball id is {r.id}, blue ball id is {b.id}')

当我与modify.py(包括在下面)同时运行query.py 时,我得到以下输出:

$ python modify.py &! python query.py
Red ball color is red                                                                                                                                                   
Blue ball color is red                                                                                                                                                  
Red ball id is 1, blue ball id is 1                                                                                                                  

问题是蓝球是红色的!

这里是models.py的内容:

import sqlalchemy as sa
import sqlalchemy.orm as sao
import sqlalchemy.ext.declarative as saed

Base = saed.declarative_base()

class Ball(Base):
    __tablename__ = 'ball'
    id = sa.Column(sa.Integer, primary_key=True)
    color = sa.Column(sa.String)

engine = sa.create_engine('sqlite:///test.db')
Base.metadata.create_all(engine)
session = sao.Session(engine)

这里是modify.py

import time
from models import Ball, session

session.query(Ball).delete()
b = Ball(color='red')
session.add(b)
session.commit()
time.sleep(2)
b.color = 'blue'
session.add(b)
session.commit()

我觉得很奇怪,我的数据库查询(查看最新的数据库状态)与通过我的数据库查询的 SQLAlchemy 身份映射返回的对象之间存在不一致(这是陈旧的,首先反映了数据库状态)读取相关行的时间)。我知道在每个查询之前在query.py 进程中重新启动我的事务将使身份映射中的缓存对象无效,并导致此处的蓝球为蓝色,但这是行不通的。

如果蓝球是蓝色的——即如果数据库查询和它返回的对象一致——或者如果蓝球查询返回None——我会很高兴——即如果并发数据库修改在查询事务。但我似乎被困在了中间。

【问题讨论】:

    标签: python sqlite sqlalchemy


    【解决方案1】:

    似乎根本问题是 Python 中默认支持 SQLite 是错误的,而 SQLAlchemy 故意继承了这个错误的行为。我最终想出了如何获得这两种可能的正确行为,即要么使blue ball blue,通过在不取消当前事务的情况下使身份映射/缓存无效,或者通过在正确隔离的事务中运行 query.py 来使对蓝球的查询返回 None

    实现隔离/使蓝球查询返回None

    我发现通过将isolation_level=<level> 传递给create_engine 来设置隔离级别没有任何效果,也就是说,这并没有提供一种方法来使query.py 在与@ 的写入隔离的事务中运行987654329@,其中蓝色球的查询将返回None。在阅读了isolation levels in SQLite 之后,似乎将隔离级别设置为SERIALIZABLE 应该可以实现这一点,但事实并非如此。但是,the SQLAlchemy documentation warns 默认情况下 SQLite 事务被破坏:

    数据库锁定行为/并发部分中,我们提到了pysqlite 驱动程序的各种问题,这些问题会阻止 SQLite 的几个功能正常工作。 pysqlite DBAPI 驱动程序有几个长期存在的错误,这些错误会影响其事务行为的正确性。在其默认操作模式下,SERIALIZABLE 隔离、事务 DDL 和 SAVEPOINT 支持等 SQLite 功能是无效的,为了使用这些功能,必须采取变通方法。

    该页面继续建议解决方法以获得正常的交易,这些解决方法对我有用。即在models.py的底部添加以下内容,实现了蓝球查询返回None的隔离行为:

    @sa.event.listens_for(engine, "connect")
    def do_connect(dbapi_connection, connection_record):
        # disable pysqlite's emitting of the BEGIN statement entirely.
        # also stops it from emitting COMMIT before any DDL.
        dbapi_connection.isolation_level = None
    
    @sa.event.listens_for(engine, "begin")
    def do_begin(conn):
        # emit our own BEGIN
        conn.exec_driver_sql("BEGIN")
    

    有了这个改变,输出变成了

    $ python modify.py &! python query.py
    Red ball color is red                                                                                                                                                   
    Traceback (most recent call last):                                                                                                                                      
      File "query.py", line 10, in <module>
        print(f'Blue ball color is {b.color}')
    AttributeError: 'NoneType' object has no attribute 'color'
    

    即在query.py 中的(隐式)事务中不可见modify.py 中的数据库写入使球变为蓝色。

    使查询和返回的对象一致/使蓝球变蓝

    另一方面,要获得蓝色球为蓝色的行为,使用session.expire_all() 在每次查询之前使缓存/身份映射无效就足够了。即,将query.py 更改为以下作品:

    import time
    from models import Ball, session
    
    time.sleep(1)
    r = session.query(Ball).filter(Ball.color=='red').first()
    print(f'Red ball color is {r.color}')
    time.sleep(2)
    # Added this line:
    session.expire_all()
    b = session.query(Ball).filter(Ball.color=='blue').first()
    print(f'Blue ball color is {b.color}')
    print(f'Red ball id is {r.id}, blue ball id is {b.id}')
    

    有了这个改变,输出变成了

    $ python modify.py &! python query.py                                                                                                                                
    Red ball color is red                                                                                                                                                   
    Blue ball color is blue                                                                                                                                                 
    Red ball id is 1, blue ball id is 1
    

    【讨论】:

      猜你喜欢
      • 2012-08-19
      • 1970-01-01
      • 2016-08-14
      • 1970-01-01
      • 1970-01-01
      • 2020-11-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多