SQLAlchemy: Bir Oturum Oluşturma ve Yeniden Kullanma


100

Hızlı bir soru: SQLAlchemy bir kez aramaktan ancak DB'nizle her konuşmanız gerektiğinde ortaya çıkan sınıfı aramaktan bahsediyor . Benim için bu, ikinci yaptığım ilk veya benzer bir şey yapacağım anlamına geliyor, önce yapacağımsessionmaker()Session()session.add(x)

from project import Session
session = Session()

Şimdi ne kadar yaptığı arama yapmak oldu session = Session()benim modelinde bir kez ve daha sonra her zaman benim uygulamada aynı oturum hiçbir yerinde aktarın. Bu bir web uygulamaları olduğundan, bu genellikle aynı anlama gelir (bir görünümün çalıştırıldığı gibi).

Ama fark nerede? Bir oturumu her zaman kullanmanın, işlevim tamamlanana kadar veri tabanı işlerim için kullanmanın ve ardından DB'mle bir dahaki konuşmak istediğimde yeni bir oturum oluşturmanın dezavantajı nedir?

Birden fazla iş parçacığı kullanırsam, her birinin kendi oturumunu alması gerektiğini anlıyorum. Ama kullanarak scoped_session(), bu sorunun olmadığından zaten emin oluyorum, değil mi?

Varsayımlarmdan herhangi birinin yanlış olup olmadığını lütfen netleştirin.

Yanıtlar:


229

sessionmaker()bir fabrika, yeni Sessionnesneler oluşturmak için yapılandırma seçeneklerini tek bir yere yerleştirmeyi teşvik etmek için var . Opsiyoneldir, çünkü Session(bind=engine, expire_on_commit=False)yenisine ihtiyaç duyduğunuz her an kolayca arayabilirdiniz Session, ancak bunun ayrıntılı ve gereksiz olması dışında ve küçük ölçekli "yardımcıların" çoğalmasını durdurmak istedim, her biri bu artıklık konusuna yeni bir şekilde yaklaştı. ve daha kafa karıştırıcı bir yol.

Bu sessionmaker(), Sessionihtiyaç duyduğunuzda nesneler oluşturmanıza yardımcı olacak bir araçtır .

Sonraki bölüm. Bence asıl soru, Session()çeşitli noktalarda yeni bir şeyler yapmak ile tamamen birini kullanmak arasındaki fark nedir ? Cevap çok değil. Sessioniçine koyduğunuz tüm nesneler için bir kaptır ve ardından açık bir işlemi de izler. rollback()Veya aradığınız anda commit()işlem bitmiştir ve SessionSQL'i yeniden yayması çağrılana kadar veritabanına bağlantısı yoktur. Eşlenen nesnelerinize tuttuğu bağlantılar, nesnelerin bekleyen değişikliklerden temiz olması koşuluyla, zayıf referans niteliğindedir; bu nedenle, bu bağlamda bile Session, uygulamanız eşlenen nesnelere olan tüm referansları kaybettiğinde kendisini yepyeni bir duruma geri boşaltır. Varsayılanı ile bırakırsan"expire_on_commit"ayarlandığında, tüm nesnelerin süresi bir işlemden sonra sona erer. Bu Sessionbeş veya yirmi dakika boyunca takılırsa ve veritabanında her türlü şey değiştiyse, bir dahaki sefere bu nesnelere eriştiğinizde bellekte oturuyor olsalar bile tüm yepyeni durumları yükleyecektir. yirmi dakika için.

