Vapauta huippusuorituskykyiset verkkosovellukset hallitsemalla asynkroninen tietokantaintegrointi FastAPI:ssä. Kattava opas SQLAlchemy- ja Databases-kirjaston esimerkeillä.
FastAPI-tietokantaintegrointi: Syväluotaus asynkronisiin tietokantatoimiin
Nykyaikaisen web-kehityksen maailmassa suorituskyky ei ole vain ominaisuus; se on perustavanlaatuinen vaatimus. Käyttäjät odottavat nopeita, reagoivia sovelluksia, ja kehittäjät etsivät jatkuvasti työkaluja ja tekniikoita näiden odotusten täyttämiseksi. FastAPI on noussut Python-ekosysteemin voimanpesäksi, jota juhlitaan sen uskomattomasta nopeudesta, mikä johtuu suurelta osin sen asynkronisesta luonteesta. Nopea kehys on kuitenkin vain yksi osa yhtälöä. Jos sovelluksesi viettää suurimman osan ajastaan odottaen hidasta tietokantaa, olet luonut huippusuorituskykyisen moottorin, joka on jumissa liikenneruuhkassa.
Tässä kohtaa asynkroniset tietokantatoiminnot tulevat kriittisiksi. Antamalla FastAPI-sovelluksesi käsitellä tietokantakyselyjä estämättä koko prosessia, voit vapauttaa todellisen rinnakkaisuuden ja rakentaa sovelluksia, jotka eivät ole vain nopeita, vaan myös erittäin skaalautuvia. Tämä kattava opas opastaa sinut asynkronisten tietokantojen integroinnin miksi, mitä ja miten FastAPI:n kanssa, jotta voit rakentaa todella huippusuorituskykyisiä palveluita globaalille yleisölle.
Ydinkonsepti: Miksi asynkroninen I/O on tärkeää
Ennen kuin sukellamme koodiin, on ratkaisevan tärkeää ymmärtää perusongelma, jonka async-toiminnot ratkaisevat: I/O-sidottu odotus.
Kuvittele erittäin taitava kokki keittiössä. Synkronisessa (tai estävässä) mallissa tämä kokki suorittaisi yhden tehtävän kerrallaan. He laittaisivat kattilallisen vettä liedelle kiehumaan ja sitten seisoisivat siinä katsellen sitä, kunnes se kiehuu. Vasta kun vesi kiehuu, he siirtyisivät vihannesten pilkkomiseen. Tämä on uskomattoman tehotonta. Kokin aika (CPU) menee hukkaan odotusajalla (I/O-toiminto).
Harkitse nyt asynkronista (ei-estävää) mallia. Kokki laittaa veden kiehumaan ja alkaa odottamisen sijaan heti pilkkoa vihanneksia. He voivat myös laittaa tarjottimen uuniin. He voivat vaihdella tehtävien välillä edistyäkseen usealla rintamalla odottaessaan hitaampien toimintojen (kuten veden kiehumisen tai paistamisen) valmistumista. Kun tehtävä on valmis (vesi kiehuu), kokille ilmoitetaan ja hän voi jatkaa kyseisen ruokalajin seuraavalla vaiheella.
Verkkosovelluksessa tietokantakyselyt, API-puhelut ja tiedostojen lukeminen vastaavat veden kiehumisen odottamista. Perinteinen synkroninen sovellus käsittelisi yhden pyynnön, lähettäisi kyselyn tietokantaan ja istuisi sitten joutilaana, estäen kaikki muut saapuvat pyynnöt, kunnes tietokanta vastaa. Asynkroninen sovellus, jonka virtaa Pythonin `asyncio` ja kehykset kuten FastAPI, voi käsitellä tuhansia samanaikaisia yhteyksiä vaihtamalla tehokkaasti niiden välillä aina, kun joku odottaa I/O:ta.
Asynkronisten tietokantatoimintojen tärkeimmät edut:
- Lisääntynyt rinnakkaisuus: Käsittele huomattavasti suurempi määrä samanaikaisia käyttäjiä samoilla laitteistoilla.
- Parannettu läpäisykyky: Käsittele enemmän pyyntöjä sekunnissa, koska sovellus ei juutu odottamaan tietokantaa.
- Parannettu käyttökokemus: Nopeammat vasteajat johtavat reagoivampaan ja tyydyttävämpään kokemukseen loppukäyttäjälle.
- Resurssitehokkuus: CPU:n ja muistin parempi käyttö, mikä voi johtaa pienempiin infrastruktuurikustannuksiin.
Asynkronisen kehitysympäristön määrittäminen
Aloittaaksesi tarvitset muutamia keskeisiä komponentteja. Käytämme PostgreSQL:ää tietokantamme näissä esimerkeissä, koska sillä on erinomainen tuki asynkronisille ajureille. Periaatteet koskevat kuitenkin myös muita tietokantoja, kuten MySQL ja SQLite, joilla on async-ajurit.
1. Ydinympäristö ja palvelin
Asenna ensin FastAPI ja ASGI-palvelin, kuten Uvicorn.
pip install fastapi uvicorn[standard]
2. Asynkronisen tietokantatyökalupakin valitseminen
Tarvitset kaksi pääkomponenttia keskustellaksesi tietokantasi kanssa asynkronisesti:
- Async-tietokanta-ajuri: Tämä on matalan tason kirjasto, joka kommunikoi tietokannan kanssa verkon kautta käyttämällä async-protokollaa. PostgreSQL:lle
asyncpgon de facto -standardi ja tunnetaan uskomattomasta suorituskyvystään. - Async-kyselyn rakentaja tai ORM: Tämä tarjoaa korkeamman tason, Python-tyyppisemmän tavan kirjoittaa kyselyjäsi. Tutkimme kahta suosittua vaihtoehtoa:
databases: Yksinkertainen, kevyt async-kyselyn rakentaja, joka tarjoaa puhtaan API:n raa'an SQL-suoritukseen.SQLAlchemy 2.0+: Tehokkaan ja monipuolisen SQLAlchemy ORM:n uusimmat versiot sisältävät natiivin, ensiluokkaisen tuen `asyncio`:lle. Tämä on usein suositeltava valinta monimutkaisille sovelluksille.
3. Asennus
Asennetaan tarvittavat kirjastot. Voit valita yhden työkalupakin tai asentaa molemmat kokeilua varten.
PostgreSQL:lle SQLAlchemy:lla ja `databases`:lla:
# Ajuri PostgreSQL:lle
pip install asyncpg
# SQLAlchemy 2.0+ -lähestymistapaa varten
pip install sqlalchemy
# 'databases'-kirjaston lähestymistapaa varten
pip install databases[postgresql]
Kun ympäristömme on valmis, tutkitaan, miten nämä työkalut integroidaan FastAPI-sovellukseen.
Strategia 1: Yksinkertaisuus `databases`-kirjastolla
databases-kirjasto on erinomainen lähtökohta. Se on suunniteltu yksinkertaiseksi ja tarjoaa ohuen kääreen pohjana olevien async-ajureiden päälle, mikä antaa sinulle async-raaka-SQL:n voiman ilman täysimittaisen ORM:n monimutkaisuutta.
Vaihe 1: Tietokantayhteys ja elinkaaren hallinta
Todellisessa sovelluksessa et halua muodostaa yhteyttä ja katkaista yhteyttä tietokantaan jokaisella pyynnöllä. Tämä on tehotonta. Sen sijaan muodostamme yhteyspoolin, kun sovellus käynnistyy, ja suljemme sen siististi, kun se sammuu. FastAPI:n tapahtumankäsittelijät (`@app.on_event("startup")` ja `@app.on_event("shutdown")`) sopivat tähän täydellisesti.
Luodaan tiedosto nimeltä main_databases.py:
import databases
import sqlalchemy
from fastapi import FastAPI
# --- Tietokannan määritys ---n# Korvaa todellisella tietokanta-URL-osoitteellasi
# Muoto asyncpg:lle: "postgresql+asyncpg://käyttäjä:salasana@host/dbname"
DATABASE_URL = "postgresql+asyncpg://käyttäjä:salasana@localhost/testdb"
database = databases.Database(DATABASE_URL)
# SQLAlchemy-mallin metatiedot (taulukon luomiseen)
metadata = sqlalchemy.MetaData()
# Määritä näytetaulu
notes = sqlalchemy.Table(
"notes",
metadata,
sqlalchemy.Column("id", sqlalchemy.Integer, primary_key=True),
sqlalchemy.Column("title", sqlalchemy.String(100)),
sqlalchemy.Column("content", sqlalchemy.String(500)),
)
# Luo moottori taulukon luomista varten (tämä osa on synkroninen)
# 'databases'-kirjasto ei käsittele skeeman luomista
engine = sqlalchemy.create_engine(DATABASE_URL.replace("+asyncpg", ""))
metadata.create_all(engine)
# --- FastAPI-sovellus ---
app = FastAPI(title="FastAPI databases -kirjastolla")
@app.on_event("startup")
async def startup():
print("Yhdistetään tietokantaan...")
await database.connect()
print("Tietokantayhteys muodostettu.")
@app.on_event("shutdown")
async def shutdown():
print("Katkaistaan yhteys tietokannasta...")
await database.disconnect()
print("Tietokantayhteys suljettu.")
# --- API-päätepisteet ---
@app.get("/")
def read_root():
return {"message": "Tervetuloa Async Database API:hen!"}
Tärkeimmät kohdat:
- Määritämme
DATABASE_URL:n käyttämälläpostgresql+asyncpg-kaaviota. - Globaali
database-objekti luodaan. - Käynnistystapahtumankäsittelijä kutsuu
await database.connect(), mikä alustaa yhteyspoolin. - Sammutustapahtumankäsittelijä kutsuu
await database.disconnect(), jotta kaikki yhteydet suljetaan siististi.
Vaihe 2: Asynkronisten CRUD-päätepisteiden toteuttaminen
Lisätään nyt päätepisteitä Create, Read, Update ja Delete (CRUD) -toimintojen suorittamiseen. Käytämme myös Pydantic:ia tietojen validointiin ja sarjoittamiseen.
Lisää seuraava tiedostoon main_databases.py:
from pydantic import BaseModel
from typing import List, Optional
# --- Pydantic-mallit tietojen validointiin ---
class NoteIn(BaseModel):
title: str
content: str
class Note(BaseModel):
id: int
title: str
content: str
# --- CRUD-päätepisteet ---
@app.post("/notes/", response_model=Note)
async def create_note(note: NoteIn):
"""Luo uusi muistiinpano tietokantaan."""
query = notes.insert().values(title=note.title, content=note.content)
last_record_id = await database.execute(query)
return {**note.dict(), "id": last_record_id}
@app.get("/notes/", response_model=List[Note])
async def read_all_notes():
"""Nouda kaikki muistiinpanot tietokannasta."""
query = notes.select()
return await database.fetch_all(query)
@app.get("/notes/{note_id}", response_model=Note)
async def read_note(note_id: int):
"""Nouda yksi muistiinpano sen tunnisteella."""
query = notes.select().where(notes.c.id == note_id)
result = await database.fetch_one(query)
if result is None:
raise HTTPException(status_code=404, detail="Muistiinpanoa ei löydy")
return result
@app.put("/notes/{note_id}", response_model=Note)
async def update_note(note_id: int, note: NoteIn):
"""Päivitä olemassa oleva muistiinpano."""
query = (
notes.update()
.where(notes.c.id == note_id)
.values(title=note.title, content=note.content)
)
result = await database.execute(query)
if result == 0:
raise HTTPException(status_code=404, detail="Muistiinpanoa ei löydy")
return {**note.dict(), "id": note_id}
@app.delete("/notes/{note_id}")
async def delete_note(note_id: int):
"""Poista muistiinpano sen tunnisteella."""
query = notes.delete().where(notes.c.id == note_id)
result = await database.execute(query)
if result == 0:
raise HTTPException(status_code=404, detail="Muistiinpanoa ei löydy")
return {"message": "Muistiinpano poistettu onnistuneesti"}
Asynkronisten kutsujen analyysi:
await database.execute(query): Käytetään toimintoihin, jotka eivät palauta rivejä, kuten INSERT, UPDATE ja DELETE. Se palauttaa vaikutettujen rivien määrän tai uuden tietueen pääavaimen.await database.fetch_all(query): Käytetään SELECT-kyselyihin, joissa odotat useita rivejä. Se palauttaa luettelon tietueista.await database.fetch_one(query): Käytetään SELECT-kyselyihin, joissa odotat korkeintaan yhtä riviä. Se palauttaa yhden tietueen taiNone.
Huomaa, että jokainen tietokantayhteys on etuliitteellä await. Tämä on taikaa, jonka avulla tapahtumasilmukka voi vaihtaa muihin tehtäviin odottaessaan tietokannan vastausta, mikä mahdollistaa korkean rinnakkaisuuden.
Strategia 2: Moderni voimanpesä - SQLAlchemy 2.0+ Async ORM
Vaikka databases-kirjasto on hieno yksinkertaisuudelle, monet laajamittaiset sovellukset hyötyvät täysimittaisesta Object-Relational Mapperista (ORM). ORM:n avulla voit käsitellä tietokantatietueitä Python-objekteina, mikä voi parantaa huomattavasti kehittäjän tuottavuutta ja koodin ylläpidettävyyttä. SQLAlchemy on tehokkain ORM Python-maailmassa, ja sen 2.0+ -versiot tarjoavat huippuluokan natiivin async-käyttöliittymän.
Vaihe 1: Async-moottorin ja istunnon määrittäminen
SQLAlchemy:n async-toiminnallisuuden ydin on AsyncEngine:ssä ja AsyncSession:issa. Asennus on hieman erilainen kuin synkroninen versio.
Järjestämme koodimme muutamiin tiedostoihin paremman rakenteen saamiseksi: database.py, models.py, schemas.py ja main_sqlalchemy.py.
database.py:
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
DATABASE_URL = "postgresql+asyncpg://käyttäjä:salasana@localhost/testdb"
# Luo async-moottori
engine = create_async_engine(DATABASE_URL, echo=True)
# Luo istuntotehdas
# expire_on_commit=False estää attribuutteja vanhentumasta commitin jälkeen
AsyncSessionLocal = sessionmaker(
bind=engine, class_=AsyncSession, expire_on_commit=False
)
models.py:
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import declarative_base
Base = declarative_base()
class Note(Base):
__tablename__ = "notes"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(100), index=True)
content = Column(String(500))
schemas.py (Pydantic-mallit):
from pydantic import BaseModel
class NoteBase(BaseModel):
title: str
content: str
class NoteCreate(NoteBase):
pass
class Note(NoteBase):
id: int
class Config:
orm_mode = True
orm_mode = True Pydantic-mallin config-luokassa on keskeinen osa taikaa. Se kertoo Pydantic:ille lukemaan tiedot paitsi sanakirjoista myös ORM-mallin attribuuteista.
Vaihe 2: Istuntojen hallinta riippuvuuden injektiolla
Suositeltava tapa hallita tietokantaistuntoja FastAPI:ssä on riippuvuuden injektio. Luomme riippuvuuden, joka tarjoaa tietokantaistunnon yhdelle pyynnölle ja varmistaa, että se suljetaan sen jälkeen, vaikka virhe tapahtuisi.
Lisää tämä tiedostoon main_sqlalchemy.py:
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from . import models, schemas
from .database import engine, AsyncSessionLocal
app = FastAPI()
# --- Riippuvuus tietokantaistunnon hankkimiseksi ---
async def get_db() -> AsyncSession:
async with AsyncSessionLocal() as session:
try:
yield session
finally:
await session.close()
# --- Tietokannan alustus (taulukoiden luomiseen) ---
@app.on_event("startup")
async def startup_event():
print("Alustetaan tietokannan skeema...")
async with engine.begin() as conn:
# await conn.run_sync(models.Base.metadata.drop_all)
await conn.run_sync(models.Base.metadata.create_all)
print("Tietokannan skeema alustettu.")
get_db-riippuvuus on tämän mallin kulmakivi. Jokaiselle päätepisteen pyynnölle, joka sitä käyttää, se:
- Luo uuden
AsyncSession:n. yield-istunnon päätepisteen funktiolle.finally-lohkon sisällä oleva koodi varmistaa, että istunto suljetaan ja yhteys palautetaan pooliin riippumatta siitä, onko pyyntö onnistunut vai ei.
Vaihe 3: Asynkronisen CRUD:n toteuttaminen SQLAlchemy ORM:llä
Nyt voimme kirjoittaa päätepisteemme. Ne näyttävät puhtaammilta ja objektiorientoituneimmilta kuin raaka SQL -lähestymistapa.
Lisää nämä päätepisteet kohtaan main_sqlalchemy.py:
@app.post("/notes/", response_model=schemas.Note)
async def create_note(
note: schemas.NoteCreate, db: AsyncSession = Depends(get_db)
):
db_note = models.Note(title=note.title, content=note.content)
db.add(db_note)
await db.commit()
await db.refresh(db_note)
return db_note
@app.get("/notes/", response_model=list[schemas.Note])
async def read_all_notes(skip: int = 0, limit: int = 100, db: AsyncSession = Depends(get_db)):
result = await db.execute(select(models.Note).offset(skip).limit(limit))
notes = result.scalars().all()
return notes
@app.get("/notes/{note_id}", response_model=schemas.Note)
async def read_note(note_id: int, db: AsyncSession = Depends(get_db)):
result = await db.execute(select(models.Note).filter(models.Note.id == note_id))
db_note = result.scalar_one_or_none()
if db_note is None:
raise HTTPException(status_code=404, detail="Muistiinpanoa ei löydy")
return db_note
@app.put("/notes/{note_id}", response_model=schemas.Note)
async def update_note(
note_id: int, note: schemas.NoteCreate, db: AsyncSession = Depends(get_db)
):
result = await db.execute(select(models.Note).filter(models.Note.id == note_id))
db_note = result.scalar_one_or_none()
if db_note is None:
raise HTTPException(status_code=404, detail="Muistiinpanoa ei löydy")
db_note.title = note.title
db_note.content = note.content
await db.commit()
await db.refresh(db_note)
return db_note
@app.delete("/notes/{note_id}")
async def delete_note(note_id: int, db: AsyncSession = Depends(get_db)):
result = await db.execute(select(models.Note).filter(models.Note.id == note_id))
db_note = result.scalar_one_or_none()
if db_note is None:
raise HTTPException(status_code=404, detail="Muistiinpanoa ei löydy")
await db.delete(db_note)
await db.commit()
return {"message": "Muistiinpano poistettu onnistuneesti"}
SQLAlchemy Async -mallin analyysi:
db: AsyncSession = Depends(get_db): Tämä injektoi tietokantaistuntomme päätepisteeseen.await db.execute(...): Tämä on ensisijainen menetelmä kyselyjen suorittamiseen.result.scalars().all()/result.scalar_one_or_none(): Näitä menetelmiä käytetään ORM-objektien poimimiseen kyselyn tuloksesta.db.add(obj): Järjestää objektin lisättäväksi.await db.commit(): Sitouttaa asynkronisesti tapahtuman tietokantaan. Tämä on keskeinenawait-kohta.await db.refresh(obj): Päivittää Python-objektin kaikilla uusilla tiedoilla tietokannasta sitoumisen jälkeen (kuten automaattisesti luotu ID).
Suorituskykyyn liittyvät näkökohdat ja parhaat käytännöt
Pelkkä `async`in ja `await`in käyttäminen on hieno alku, mutta rakentaaksesi todella vankkoja ja tehokkaita sovelluksia, harkitse näitä parhaita käytäntöjä.
1. Ymmärrä yhteyspoolitus
Sekä databases että SQLAlchemy:n AsyncEngine hallitsevat yhteyspoolia kulissien takana. Tämä pooli ylläpitää joukkoa avoimia tietokantayhteyksiä, joita eri pyynnöt voivat käyttää uudelleen. Tämä välttää kalliit uuden TCP-yhteyden muodostamisen ja tietokannan todentamisen jokaiselle kyselylle. Voit virittää poolin koon (esim. `pool_size`, `max_overflow`) moottorin kokoonpanossa tiettyä kuormitustasi varten.
2. Älä koskaan sekoita synkronisia ja asynkronisia tietokantakutsuja
Tärkein sääntö on, että älä koskaan kutsu synkronista, estävää I/O-funktiota async def -funktion sisällä. Tavallinen, synkroninen tietokantakutsu (esim. käyttämällä `psycopg2`a suoraan) estää koko tapahtumasilmukan, jäädyttää sovelluksesi ja tekee asynkronisuuden tarkoituksen tyhjäksi.
Jos sinun on ehdottomasti pakko suorittaa synkroninen koodinpätkä (ehkä CPU-sidotussa kirjastossa), käytä FastAPI:n `run_in_threadpool`ia estämisen välttämiseksi:
from fastapi.concurrency import run_in_threadpool
@app.get("/run-sync-task/")
async def run_sync_task():
# 'some_blocking_io_function' on tavallinen sync-funktio
result = await run_in_threadpool(some_blocking_io_function, arg1, arg2)
return {"result": result}
3. Käytä asynkronisia transaktioita
Kun operaatio sisältää useita tietokantamuutoksia, joiden on onnistuttava tai epäonnistuttava yhdessä (atominen operaatio), sinun on käytettävä transaktiota. Molemmat kirjastot tukevat tätä async-kontekstinhallinnan kautta.
`databases`:lla:
async def transfer_funds():
async with database.transaction():
await database.execute(query_for_debit)
await database.execute(query_for_credit)
SQLAlchemy:lla:
async def transfer_funds(db: AsyncSession = Depends(get_db)):
async with db.begin(): # Tämä käynnistää tapahtuman
# Etsi tilit
account_from = ...
account_to = ...
# Päivitä saldot
account_from.balance -= 100
account_to.balance += 100
# Tapahtuma sitoutuu automaattisesti lohkon poistumisesta
# tai palautetaan, jos poikkeus ilmenee.
4. Valitse vain mitä tarvitset
Vältä `SELECT *`, kun tarvitset vain muutamia sarakkeita. Vähemmän tiedon siirtäminen verkon yli vähentää I/O-odotusaikaa. SQLAlchemy:lla voit käyttää `options(load_only(model.col1, model.col2))` määrittääksesi, mitkä sarakkeet noudetaan.
Johtopäätös: Ota asynkroninen tulevaisuus omaksesi
Asynkronisten tietokantatoimintojen integrointi FastAPI-sovellukseesi on avain sen täyden suorituskyvyn potentiaalin avaamiseen. Varmistamalla, että sovelluksesi ei esty odottaessaan tietokantaa, voit rakentaa palveluita, jotka ovat uskomattoman nopeita, skaalautuvia ja tehokkaita ja kykenevät palvelemaan globaalia käyttäjäkuntaa hikoilematta.
Olemme tutkineet kahta tehokasta strategiaa:
- `databases`-kirjasto tarjoaa suoraviivaisen, kevyen lähestymistavan kehittäjille, jotka haluavat kirjoittaa SQL:ää ja tarvitsevat yksinkertaisen, nopean async-käyttöliittymän.
- SQLAlchemy 2.0+ tarjoaa täysimittaisen, vankan ORM:n natiivilla async-API:lla, mikä tekee siitä ihanteellisen valinnan monimutkaisille sovelluksille, joissa kehittäjien tuottavuus ja ylläpidettävyys ovat ensiarvoisen tärkeitä.
Valinta niiden välillä riippuu projektisi tarpeista, mutta perusperiaate on sama: ajatele ei-estämistä. Ottamalla käyttöön nämä mallit ja parhaat käytännöt et vain kirjoita koodia; vaan arkkitehtuuria järjestelmiä modernin verkon korkean samanaikaisuuden vaatimuksiin. Aloita seuraavan huippusuorituskykyisen FastAPI-sovelluksesi rakentaminen tänään ja koe asynkronisen Pythonin voima ensikäden.