Основы сессии

Что делает сессия?

В самом общем смысле, Session устанавливает все разговоры с базой данных и представляет собой «зону хранения» для всех объектов, которые вы загрузили или связали с ним в течение его жизни. Он предоставляет интерфейс, через который выполняются SELECT и другие запросы, возвращающие и изменяющие объекты ORM. Сами объекты ORM хранятся внутри Session, внутри структуры, называемой identity map - структуры данных, которая хранит уникальные копии каждого объекта, где «уникальный» означает «только один объект с определенным первичным ключом».

Session начинает свою работу в основном в безэталонной форме. После выполнения запросов или персистирования других объектов он запрашивает ресурс соединения у Engine, который связан с Session, а затем устанавливает транзакцию на этом соединении. Эта транзакция остается в силе до тех пор, пока Session не получит указание зафиксировать или откатить транзакцию.

Объекты ORM, поддерживаемые Session, являются instrumented такими, что всякий раз, когда атрибут или коллекция изменяется в программе Python, генерируется событие изменения, которое регистрируется Session. Когда к базе данных собирается запрос или когда транзакция собирается быть зафиксированной, Session сначала промывает все ожидающие изменения, хранящиеся в памяти, в базу данных. Это известно как паттерн unit of work.

При использовании Session полезно рассматривать объекты ORM mapped, которые он поддерживает, как прокси-объекты к строкам базы данных, которые являются локальными для транзакции, проводимой Session. Для поддержания состояния объектов в соответствии с тем, что на самом деле находится в базе данных, существует целый ряд событий, которые заставят объекты повторно обращаться к базе данных для поддержания синхронизации. Можно «отсоединить» объекты от Session и продолжать их использовать, хотя эта практика имеет свои недостатки. Предполагается, что обычно вы повторно связываете отсоединенные объекты с другим Session, когда хотите снова работать с ними, чтобы они могли возобновить свою обычную задачу представления состояния базы данных.

Основы использования сеанса

Здесь представлены самые основные схемы использования Session.

Открытие и закрытие сессии

Session может быть построен самостоятельно или с помощью класса sessionmaker. Обычно ему передается один Engine в качестве источника связности. Типичное использование может выглядеть следующим образом:

from sqlalchemy import create_engine
from sqlalchemy.orm import Session

# an Engine, which the Session will use for connection
# resources
engine = create_engine("postgresql://scott:tiger@localhost/")

# create session and add objects
with Session(engine) as session:
    session.add(some_object)
    session.add(some_other_object)
    session.commit()

Выше Session инстанцируется Engine, связанный с определенным URL базы данных. Затем он используется в менеджере контекста Python (т.е. в операторе with:) таким образом, что он автоматически закрывается в конце блока; это эквивалентно вызову метода Session.close().

Вызов Session.commit() является необязательным и нужен только в том случае, если работа, которую мы проделали с помощью Session, включает новые данные, которые должны быть сохранены в базе данных. Если бы мы выполняли только вызовы SELECT и не нуждались в записи изменений, то вызов Session.commit() был бы излишним.

Примечание

Обратите внимание, что после вызова Session.commit(), явно или при использовании менеджера контекста, все объекты, связанные с Session, становятся expired, то есть их содержимое стирается для повторной загрузки в следующей транзакции. Если эти объекты вместо этого detached, они будут нефункциональны до тех пор, пока не будут повторно ассоциированы с новым Session, если только параметр Session.expire_on_commit не используется для отключения этого поведения. Более подробно см. раздел Совершение.

Составление блока «начало / фиксация / откат

Мы также можем заключить вызов Session.commit() и общее «обрамление» транзакции в контекстный менеджер для тех случаев, когда мы будем фиксировать данные в базе данных. Под «обрамлением» мы подразумеваем, что если все операции пройдут успешно, будет вызван метод Session.commit(), но если возникнут исключения, будет вызван метод Session.rollback(), чтобы транзакция была откатана немедленно, до распространения исключения наружу. В Python это наиболее просто выражается с помощью блока try: / except: / else:, такого как:

# verbose version of what a context manager will do
with Session(engine) as session:
    session.begin()
    try:
        session.add(some_object)
        session.add(some_other_object)
    except:
        session.rollback()
        raise
    else:
        session.commit()

Длинную последовательность операций, показанную выше, можно выполнить более кратко, используя объект SessionTransaction, возвращаемый методом Session.begin(), который предоставляет интерфейс менеджера контекста для той же последовательности операций:

# create session and add objects
with Session(engine) as session:
    with session.begin():
        session.add(some_object)
        session.add(some_other_object)
    # inner context calls session.commit(), if there were no exceptions
# outer context calls session.close()

В более кратком виде эти два контекста можно объединить:

# create session and add objects
with Session(engine) as session, session.begin():
    session.add(some_object)
    session.add(some_other_object)
# inner context calls session.commit(), if there were no exceptions
# outer context calls session.close()

Использование сеансовика

Цель sessionmaker - предоставить фабрику для Session объектов с фиксированной конфигурацией. Поскольку типично, что приложение будет иметь объект Engine в области действия модуля, sessionmaker может предоставить фабрику для объектов Session, которые противоречат этому движку:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

