SQLAlchemy:如何避免 ORM 缓存和 DB

问题描述

这是我的问题的简化版本。我有一个程序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}')

当我与 query.py包括在下面)同时运行 modify.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 故意继承了这种有问题的行为。我最终想出了如何获得两种可能的正确行为,即要么使blue ball blue,通过在不取消当前事务的情况下使身份映射/缓存无效,或者通过在适当隔离的事务中运行 None 使对蓝色球的查询返回 query.py

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

我发现通过将 isolation_level=<level> 传递给 create_engine 来设置隔离级别没有任何效果,即这并没有提供一种方法来使 query.py 在一个事务中运行与 modify.py 的写入隔离,其中对蓝色球的查询将返回 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'

modify.py 中使球变为蓝色的 DB 写入在 query.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

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...