SQLAlchemy: różnica silnika, połączenia i sesji

134

Używam SQLAlchemy i istnieją co najmniej trzy podmioty: engine, sessioni connection, które mają executemetody, więc jeśli na przykład chcemy, aby zaznaczyć wszystkie rekordy ze tablemogę to zrobić

engine.execute(select([table])).fetchall()

i to

connection.execute(select([table])).fetchall()

a nawet to

session.execute(select([table])).fetchall()

- wyniki będą takie same.

Jak rozumiem, jeśli ktoś engine.executego używa , tworzy connection, otwiera session(Alchemy zajmuje się tym za Ciebie) i wykonuje zapytanie. Ale czy istnieje globalna różnica między tymi trzema sposobami wykonywania takiego zadania?

ololobus
źródło
Myślę, że twoja odpowiedź jest właśnie tutaj: hackersandslackers.com/…
SeF

Odpowiedzi:

123

Podsumowanie jednowierszowe:

Zachowanie execute()jest taka sama we wszystkich przypadkach, ale są 3 różne sposoby, w Engine, Connectioni Sessionzajęcia.

Co to dokładnie jest execute():

Aby zrozumieć zachowanie execute(), musimy przyjrzeć się Executableklasie. Executablejest nadklasą dla wszystkich typów obiektów typu „instrukcja”, w tym select (), delete (), update (), insert (), text () - najprościej mówiąc, Executablejest to konstrukcja wyrażenia SQL obsługiwana w SQLAlchemy.

We wszystkich przypadkach execute()metoda przyjmuje tekst SQL lub skonstruowane wyrażenie SQL, tj. Dowolną z różnych konstrukcji wyrażeń SQL obsługiwanych w SQLAlchemy i zwraca wyniki zapytania (a ResultProxy- Zawija DB-APIobiekt kursora, aby zapewnić łatwiejszy dostęp do kolumn wiersza).


Aby wyjaśnić to dalej (tylko w celu wyjaśnienia pojęciowego, a nie zalecanego podejścia) :

Oprócz Engine.execute()(wykonywanie bezpołączeniowe), Connection.execute()i Session.execute()możliwe jest również użycie execute()bezpośrednio w dowolnej Executablekonstrukcji. ExecutableKlasa ma własną realizację execute()- zgodnie z oficjalnej dokumentacji, jedna linia tekstu o tym, co execute()robi, jest „ skompilować i wykonać toExecutable ”. W tym przypadku musimy jawnie powiązać Executable(konstrukcję wyrażenia SQL) z Connectionobiektem lub Engineobiektem (który niejawnie pobiera Connectionobiekt), aby execute()wiedział, gdzie wykonać SQL.

Poniższy przykład dobrze to ilustruje - biorąc pod uwagę tabelę jak poniżej:

from sqlalchemy import MetaData, Table, Column, Integer

meta = MetaData()
users_table = Table('users', meta,
    Column('id', Integer, primary_key=True),
    Column('name', String(50)))

Jawne wykonanie czyli Connection.execute()przekazanie tekstu SQL lub skonstruowanego wyrażenia SQL do execute()metody Connection:

engine = create_engine('sqlite:///file.db')
connection = engine.connect()
result = connection.execute(users_table.select())
for row in result:
    # ....
connection.close()

Jawne wykonanie bezpołączeniowe tj. Engine.execute()- przekazanie tekstu SQL lub skonstruowanego wyrażenia SQL bezpośrednio do execute()metody Engine:

engine = create_engine('sqlite:///file.db')
result = engine.execute(users_table.select())
for row in result:
    # ....
result.close()

Niejawne wykonanie, tj. Executable.execute()- jest również bezpołączeniowe i wywołuje execute()metodę Executable, to znaczy wywołuje execute()metodę bezpośrednio na samej SQLkonstrukcji wyrażenia (jej instancji Executable).

engine = create_engine('sqlite:///file.db')
meta.bind = engine
result = users_table.select().execute()
for row in result:
    # ....
result.close()

Uwaga: w celu wyjaśnienia podano domyślny przykład wykonania - ten sposób wykonania nie jest zdecydowanie zalecany - zgodnie z dokumentami :