# an Engine, which the Session will use for connection
# resources, typically in module scope
engine = create_engine("postgresql://scott:tiger@localhost/")

# a sessionmaker(), also in the same scope as the engine
Session = sessionmaker(engine)

# we can now construct a Session() without needing to pass the
# engine each time
with Session() as session:
    session.add(some_object)
    session.add(some_other_object)
    session.commit()
# closes the session

sessionmaker аналогичен Engine как фабрика на уровне модуля для сессий/соединений на уровне функций. Как таковой он также имеет свой собственный метод sessionmaker.begin(), аналогичный Engine.begin(), который возвращает объект Session, а также поддерживает блок begin/commit/rollback:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

# an Engine, which the Session will use for connection
# resources
engine = create_engine("postgresql://scott:tiger@localhost/")

# a sessionmaker(), also in the same scope as the engine
Session = sessionmaker(engine)

# we can now construct a Session() and include begin()/commit()/rollback()
# at once
with Session.begin() as session:
    session.add(some_object)
    session.add(some_other_object)
# commits the transaction, closes the session

Там, где выше, у Session будет зафиксирована транзакция, а также то, что Session будет закрыт, когда закончится вышеупомянутый блок with:.

Когда вы пишете свое приложение, фабрика sessionmaker должна иметь ту же область видимости, что и объект Engine, созданный create_engine(), которая обычно находится на уровне модуля или в глобальной области видимости. Поскольку оба эти объекта являются фабриками, они могут использоваться одновременно любым количеством функций и потоков.

См.также

sessionmaker

Session

Запросы (стиль 1.x)

Функция Session.query() принимает одну или несколько сущностей и возвращает новый объект Query, который будет выдавать запросы маппера в контексте данной сессии. Под «сущностью» мы понимаем сопоставленный класс, атрибут сопоставленного класса или другие конструкции ORM, такие как конструкция aliased():

# query from a class
results = session.query(User).filter_by(name="ed").all()

# query with multiple classes, returns tuples
results = session.query(User, Address).join("addresses").filter_by(name="ed").all()

# query using orm-columns, also returns tuples
results = session.query(User.name, User.fullname).all()

Когда объекты ORM возвращаются в результатах, они также сохраняются в карте идентификации. Когда входящая строка базы данных имеет первичный ключ, совпадающий с объектом, который уже присутствует, возвращается тот же объект, а те атрибуты объекта, которые уже имеют значение, не заполняются повторно.

Session автоматически завершает все экземпляры вдоль границ транзакции (т.е. когда текущая транзакция фиксируется или откатывается), так что при нормально изолированной транзакции данные обновятся сами собой, когда начнется новая транзакция.

Объект Query подробно представлен в Объектно-реляционный учебник (API 1.x), а более подробно документирован в API запросов.

Составление запросов (стиль 2.0)

Добавлено в версии 1.4.

SQLAlchemy 2.0 стандартизирует создание операторов SELECT в Core и ORM, используя непосредственно объект Select в ORM, устраняя необходимость в отдельном объекте Query. Этот режим работы доступен в SQLAlchemy 1.4 уже сейчас для поддержки приложений, которые будут переходить на версию 2.0. Session должен быть инстанцирован с флагом Session.future, установленным на True; с этого момента метод Session.execute() будет возвращать результаты ORM через стандартный объект Result при вызове операторов select(), использующих сущности ORM:

from sqlalchemy import select
from sqlalchemy.orm import Session

session = Session(engine, future=True)

# query from a class
statement = select(User).filter_by(name="ed")

# list of first element of each row (i.e. User objects)
result = session.execute(statement).scalars().all()

# query with multiple classes
statement = select(User, Address).join("addresses").filter_by(name="ed")

# list of tuples
result = session.execute(statement).all()

# query with ORM columns
statement = select(User.name, User.fullname)

# list of tuples
result = session.execute(statement).all()

Важно отметить, что в то время как методы Query, такие как Query.all() и Query.one(), будут возвращать экземпляры объектов ORM mapped непосредственно в случае, если была запрошена только одна полная сущность, объект Result, возвращаемый Session.execute(), по умолчанию всегда будет передавать строки (именуемые кортежами); это сделано для того, чтобы результаты по одиночным или множественным объектам ORM, столбцам, таблицам и т.д. могли обрабатываться одинаково.

Если запрашивалась только одна сущность ORM, возвращаемые строки будут иметь ровно один столбец, состоящий из сопоставленного с ORM экземпляра объекта для каждой строки. Чтобы преобразовать эти строки в экземпляры объектов без кортежей, используется метод Result.scalars(), чтобы сначала применить фильтр «scalars» к результату; затем Result можно выполнить итерацию или доставить строки стандартными методами, такими как Result.all(), Result.first() и т.д.

Добавление новых или существующих элементов

Session.add() используется для размещения экземпляров в сессии. Для экземпляров transient (т.е. совершенно новых) это будет иметь эффект INSERT, который произойдет для этих экземпляров при следующем flush. Для экземпляров, которые являются persistent (т.е. были загружены этой сессией), они уже присутствуют и их не нужно добавлять. Экземпляры, которые являются detached (т.е. были удалены из сессии), могут быть повторно ассоциированы с сессией с помощью этого метода:

