SQLAlchemy: Створення проти повторного використання сесії


98

Просто швидке запитання: SQLAlchemy розповідає про виклик sessionmaker()один раз, але виклик результуючого Session()класу кожен раз, коли вам потрібно поговорити зі своєю БД. Для мене це означає друге, що я зробив би своє перше session.add(x)чи щось подібне, я би зробив перше

from project import Session
session = Session()

Те, що я робив до цього часу, - це здійснити дзвінок session = Session()у своїй моделі один раз, а потім завжди імпортувати один і той же сеанс кудись у моїй програмі. Оскільки це веб-додатки, зазвичай це означає те саме (як виконується одне представлення).

Але де різниця? Який недолік використовувати один сеанс весь час проти використання його для моєї бази даних, поки моя функція не буде виконана, а потім створити новий наступного разу, коли я захочу поговорити зі своєю БД?

Я розумію, що якщо я використовую кілька потоків, кожен повинен отримати власний сеанс. Але використовуючи scoped_session(), я вже переконуюсь, що проблеми не існує, чи не так?

Будь ласка, уточніть, чи якесь із моїх припущень невірно.

Відповіді:


224

sessionmaker()це фабрика, там можна заохочувати розміщення параметрів конфігурації для створення нових Sessionоб’єктів лише в одному місці. Це необов'язково, оскільки ви могли так само легко зателефонувати в Session(bind=engine, expire_on_commit=False)будь-який час, коли вам знадобилося новеSession , за винятком того, що його багатослівний і зайвий, і я хотів зупинити розповсюдження дрібних "помічників", які кожен підходив до проблеми цієї надмірності в деяких нових і більш заплутаний спосіб.

Отже, sessionmaker()це лише інструмент, який допоможе вам створити Sessionоб’єкти, коли вони вам потрібні.

Наступна частина. Я думаю, що питання полягає в тому, яка різниця між створенням нового Session()в різних точках, а не просто використанням одного до кінця. Відповідь, не дуже. Sessionє контейнером для всіх об'єктів, які ви вкладаєте в нього, а потім він також відстежує відкриту транзакцію. У той момент, коли ви дзвоните rollback()або commit(), транзакція закінчена, і Sessionне має зв'язку з базою даних, поки не буде покликано знову випромінювати SQL. Посилання, які він тримає до ваших відображених об’єктів, є слабкими посиланнями, за умови, що об'єкти чисті від очікуючих змін, тому навіть у зв'язку з цим налаштуванням всі об'єкти закінчуються після фіксації. Якщо цеSession воля випорожниться назад до абсолютно нового стану, коли ваша програма втратить усі посилання на відображені об’єкти. Якщо залишити його за замовчуванням"expire_on_commit"Sessionзависає протягом п’яти-двадцяти хвилин, і всі види речей змінилися в базі даних наступного разу, коли ви будете використовувати її, вона завантажить абсолютно новий стан наступного разу, коли ви отримаєте доступ до цих об'єктів, навіть якщо вони сидять у пам'яті двадцять хвилин.

У веб-додатках ми, як правило, кажемо, ей, чому б вам не зробити новенький Sessionна кожен запит, а не використовувати один і той же раз і знову. Ця практика гарантує, що новий запит починається "чисто". Якщо деякі об'єкти з попереднього запиту ще не були зібрані сміттям, і якщо, можливо, ви його вимкнули "expire_on_commit", можливо, якийсь стан із попереднього запиту все ще зависає, і цей стан може бути навіть досить старим. Якщо ви обережно залишаєте expire_on_commitувімкненим і обов'язково зателефонували commit()або rollback()на запит закінчились, тоді це добре, але якщо ви почнете з абсолютно нового Session, тоді навіть не виникає жодних питань, з якими ви починаєте чисто. Тож ідея починати кожен запит з новогоSessionнасправді просто найпростіший спосіб переконатися, що ви починаєте свіжим, і використовувати його в середині серії операцій. Не впевнений, чи відповідає це на ваше запитання.expire_on_commit майже необов'язковим, оскільки цей прапор може спричинити багато додаткового SQL для операції, яка викликаєcommit()

Наступний раунд - це те, що ви згадуєте про нарізування різьби. Якщо ваш додаток багатопотокове, радимо переконатися, що Sessionвикористання використовується локальним для чогось. scoped_session()за замовчуванням робить його локальним для поточного потоку. У веб-додатку локальний запит насправді ще кращий. Flask-SQLAlchemy насправді надсилає користувацьку "функцію діапазону", щоб scoped_session()ви отримали сеанс, що охоплює запит. Середня програма Pyramid вписує Сесію в реєстр "запитів". Використовуючи подібні схеми, ідея "створити нову сесію під час запиту" продовжує виглядати як найпростіший спосіб зробити все прямо.


17
Нічого собі, це відповідає на всі мої запитання в частині SQLAlchemy і навіть додає інформацію про колбу та піраміду! Доданий бонус: розробники відповідають;) Я б хотів проголосувати не раз. Велике спасибі!
javex

Одне уточнення, якщо можливо: ви кажете, що expire_on_commit "може спричинити багато зайвих SQL" ... чи можете ви детальніше? Я думав, що expire_on_commit стосується лише того, що відбувається в оперативній пам'яті, а не того, що відбувається в базі даних.
Векі

3
expire_on_commit може призвести до більше SQL, якщо ви повторно використовуєте той самий сеанс ще раз, а деякі об’єкти все ще звисають на цьому сеансі, коли ви отримуєте доступ до них, ви отримуєте однорядний SELECT для кожного з них, оскільки вони оновлюються окремо. їх стан з точки зору нової угоди.
zzzeek

1
Привіт, @zzzeek. Дякую за відмінну відповідь. Я дуже новий в python і декількох речах, які я хочу уточнити: 1) Чи розумію я правильно, коли створюю новий "сеанс", викликаючи метод Session (), він створить транзакцію SQL, тоді транзакція буде відкрита до моменту сеансу фіксації / відкату. ? 2) Чи використовує сеанс () якийсь пул з'єднань або кожен раз здійснює нове з'єднання з sql?
Олексій Гурський

27

Окрім чудової відповіді zzzeek, ​​ось простий рецепт для швидкого створення самостійних закритих сесій:

from contextlib import contextmanager

from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker

@contextmanager
def db_session(db_url):
    """ Creates a context with an open SQLAlchemy session.
    """
    engine = create_engine(db_url, convert_unicode=True)
    connection = engine.connect()
    db_session = scoped_session(sessionmaker(autocommit=False, autoflush=True, bind=engine))
    yield db_session
    db_session.close()
    connection.close()

Використання:

from mymodels import Foo

with db_session("sqlite://") as db:
    foos = db.query(Foo).all()

3
Чи є причина, чому ви створюєте не просто новий сеанс, але і свіжий зв’язок?
danqing

Не дуже - це швидкий приклад показати механізм, хоча має сенс створити все свіже на тестуванні, де я найбільше використовую цей підхід. Розширити цю функцію слід з'єднанням як необов'язковий аргумент.
Берислав Лопак
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.