„Niejawne wykonanie” to bardzo stary wzorzec użycia, który w większości przypadków jest bardziej zagmatwany niż pomocny, a jego stosowanie jest odradzane. Oba schematy wydają się zachęcać do nadużywania celowych „skrótów” w projektowaniu aplikacji, co prowadzi do późniejszych problemów.


Twoje pytania:

Jak rozumiem, jeśli ktoś używa engine.execute to tworzy połączenie, otwiera sesję (Alchemia o to dba) i wykonuje zapytanie.

Masz rację co do części "jeśli ktoś engine.executejej użyje , tworzy connection" ale nie dla "otwiera session(alchemia dba o to za ciebie) i wykonuje zapytanie" - Używanie Engine.execute()i Connection.execute()jest (prawie) tym samym, formalnie Connectionobiekt jest tworzony niejawnie , aw późniejszym przypadku jawnie tworzymy instancję. To, co naprawdę dzieje się w tym przypadku, to:

`Engine` object (instantiated via `create_engine()`) -> `Connection` object (instantiated via `engine_instance.connect()`) -> `connection.execute({*SQL expression*})`

Ale czy istnieje globalna różnica między tymi trzema sposobami wykonywania takiego zadania?

W warstwie DB jest dokładnie to samo, wszystkie wykonują SQL (wyrażenie tekstowe lub różne konstrukcje wyrażeń SQL). Z punktu widzenia aplikacji są dwie możliwości:

  • Wykonanie bezpośrednie - przy użyciu Engine.execute()lubConnection.execute()
  • Korzystanie sessions- sprawnie obsługuje transakcję jako pojedyncza jednostka-of-pracy, z łatwością za pomocą session.add(), session.rollback(), session.commit(), session.close(). Jest to sposób na interakcję z bazą danych w przypadku ORM, czyli mapowanych tabel. Zapewnia identity_map do natychmiastowego uzyskiwania dostępu do już używanych lub nowo utworzonych / dodanych obiektów podczas pojedynczego żądania.

Session.execute()ostatecznie używa Connection.execute()metody wykonania instrukcji w celu wykonania instrukcji SQL. Korzystanie z Sessionobiektu jest zalecanym sposobem SQLAlchemy ORM dla aplikacji do interakcji z bazą danych.

Fragment dokumentacji :

Należy zauważyć, że podczas korzystania z ORM SQLAlchemy te obiekty nie są generalnie dostępne; zamiast tego obiekt Session jest używany jako interfejs do bazy danych. Jednak w przypadku aplikacji, które są zbudowane wokół bezpośredniego użycia tekstowych instrukcji SQL i / lub konstrukcji wyrażeń SQL bez udziału usług zarządzania wyższego poziomu ORM, silnik i połączenie są królem (i królową?) - czytaj dalej.

Nabeel Ahmed
źródło
Słowo „bezpołączeniowe” oznacza, że ​​żadne połączenie nie jest tworzone, co zgodnie z odpowiedzią Neala nie ma miejsca.
Atom
111

Odpowiedź Nabeela obejmuje wiele szczegółów i jest pomocna, ale jej śledzenie było dla mnie niejasne. Ponieważ jest to obecnie pierwszy wynik Google dotyczący tego problemu, dodam moje zrozumienie dla przyszłych osób, które znajdą to pytanie:

Uruchamianie .execute ()

Ponieważ zarówno OP, jak i Nabell Ahmed zauważają, podczas wykonywania zwykłego wyniku SELECT * FROM tablenamenie ma różnicy w podanym wyniku.

Różnice między tymi trzema obiektami stają się ważne, w zależności od kontekstu, że SELECToświadczenie jest stosowany lub, częściej, gdy chcesz robić inne rzeczy, jak INSERT, DELETEitp