user1 = User(name="user1")
user2 = User(name="user2")
session.add(user1)
session.add(user2)

session.commit()  # write changes to the database

Чтобы добавить в сессию сразу список элементов, используйте Session.add_all():

session.add_all([item1, item2, item3])

Операция Session.add() каскадирует по каскаду save-update. Более подробно см. раздел Каскады.

Удаление

Метод Session.delete() помещает экземпляр в список объектов сессии, которые должны быть помечены как удаленные:

# mark two objects to be deleted
session.delete(obj1)
session.delete(obj2)

# commit (or flush)
session.commit()

Session.delete() помечает объект для удаления, в результате чего для каждого затронутого первичного ключа будет выпущен оператор DELETE. До того, как ожидающие удаления будут удалены, объекты, помеченные «delete», присутствуют в коллекции Session.deleted. После DELETE они исключаются из коллекции Session, которая становится постоянной после фиксации транзакции.

С операцией Session.delete() связаны различные важные поведения, особенно в том, как обрабатываются отношения с другими объектами и коллекциями. Подробнее об этом говорится в разделе Каскады, но в целом правила таковы:

  • Строки, соответствующие сопоставленным объектам, которые связаны с удаляемым объектом с помощью директивы relationship(), по умолчанию не удаляются. Если эти объекты имеют ограничение внешнего ключа, обратное удаляемой строке, эти столбцы устанавливаются в NULL. Это приведет к нарушению ограничений, если столбцы являются ненулевыми.

  • Чтобы изменить «SET NULL» на DELETE строки связанного объекта, используйте каскад удалить на relationship().

  • Строки, которые находятся в таблицах, связанных как таблицы «многие-ко-многим», через параметр relationship.secondary, удаляются во всех случаях, когда объект, на который они ссылаются, удаляется.

  • Если связанные объекты включают ограничение внешнего ключа, обратное удаляемому объекту, и связанные коллекции, к которым они принадлежат, в настоящее время не загружены в память, единица работы будет выдавать SELECT для получения всех связанных строк, так что их значения первичного ключа могут быть использованы для выдачи операторов UPDATE или DELETE для этих связанных строк. Таким образом, ORM без дополнительных инструкций выполнит функцию ON DELETE CASCADE, даже если она настроена на объекты Core ForeignKeyConstraint.

  • Параметр relationship.passive_deletes может быть использован для настройки этого поведения и более естественного использования «ON DELETE CASCADE»; при установке в True, эта операция SELECT больше не будет выполняться, однако строки, присутствующие локально, все еще будут подвергаться явному SET NULL или DELETE. Установка relationship.passive_deletes в строку "all" отключит все связанные обновления/удаления объектов.

  • Когда происходит DELETE для объекта, помеченного для удаления, объект не удаляется автоматически из коллекций или объектных ссылок, которые на него ссылаются. Когда истекает срок действия Session, эти коллекции могут быть загружены снова, так что объект больше не будет присутствовать. Однако предпочтительнее, чтобы вместо использования Session.delete() для этих объектов, объект был удален из своей коллекции, а затем было использовано delete-orphan, чтобы он был удален как вторичный эффект удаления коллекции. Пример этого см. в разделе Примечания по удалению - Удаление объектов, на которые ссылаются коллекции и скалярные отношения.

См.также

удалить - описывает «каскад удаления», который отмечает связанные объекты для удаления при удалении ведущего объекта.

delete-orphan - описывает «каскад удаления сирот», который помечает связанные объекты для удаления, когда они де-ассоциируются со своим ведущим объектом.

Примечания по удалению - Удаление объектов, на которые ссылаются коллекции и скалярные отношения - важный фон для Session.delete(), поскольку включает в себя отношения, освежаемые в памяти.

Промывка

Когда Session используется в конфигурации по умолчанию, шаг flush почти всегда выполняется прозрачно. В частности, промывка происходит перед выпуском любого отдельного оператора SQL в результате вызова Query или 2.0-style Session.execute(), а также в рамках вызова Session.commit() перед фиксацией транзакции. Это также происходит перед выдачей SAVEPOINT при использовании Session.begin_nested().

Промывка Session может быть принудительно выполнена в любое время путем вызова метода Session.flush():

session.flush()

Промывка, которая происходит автоматически в области действия определенных методов, известна как autoflush. Autoflush определяется как настраиваемый автоматический вызов смыва, который происходит в начале методов, включая:

  • Session.execute() и другие методы SQL-выполнения

  • Когда вызывается Query для отправки SQL в базу данных

  • В методе Session.merge() перед запросом к базе данных

  • Когда объекты refreshed

  • Когда операции ORM lazy load происходят над выгруженными атрибутами объекта.

Существуют также точки, в которых смыв происходит безусловно; эти точки находятся в пределах ключевых транзакционных границ, которые включают:

  • В процессе выполнения метода Session.commit()

  • Когда вызывается Session.begin_nested()

  • Когда используется метод Session.prepare() 2PC.

Поведение autoflush, применяемое к предыдущему списку элементов, можно отключить, построив Session или sessionmaker, передав параметр Session.autoflush как False:

Session = sessionmaker(autoflush=False)

Кроме того, автосмыв можно временно отключить в потоке использования Session с помощью менеджера контекста Session.no_autoflush:

with mysession.no_autoflush:
    mysession.add(some_object)
    mysession.flush()

Повторимся: Процесс промывки всегда происходит, когда вызываются транзакционные методы, такие как Session.commit() и Session.begin_nested(), независимо от любых настроек «autoflush», когда Session имеет оставшиеся незавершенные изменения для обработки.

Процесс flush всегда происходит внутри транзакции (при условии isolation level транзакции базы данных), при условии, что DBAPI не находится в режиме driver level autocommit. Это относится даже к тем случаям, когда Session был сконфигурирован с устаревшей настройкой Session.autocommit, которая отключает постоянное транзакционное состояние сессии. Если транзакция отсутствует, Session.flush() создает свою собственную транзакцию и фиксирует ее. Это означает, что если соединение с базой данных предусматривает atomicity в настройках транзакций, то при неудаче любого отдельного оператора DML внутри flush вся операция будет откатана.

За исключением использования Session.autocommit, когда происходит сбой в рамках flush, для продолжения использования того же Session, после сбоя flush требуется явный вызов Session.rollback(), даже если базовая транзакция уже была откатана (даже если драйвер базы данных технически находится в режиме автокоммита на уровне драйвера). Это делается для того, чтобы последовательно сохранялась общая схема вложенности так называемых «субтранзакций». В разделе FAQ «Транзакция этого сеанса была откатана из-за предыдущего исключения во время промывки». (или аналогично) содержится более подробное описание этого поведения.

Получить по первичному ключу

Поскольку Session использует identity map, который ссылается на текущие объекты в памяти по первичному ключу, метод Session.get() предоставляется как средство нахождения объектов по первичному ключу, сначала ища в текущей карте идентичности, а затем запрашивая базу данных для не присутствующих значений. Например, чтобы найти объект User с первичным ключом (5, ):

my_user = session.get(User, 5)

Session.get() также включает формы вызова для составных значений первичного ключа, которые могут быть переданы в виде кортежей или словарей, а также дополнительные параметры, позволяющие использовать определенные опции загрузчика и выполнения. Полный список параметров приведен в Session.get().

См.также

Session.get()

Истечение срока действия / Обновление

Важным моментом, который часто возникает при использовании Session, является работа с состоянием, которое присутствует на объектах, загруженных из базы данных, в плане поддержания их синхронизации с текущим состоянием транзакции. SQLAlchemy ORM основана на концепции identity map, согласно которой, когда объект «загружается» из SQL-запроса, сохраняется уникальный экземпляр объекта Python, соответствующий определенному идентификатору базы данных. Это означает, что если мы выполним два отдельных запроса, каждый для одной и той же строки, и получим обратно сопоставленный объект, эти два запроса вернут один и тот же объект Python:

>>> u1 = session.query(User).filter(id=5).first()
>>> u2 = session.query(User).filter(id=5).first()
>>> u1 is u2
True

Исходя из этого, когда ORM получает строки обратно из запроса, он пропускает создание атрибутов для объекта, который уже загружен. При проектировании предполагается, что транзакция идеально изолирована, а затем, если транзакция не изолирована, приложение может предпринимать шаги по мере необходимости для обновления объектов из транзакции базы данных. Более подробно эта концепция обсуждается в FAQ по адресу Я повторно загружаю данные с помощью Session, но он не видит изменений, которые я сделал в другом месте..

Когда сопоставленный объект ORM загружается в память, существует три общих способа обновить его содержимое новыми данными из текущей транзакции:

  • метод expire() - метод Session.expire() стирает содержимое выбранных или всех атрибутов объекта так, что они будут загружены из базы данных при следующем обращении к ним, например, с помощью шаблона lazy loading:

    session.expire(u1)
    u1.some_attribute  # <-- lazy loads from the transaction
  • метод refresh() - тесно связан с методом Session.refresh(), который делает все то же самое, что и метод Session.expire(), но при этом немедленно выдает один или несколько SQL-запросов, чтобы фактически обновить содержимое объекта:

    session.refresh(u1)  # <-- emits a SQL query
    u1.some_attribute  # <-- is refreshed from the transaction
  • метод populate_existing() или опция выполнения - Теперь это опция выполнения, документированная в Заполнить существующие; в унаследованной форме она находится на объекте Query как метод Query.populate_existing(). Эта операция в любой форме указывает, что объекты, возвращаемые из запроса, должны быть безусловно переполнены из их содержимого в базе данных:

    u2 = session.query(User).populate_existing().filter(id=5).first()

Дальнейшее обсуждение концепции обновления / истечения срока действия можно найти в Обновление / истечение срока действия.

UPDATE и DELETE с произвольным предложением WHERE

В разделах Session.flush() и Session.delete() подробно описано, как можно вставлять, обновлять и удалять строки в базе данных на основе первичных ключей, на которые ссылаются сопоставленные объекты Python в приложении. Session также может создавать операторы UPDATE и DELETE с произвольными формулами WHERE и одновременно обновлять локально присутствующие объекты, соответствующие этим строкам.

Чтобы выдать UPDATE с поддержкой ORM в 1.x style, можно использовать метод Query.update():

session.query(User).filter(User.name == "squidward").update(
    {"name": "spongebob"}, synchronize_session="fetch"
)

