Исключение StaleDataError
Исключение StaleDataError наследуется от
SQLAlchemyError и возбуждается ORM-сессией,
когда операция UPDATE или DELETE не затронула
ни одной строки. Это означает, что данные в базе
изменились с момента последнего чтения объекта
в текущей сессии. Обычно такое поведение связано
с параллельным доступом: другая транзакция
изменила или удалила запись раньше нас. SQLAlchemy
сравнивает количество ожидаемых и реально
изменённых строк и сигнализирует о рассинхронизации
через StaleDataError.
Синтаксис
from sqlalchemy.orm.exc import StaleDataError
Пример
Давайте создадим объект, сохраним его, а затем удалим строку напрямую через SQL. При повторном сохранении объекта ORM обнаружит, что строка больше не существует, и возбудит исключение:
from sqlalchemy import create_engine, text
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
from sqlalchemy.orm.exc import StaleDataError
class Base(DeclarativeBase):
pass
class Article(Base):
__tablename__ = 'articles'
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str]
engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
with Session(engine) as session:
article = Article(title='article')
session.add(article)
session.commit()
# delete the row directly, bypassing ORM
with engine.connect() as conn:
conn.execute(text('DELETE FROM articles WHERE id = :id'), {'id': article.id})
conn.commit()
article.title = 'article updated'
try:
session.commit()
except StaleDataError as exc:
print(type(exc).__name__)
session.rollback()
Результат выполнения кода:
"StaleDataError"
Пример
Давайте смоделируем параллельное изменение: одна сессия читает объект, вторая меняет его и коммитит, после чего первая сессия пытается записать устаревшие данные:
from sqlalchemy import create_engine
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
from sqlalchemy.orm.exc import StaleDataError
class Base(DeclarativeBase):
pass
class Article(Base):
__tablename__ = 'articles'
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str]
num: Mapped[int]
engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
with Session(engine) as session:
article = Article(title='article', num=1)
session.add(article)
session.commit()
article_id = article.id
with Session(engine) as session_a, Session(engine) as session_b:
a = session_a.get(Article, article_id)
b = session_b.get(Article, article_id)
b.title = 'article'
b.num = 2
session_b.commit()
a.num = 3
try:
session_a.commit()
except StaleDataError as exc:
print(type(exc).__name__)
session_a.rollback()
Результат выполнения кода:
"StaleDataError"
Пример
Давайте включим оптимистичную блокировку через
параметр version_id_col и посмотрим, как
устаревшая версия объекта приводит к исключению:
from sqlalchemy import create_engine
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
from sqlalchemy.orm.exc import StaleDataError
class Base(DeclarativeBase):
pass
class Article(Base):
__tablename__ = 'articles'
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str]
version_id: Mapped[int] = mapped_column(nullable=False)
__mapper_args__ = {'version_id_col': version_id}
engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
with Session(engine) as session:
article = Article(title='article', version_id=1)
session.add(article)
session.commit()
article_id = article.id
with Session(engine) as session_a, Session(engine) as session_b:
a = session_a.get(Article, article_id)
b = session_b.get(Article, article_id)
b.title = 'article'
session_b.commit()
a.title = 'article updated'
try:
session_a.commit()
except StaleDataError as exc:
print(type(exc).__name__)
session_a.rollback()
Результат выполнения кода:
"StaleDataError"
Смотрите также
-
исключение
SQLAlchemyError,
которое является базовым для всех ошибок SQLAlchemy -
исключение
InvalidRequestError,
которое сигнализирует о некорректном запросе к сессии -
исключение
FlushError,
которое возникает при проблемах во время flush сессии -
исключение
ObjectDeletedError,
которое возбуждается при обращении к удалённому объекту