Web uygulamalarında, biz genellikle deriz ki, hey neden Sessionher istek üzerine aynı şeyi tekrar tekrar kullanmak yerine yepyeni bir şey yapmıyorsunuz . Bu uygulama, yeni isteğin "temiz" başlamasını sağlar. Önceki istekte bulunan bazı nesneler henüz çöp toplanmadıysa ve belki kapattıysanız "expire_on_commit", belki önceki istekten gelen bazı durumlar hala ortalıkta dolaşıyor ve bu durum oldukça eski bile olabilir. Açık bırakmaya expire_on_commitve kesinlikle aramayı commit()ya rollback()da istek sonunda yapmaya dikkat ediyorsanız , sorun değil, ancak yepyeni biriyle başlarsanız Session, o zaman temiz başladığınıza dair herhangi bir soru bile yoktur. Dolayısıyla, her isteği yeni birSessionexpire_on_commitbu commit(), bir dizi işlemin ortasında çağıran bir işlem için fazladan SQL'e neden olabileceğinden , gerçekten de yeni bir başlangıç ​​yaptığınızdan emin olmanın ve hemen hemen isteğe bağlı kullanım yapmanın en basit yoludur . Sorunuzun cevabının bu olduğundan emin değilim.

Sonraki tur, iplik geçirme hakkında bahsettiğiniz şey. Uygulamanız çok iş parçacıklıysa, Sessionkullanımda olanın yerel bir şey olduğundan emin olmanızı öneririz . scoped_session()varsayılan olarak mevcut iş parçacığı için yerel yapar. Bir web uygulamasında, istek için yerel olan aslında daha da iyidir. Flask-SQLAlchemy aslında scoped_session(), istek kapsamlı bir oturum alabilmeniz için özel bir "kapsam işlevi" gönderir . Ortalama bir Piramit uygulaması, Oturumu "istek" kaydına yapıştırır. Bunun gibi şemaları kullanırken, "istek başladığında yeni Oturum oluştur" fikri, işleri yoluna koymanın en basit yolu gibi görünmeye devam eder.


17
Vay canına, bu SQLAlchemy kısmındaki tüm sorularıma cevap veriyor ve hatta Flask ve Piramit hakkında bazı bilgiler ekliyor! Eklenen bonus: geliştiriciler cevap verir;) Keşke birden fazla oy kullanabilseydim. Çok teşekkür ederim!
javex

Mümkünse bir açıklama: expire_on_commit "çok fazla SQL gerektirebilir" diyorsunuz ... daha fazla ayrıntı verebilir misiniz? Expire_on_commit'in veri tabanında olanlarla değil, sadece RAM'de olanlarla ilgilendiğini düşündüm.
Veky

3
expire_on_commit, aynı Oturumu tekrar kullanırsanız daha fazla SQL ile sonuçlanabilir ve bazı nesneler hala o Oturumda takılıyorsa, bunlara eriştiğinizde, her biri ayrı ayrı yenilenirken her biri için tek satırlı bir SELECT alacaksınız. yeni işlem açısından durumları.
zzzeek

1
Merhaba @zzzeek. Mükemmel cevabınız için teşekkürler. Python'da çok yeniyim ve açıklığa kavuşturmak istediğim birkaç şey var: 1) Session () yöntemini çağırarak yeni "oturum" oluşturduğumda doğru anlıyor muyum, bu SQL İşlemi oluşturacak, sonra işlem tamamlanana / geri alınana kadar işlem açılacak ? 2) Oturum () bir çeşit bağlantı havuzu kullanıyor mu veya her seferinde sql'ye yeni bağlantı yapıyor mu?
Alex Gurskiy

27

Mükemmel zzzeek'in cevabına ek olarak, işte hızlıca atılabilir, kendi kendini kapatan oturumlar oluşturmak için basit bir tarif:

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()

Kullanım:

from mymodels import Foo

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

3
Yalnızca yeni bir oturum değil, aynı zamanda yeni bir bağlantı da oluşturmanızın bir nedeni var mı?
danqing

Pek sayılmaz - bu, mekanizmayı göstermenin hızlı bir örneğidir, ancak bu yaklaşımı en çok kullandığım yerde testte her şeyi taze olarak yaratmak mantıklı olsa da. Bu işlevi, isteğe bağlı bir bağımsız değişken olarak bağlantıyla genişletmek kolay olmalıdır.
Berislav Lopac
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.