Выше, UPDATE будет выдан для всех строк, которые соответствуют имени «squidward» и будут обновлены до имени «spongebob». Параметр Query.update.synchronize_session, относящийся к «fetch», указывает, что список затронутых первичных ключей должен быть получен либо через отдельный оператор SELECT, либо через RETURNING, если внутренняя база данных поддерживает это; объекты, локально присутствующие в памяти, будут обновлены в памяти на основе этих идентификаторов первичных ключей.

Для UPDATE с поддержкой ORM в 2.0 style, Session.execute() используется с конструкцией Core Update:

from sqlalchemy import update

stmt = (
    update(User)
    .where(User.name == "squidward")
    .values(name="spongebob")
    .execution_options(synchronize_session="fetch")
)

result = session.execute(stmt)

Выше, метод Update.execution_options() может быть использован для установки опций времени выполнения, таких как «synchronize_session».

Возвращаемый объект результата является экземпляром CursorResult; чтобы получить количество строк, совпавших с любым оператором UPDATE или DELETE, используйте CursorResult.rowcount:

num_rows_matched = result.rowcount

DELETE работают так же, как и UPDATE, за исключением того, что не устанавливается оговорка «values / set». Когда используется synchronize_session, совпадающие объекты в пределах Session будут помечены как удаленные и исключены.

ORM-enabled delete, 1.x style:

session.query(User).filter(User.name == "squidward").delete(synchronize_session="fetch")

ORM-enabled delete, 2.0 style:

from sqlalchemy import delete

stmt = (
    delete(User)
    .where(User.name == "squidward")
    .execution_options(synchronize_session="fetch")
)

session.execute(stmt)

Выбор стратегии синхронизации

Как в версии 1.x, так и в версии 2.0 обновления и удаления с поддержкой ORM поддерживаются следующие значения для synchronize_session:

  • False - не синхронизировать сессию. Этот вариант является наиболее эффективным и надежным после истечения срока действия сессии, что обычно происходит после commit() или явного использования expire_all(). До истечения срока действия, объекты, которые были обновлены или удалены в базе данных, могут оставаться в сессии с устаревшими значениями, что может привести к запутанным результатам.

  • 'fetch' - Извлекает идентификатор первичного ключа затронутых строк, либо выполняя SELECT перед UPDATE или DELETE, либо используя RETURNING, если база данных поддерживает это, так что объекты в памяти, затронутые операцией, могут быть обновлены новыми значениями (обновления) или исключены из Session (удаления). Обратите внимание, что эта стратегия синхронизации недоступна, если в данной конструкции update() или delete() явно указаны столбцы для UpdateBase.returning().

  • 'evaluate' - Оценить критерии WHERE, заданные в операторе UPDATE или DELETE в Python, чтобы найти совпадающие объекты в пределах Session. Этот подход не добавляет никаких обходных путей и при отсутствии поддержки RETURNING является более эффективным. Для операторов UPDATE или DELETE со сложными критериями стратегия 'evaluate' может не справиться с оценкой выражения в Python и выдать ошибку. Если это произойдет, используйте для этой операции стратегию 'fetch'.

    Предупреждение

    Стратегию "evaluate" следует избегать, если операция UPDATE выполняется на Session, имеющем много объектов с истекшим сроком действия, поскольку ей обязательно потребуется обновлять эти объекты по мере их нахождения, что приведет к появлению SELECT для каждого из них. У Session могут быть просроченные объекты, если он используется в нескольких вызовах Session.commit() и флаг Session.expire_on_commit имеет значение по умолчанию True.

Предупреждение

Дополнительные предостережения для обновлений и удалений с поддержкой ORM.

Функции UPDATE и DELETE с поддержкой ORM позволяют обойти автоматизацию единиц работы ORM в пользу возможности создания одного оператора UPDATE или DELETE, который соответствует сразу нескольким строкам без каких-либо сложностей.

  • Эти операции не предлагают каскадирование отношений в Python - предполагается, что ON UPDATE CASCADE и/или ON DELETE CASCADE настроены для всех ссылок на внешние ключи, которые требуют этого, иначе база данных может выдать нарушение целостности, если ссылки на внешние ключи выполняются.

  • После UPDATE или DELETE зависимые объекты в Session, которые были затронуты ON UPDATE CASCADE или ON DELETE CASCADE на связанных таблицах, могут не содержать текущего состояния; эта проблема решается после истечения срока действия Session, что обычно происходит при Session.commit() или может быть принудительно выполнено с помощью Session.expire_all().

  • Стратегия 'fetch' при выполнении в базе данных, которая не поддерживает RETURNING, например, MySQL или SQLite, приводит к выбросу дополнительного оператора SELECT, что может снизить производительность. Используйте эхо SQL при разработке, чтобы оценить влияние выдаваемого SQL.

  • UPDATE и DELETE с поддержкой ORM не обрабатывают наследование объединенных таблиц автоматически. Если операция выполняется над несколькими таблицами, обычно используются отдельные операторы UPDATE / DELETE для отдельных таблиц. Некоторые базы данных поддерживают UPDATE для нескольких таблиц. В этом случае могут применяться рекомендации, аналогичные тем, что описаны в UPDATE… FROM.

  • Критерии WHERE, необходимые для ограничения полиморфной идентичности определенными подклассами для отображений с наследованием одной таблицы, включаются автоматически. Это применимо только для отображения подклассов, не имеющих собственной таблицы.

    Изменено в версии 1.4: Обновления/удаления ORM теперь автоматически учитывают критерии WHERE, добавленные для отображений с одним наследованием.

  • Опция with_loader_criteria() поддерживается операциями обновления и удаления ORM; критерии здесь будут добавлены к критериям испускаемого оператора UPDATE или DELETE, а также учтены в процессе «синхронизации».

  • Чтобы перехватить операции UPDATE и DELETE с поддержкой ORM с помощью обработчиков событий, используйте событие SessionEvents.do_orm_execute().