Kiedy ogólnie używać silnika, połączenia, sesji

  • Silnik to obiekt najniższego poziomu używany przez SQLAlchemy. To utrzymuje pulę połączeń dostępnych do wykorzystania, gdy aplikacja potrzebuje porozmawiać z bazy danych. .execute()to wygodna metoda, która najpierw wywołuje, conn = engine.connect(close_with_result=True)a następnie conn.execute(). Parametr close_with_result oznacza, że ​​połączenie jest zamykane automatycznie. (Parafrazuję nieco kod źródłowy, ale zasadniczo prawda). edycja: Oto kod źródłowy pliku engine.execute

    Możesz użyć silnika do wykonania surowego kodu SQL.

    result = engine.execute('SELECT * FROM tablename;')
    #what engine.execute() is doing under the hood
    conn = engine.connect(close_with_result=True)
    result = conn.execute('SELECT * FROM tablename;')
    
    #after you iterate over the results, the result and connection get closed
    for row in result:
        print(result['columnname']
    
    #or you can explicitly close the result, which also closes the connection
    result.close()

    Jest to omówione w dokumentacji w ramach podstawowego użycia .

  • Połączenie jest (jak widzieliśmy powyżej) tym, co faktycznie wykonuje zapytanie SQL. Powinieneś to zrobić zawsze, gdy chcesz mieć większą kontrolę nad atrybutami połączenia, kiedy zostanie ono zamknięte, itp. Na przykład bardzo ważnym tego przykładem jest Transakcja , która pozwala zdecydować, kiedy zatwierdzić zmiany w bazie danych. Podczas normalnego użytkowania zmiany są automatycznie zatwierdzane. Korzystając z transakcji, możesz (na przykład) uruchomić kilka różnych instrukcji SQL, a jeśli coś pójdzie nie tak z jedną z nich, możesz cofnąć wszystkie zmiany naraz.

    connection = engine.connect()
    trans = connection.begin()
    try:
        connection.execute("INSERT INTO films VALUES ('Comedy', '82 minutes');")
        connection.execute("INSERT INTO datalog VALUES ('added a comedy');")
        trans.commit()
    except:
        trans.rollback()
        raise

    Umożliwiłoby to cofnięcie obu zmian w przypadku niepowodzenia jednej z nich, na przykład w przypadku zapomnienia o utworzeniu tabeli datalogu.

    Jeśli więc wykonujesz surowy kod SQL i potrzebujesz kontroli, użyj połączeń

  • Sesje są używane w aspekcie zarządzania relacjami z obiektami (ORM) w SQLAlchemy (w rzeczywistości można to zobaczyć na podstawie sposobu ich importowania:) from sqlalchemy.orm import sessionmaker. Korzystają z połączeń i transakcji pod maską, aby uruchamiać automatycznie generowane instrukcje SQL. .execute()to wygodna funkcja, która przechodzi do wszystkiego, do czego jest przypisana sesja (zwykle jest to silnik, ale może to być połączenie).

    Jeśli korzystasz z funkcji ORM, użyj sesji; jeśli wykonujesz tylko proste zapytania SQL, które nie są powiązane z obiektami, prawdopodobnie lepiej będzie używać połączeń bezpośrednio.

Neal
źródło
1
Czy instrukcje wstawiania nie powinny być ujęte w podwójne cudzysłowy ""?
mingchau
2
@mingchau Tak, masz rację, moje pojedyncze cudzysłowy przeszkadzałyby sobie nawzajem, podwójne cudzysłowy są znacznie łatwiejsze do uniknięcia tego problemu. Zaktualizowano.
Neal
Biorąc pod uwagę utworzoną sesję, w jaki sposób moja sesja łączy się z połączeniem z PostgreSQL?
Raju yourPepe
@RajuyourPepe my_session.connection(). Dokumenty: docs.sqlalchemy.org/en/13/orm/… .
Neal
Poważnie ? Obiekt „Session” nie ma atrybutu „connect” ”, oto co znalazłem
Raju yourPepe
0

Oto przykład uruchomienia DCL (Data Control Language), takiego jak GRANT

def grantAccess(db, tb, user):
  import sqlalchemy as SA
  import psycopg2

  url = "{d}+{driver}://{u}:{p}@{h}:{port}/{db}".\
            format(d="redshift",
            driver='psycopg2',
            u=username,
            p=password,
            h=host,
            port=port,
            db=db)
  engine = SA.create_engine(url)
  cnn = engine.connect()
  trans = cnn.begin()
  strSQL = "GRANT SELECT on table " + tb + " to " + user + " ;"
  try:
      cnn.execute(strSQL)
      trans.commit()
  except:
      trans.rollback()
      raise
Jie
źródło