Выбор объектов ORM Inline с помощью UPDATE… ВОЗВРАТ или INSERT…ВОЗВРАТ

Этот раздел переехал. См. Использование INSERT, UPDATE и ON CONFLICT (т.е. upsert) для возврата объектов ORM.

Автозапуск

Добавлено в версии 1.4: В этом разделе описывается поведение, которое является новым в SQLAlchemy 1.4 и не относится к предыдущим версиям. Более подробная информация об изменении «autobegin» находится в Session features new «autobegin» behavior.

Объект Session характеризуется поведением, известным как autobegin. Это означает, что объект Session будет считать себя находящимся в состоянии «транзакции», как только с объектом Session будет выполнена какая-либо работа, связанная с модификацией внутреннего состояния объекта Session в связи с изменением состояния объекта, или с операциями, требующими подключения к базе данных.

Когда Session впервые создается, транзакционное состояние отсутствует. Транзакционное состояние начинается автоматически, когда вызывается метод, такой как Session.add() или Session.execute(), или аналогично, если выполняется Query для возврата результатов (что в конечном итоге использует Session.execute()), или если изменяется атрибут объекта persistent.

Состояние транзакции можно проверить, обратившись к методу Session.in_transaction(), который возвращает True или False, указывая, был ли выполнен шаг «autobegin». Хотя обычно в этом нет необходимости, метод Session.get_transaction() возвращает фактический объект SessionTransaction, который представляет данное состояние транзакции.

Транзакционное состояние Session можно также запустить явно, вызвав метод Session.begin(). При вызове этого метода Session переходит в «транзакционное» состояние безоговорочно. Session.begin() может использоваться в качестве менеджера контекста, как описано в Составление блока «начало / фиксация / откат.

Изменено в версии 1.4.12: - autobegin now correctly occurs if object attributes are modified; previously this was not occurring.

Совершение

Session.commit() используется для фиксации текущей транзакции. По своей сути это означает, что он испускает COMMIT на всех текущих соединениях с базой данных, где идет транзакция; с точки зрения DBAPI это означает, что метод connection.commit() DBAPI вызывается на каждом соединении DBAPI.

Если для Session не существует транзакции, указывающей на то, что с момента предыдущего вызова Session для этого Session.commit() не было вызвано никаких операций, метод начнет и зафиксирует только внутреннюю «логическую» транзакцию, которая обычно не влияет на базу данных, если только не были обнаружены ожидающие изменения флеша, но все равно вызовет обработчики событий и правила истечения срока действия объектов.

Операция Session.commit() безоговорочно выдает Session.flush() перед выполнением COMMIT на соответствующих соединениях с базой данных. Если не обнаружено никаких ожидающих изменений, то SQL не передается в базу данных. Это поведение не настраивается и не зависит от параметра Session.autoflush.

После этого Session.commit() выполнит COMMIT фактической транзакции базы данных или транзакций, если таковые имеются.

Наконец, все объекты в пределах Session становятся expired при закрытии транзакции. Это делается для того, чтобы при следующем обращении к экземплярам, либо через доступ к атрибутам, либо если они присутствуют в результате SELECT, они получали самое последнее состояние. Это поведение может контролироваться флагом Session.expire_on_commit, который может быть установлен в False, если такое поведение нежелательно.

См.также

Автозапуск

Откат

Session.rollback() откатывает текущую транзакцию, если таковая имеется. Если транзакция отсутствует, метод проходит молча.

При стандартной конфигурации сессии состояние сессии после отката после начала транзакции либо через autobegin, либо явным вызовом метода Session.begin() выглядит следующим образом:

  • Все транзакции сворачиваются, а все соединения возвращаются в пул соединений, если только сессия не была связана непосредственно с соединением, в этом случае соединение сохраняется (но все равно сворачивается).

  • Объекты, которые изначально находились в состоянии pending, когда они были добавлены в Session в течение жизни транзакции, удаляются, что соответствует откату оператора INSERT. Состояние их атрибутов остается неизменным.

  • Объекты, которые были помечены как deleted в течение жизни транзакции, переводятся обратно в состояние persistent, что соответствует откату их оператора DELETE. Обратите внимание, что если эти объекты были сначала помечены как pending в рамках транзакции, то эта операция будет иметь приоритет.

  • Все объекты, которые не были удалены, полностью истекли - это происходит независимо от настройки Session.expire_on_commit.

Поняв это состояние, Session может спокойно продолжать использование после отката.

Изменено в версии 1.4: Объект Session теперь имеет отложенное поведение «begin», как описано в autobegin. Если транзакция не начата, методы Session.commit() и Session.rollback() не имеют эффекта. Такое поведение не наблюдалось до версии 1.4, поскольку в режиме без автокоммита транзакция всегда неявно присутствовала.

Когда Session.flush() терпит неудачу, обычно по таким причинам, как нарушение первичного ключа, внешнего ключа или ограничения «not nullable», автоматически выдается ROLLBACK (в настоящее время невозможно продолжить работу после частичного сбоя). Однако в этот момент Session переходит в состояние, известное как «неактивное», и вызывающее приложение должно всегда явно вызывать метод Session.rollback(), чтобы Session мог вернуться в состояние, пригодное для использования (его также можно просто закрыть и выбросить). Дальнейшее обсуждение см. в разделе FAQ по адресу «Транзакция этого сеанса была откатана из-за предыдущего исключения во время промывки». (или аналогично).

См.также

Автозапуск

Закрытие

Метод Session.close() выдает команду Session.expunge_all(), которая удаляет все ORM-сопоставленные объекты из сессии, и releases любые транзакционные ресурсы/ресурсы соединения из объекта(ов) Engine, с которым он связан. Когда соединения возвращаются в пул соединений, транзакционное состояние также сворачивается.

Когда Session закрыт, он по существу находится в исходном состоянии, когда он был впервые создан, и может быть использован снова. В этом смысле метод Session.close() больше похож на «сброс» обратно в чистое состояние и не так сильно похож на метод «закрытия базы данных».

Рекомендуется ограничивать область действия Session вызовом Session.close() в конце, особенно если не используются методы Session.commit() или Session.rollback(). Session может использоваться в качестве менеджера контекста для обеспечения вызова Session.close():

with Session(engine) as session:
    result = session.execute(select(User))

# closes session automatically

Изменено в версии 1.4: Объект Session имеет отложенное поведение «begin», как описано в autobegin. больше не начинает немедленно новую транзакцию после вызова метода Session.close().

Часто задаваемые вопросы

К этому моменту у многих пользователей уже возникают вопросы о сессиях. В этом разделе представлен мини-FAQ (обратите внимание, что у нас также есть real FAQ) по самым основным вопросам, с которыми можно столкнуться при использовании Session.

Когда я должен сделать sessionmaker?

Только один раз, где-то в глобальной области видимости вашего приложения. Это должно рассматриваться как часть конфигурации вашего приложения. Если ваше приложение имеет три файла .py в пакете, вы можете, например, поместить строку sessionmaker в ваш файл __init__.py; с этого момента ваши другие модули говорят «from mypackage import Session». Таким образом, все остальные просто используют Session(), а конфигурация этой сессии контролируется центральным модулем.

Если ваше приложение запускается, выполняет импорт, но не знает, к какой базе данных оно будет подключаться, вы можете привязать Session на уровне «класса» к движку позже, используя sessionmaker.configure().

В примерах этого раздела мы часто будем показывать, что sessionmaker создается прямо над строкой, где мы вызываем Session. Но это только для примера! В действительности sessionmaker будет находиться где-то на уровне модуля. Вызовы для инстанцирования Session будут размещены в той точке приложения, где начинается общение с базой данных.

Когда я строю Session, когда я фиксирую его и когда я закрываю его?

Session обычно строится в начале логической операции, где потенциально ожидается доступ к базе данных.

Session, когда бы он ни использовался для связи с базой данных, начинает транзакцию базы данных, как только он начинает взаимодействие. Эта транзакция продолжается до тех пор, пока Session не будет откатана, зафиксирована или закрыта. Session начнет новую транзакцию, если будет использован снова, после завершения предыдущей транзакции; из этого следует, что Session способен прожить множество транзакций, хотя только одну за раз. Мы называем эти две концепции областью транзакций и областью сессий.

Обычно не очень сложно определить оптимальные точки начала и окончания области видимости Session, хотя широкое разнообразие возможных архитектур приложений может создавать сложные ситуации.

Некоторые примерные сценарии включают:

  • Веб-приложения. В этом случае лучше всего воспользоваться интеграцией SQLAlchemy, предоставляемой используемым веб-фреймворком. В противном случае, основной схемой является создание Session в начале веб-запроса, вызов метода Session.commit() в конце веб-запросов, которые выполняют POST, PUT или DELETE, а затем закрытие сессии в конце веб-запроса. Также обычно полезно установить Session.expire_on_commit в False, чтобы при последующем доступе к объектам, полученным из Session на уровне представления, не нужно было выдавать новые SQL-запросы для обновления объектов, если транзакция уже была зафиксирована.

  • Фоновый демон, порождающий дочерние форки, захочет создать Session, локальный для каждого дочернего процесса, работать с этим Session в течение всего времени выполнения «задания», которое обрабатывает форк, а затем снести его, когда задание будет выполнено.

  • Для сценария командной строки приложение создаст единственный, глобальный Session, который устанавливается, когда программа начинает выполнять свою работу, и фиксирует его сразу после того, как программа завершает свою задачу.

  • Для приложения, управляемого графическим интерфейсом, область действия Session может находиться в пределах генерируемого пользователем события, такого как нажатие кнопки. Или же область действия может соответствовать явному взаимодействию с пользователем, например, пользователь «открывает» серию записей, а затем «сохраняет» их.

Как правило, приложение должно управлять жизненным циклом сессии внешне к функциям, которые работают с конкретными данными. Это фундаментальное разделение задач, которое сохраняет специфические для данных операции агностичными по отношению к контексту, в котором они обращаются к данным и манипулируют ими.

Например, не делай этого:

### this is the **wrong way to do it** ###


class ThingOne(object):
    def go(self):
        session = Session()
        try:
            session.query(FooBar).update({"x": 5})
            session.commit()
        except:
            session.rollback()
            raise


class ThingTwo(object):
    def go(self):
        session = Session()
        try:
            session.query(Widget).update({"q": 18})
            session.commit()
        except:
            session.rollback()
            raise


def run_my_program():
    ThingOne().go()
    ThingTwo().go()

Храните жизненный цикл сессии (и обычно транзакции) отдельным и внешним. Пример ниже иллюстрирует, как это может выглядеть, и дополнительно использует менеджер контекста Python (т.е. ключевое слово with:), чтобы автоматически управлять областью действия Session и его транзакцией:

### this is a **better** (but not the only) way to do it ###


class ThingOne(object):
    def go(self, session):
        session.query(FooBar).update({"x": 5})


class ThingTwo(object):
    def go(self, session):
        session.query(Widget).update({"q": 18})


def run_my_program():
    with Session() as session:
        with session.begin():
            ThingOne().go(session)
            ThingTwo().go(session)

Изменено в версии 1.4: Session может использоваться в качестве менеджера контекста без использования внешних вспомогательных функций.

Является ли сессия кэшем?

Да… нет. Он в некоторой степени используется как кэш, поскольку реализует шаблон identity map и хранит объекты по первичному ключу. Однако он не выполняет никакого кэширования запросов. Это означает, что если вы говорите session.query(Foo).filter_by(name='bar'), даже если Foo(name='bar') находится прямо там, в карте идентификации, сессия не имеет об этом никакого представления. Она должна отправить SQL к базе данных, получить строки обратно, а затем, когда она увидит первичный ключ в строке, тогда она может посмотреть в локальной карте идентификации и увидеть, что объект уже там. Только когда вы говорите query.get({some primary key}), Session не должен выдавать запрос.

Кроме того, по умолчанию сессия хранит экземпляры объектов, используя слабую ссылку. Это также противоречит цели использования сеанса в качестве кэша.

Session не предназначен для того, чтобы быть глобальным объектом, к которому все обращаются как к «реестру» объектов. Это скорее задача кэша второго уровня. SQLAlchemy предоставляет шаблон для реализации кэширования второго уровня с использованием dogpile.cache на примере Кэширование Dogpile.

Как я могу получить Session для определенного объекта?

Используйте метод класса Session.object_session(), доступный на Session:

session = Session.object_session(someobject)

Также можно использовать более новую систему API для проверки во время выполнения:

from sqlalchemy import inspect

session = inspect(someobject).session

Является ли сессия потокобезопасной?

Session в значительной степени предназначен для использования в непоследовательном режиме, что обычно означает использование только в одном потоке одновременно.

Session следует использовать таким образом, чтобы один экземпляр существовал для одной серии операций в рамках одной транзакции. Один из целесообразных способов добиться такого эффекта - связать Session с текущим потоком (см. раздел Контекстные/поточно-локальные сеансы для справки). Другой способ - использовать схему, в которой Session передается между функциями и не используется совместно с другими потоками.

Суть в том, что вы не должны хотеть использовать сессию с несколькими параллельными потоками. Это все равно, что заставить всех в ресторане есть из одной тарелки. Сессия - это локальное «рабочее пространство», которое вы используете для выполнения определенного набора задач; вы не хотите или не должны использовать эту сессию совместно с другими потоками, которые выполняют какую-то другую задачу.

Убедиться, что Session используется только в одном потоке одновременно, называется подходом к параллелизму «ничего не разделять». Но на самом деле, отсутствие совместного использования Session подразумевает более существенную закономерность; это означает, что не только сам объект Session, но и все объекты, связанные с этим Session, должны находиться в пределах одного потока. Набор сопоставленных объектов, связанных с Session, по сути, является прокси для данных в строках базы данных, доступ к которым осуществляется через соединение с базой данных, и поэтому, как и сам Session, весь набор объектов является просто крупномасштабным прокси для соединения (или соединений) с базой данных. В конечном счете, мы не допускаем одновременного доступа в основном к самому соединению DBAPI; но поскольку Session и все связанные с ним объекты являются прокси для этого соединения DBAPI, весь граф по существу небезопасен для одновременного доступа.

Если в одной задаче участвует несколько потоков, то можно рассмотреть возможность разделения сессии и ее объектов между этими потоками; однако в этом крайне необычном сценарии приложению необходимо обеспечить надлежащую схему блокировки, чтобы не было последовательного доступа к Session или его состоянию. Более распространенный подход к этой ситуации - поддерживать один Session для каждого параллельного потока, но вместо этого копировать объекты из одного Session в другой, часто используя метод Session.merge() для копирования состояния объекта в новый объект, локальный для другого Session.

Вернуться на верх