Indizes - Abfragen optimieren¶
Wir haben gerade gesehen, wie wir Daten abfragen können, WHERE eine Bedingung wahr ist. Zum Beispiel, wo der Heldenname "Deadpond" ist.
Wenn wir die Tabellen und Daten einfach so erstellen, wie wir es bisher getan haben, müsste die Datenbank, wenn wir mit SELECT Daten mit WHERE abfragen, jeden einzelnen Datensatz durchsuchen, um die passenden zu finden. Das ist bei 3 Helden wie in diesen Beispielen kein Problem.
Aber stellen Sie sich vor, Ihre Datenbank hat Tausende oder Millionen von Datensätzen. Wenn jedes Mal, wenn Sie die Helden mit dem Namen "Deadpond" finden möchten, die Datenbank alle Datensätze durchsuchen muss, um alle möglichen Übereinstimmungen zu finden, dann wird das problematisch, da es zu langsam wäre.
Ich zeige Ihnen, wie Sie damit mit einem Datenbankindex umgehen können.
Die Änderung im Code ist extrem klein, aber es ist nützlich zu verstehen, was im Hintergrund passiert, daher zeige ich Ihnen, wie alles funktioniert und was es bedeutet.
Wenn Sie die vorherigen Beispiele bereits ausgeführt haben und eine Datenbank mit Daten haben, entfernen Sie die Datenbankdatei, bevor Sie jedes Beispiel ausführen. So vermeiden Sie doppelte Daten und erzielen dieselben Ergebnisse.
Keine Zeit zum Erklären¶
Sind Sie bereits ein SQL-Experte und haben keine Zeit für all meine Erklärungen?
Gut, in diesem Fall können Sie hier einen Schnellblick auf den endgültigen Code zum Erstellen von Indizes werfen.
👀 Vorschau der vollständigen Datei
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Hero(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: int | None = Field(default=None, index=True)
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
def create_heroes():
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)
hero_4 = Hero(name="Tarantula", secret_name="Natalia Roman-on", age=32)
hero_5 = Hero(name="Black Lion", secret_name="Trevor Challa", age=35)
hero_6 = Hero(name="Dr. Weird", secret_name="Steve Weird", age=36)
hero_7 = Hero(name="Captain North America", secret_name="Esteban Rogelios", age=93)
with Session(engine) as session:
session.add(hero_1)
session.add(hero_2)
session.add(hero_3)
session.add(hero_4)
session.add(hero_5)
session.add(hero_6)
session.add(hero_7)
session.commit()
def select_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.age <= 35)
results = session.exec(statement)
for hero in results:
print(hero)
def main():
create_db_and_tables()
create_heroes()
select_heroes()
if __name__ == "__main__":
main()
🤓 Andere Versionen und Varianten
from typing import Optional
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: Optional[int] = Field(default=None, index=True)
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
def create_heroes():
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)
hero_4 = Hero(name="Tarantula", secret_name="Natalia Roman-on", age=32)
hero_5 = Hero(name="Black Lion", secret_name="Trevor Challa", age=35)
hero_6 = Hero(name="Dr. Weird", secret_name="Steve Weird", age=36)
hero_7 = Hero(name="Captain North America", secret_name="Esteban Rogelios", age=93)
with Session(engine) as session:
session.add(hero_1)
session.add(hero_2)
session.add(hero_3)
session.add(hero_4)
session.add(hero_5)
session.add(hero_6)
session.add(hero_7)
session.commit()
def select_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.age <= 35)
results = session.exec(statement)
for hero in results:
print(hero)
def main():
create_db_and_tables()
create_heroes()
select_heroes()
if __name__ == "__main__":
main()
..aber wenn Sie kein Experte sind, lesen Sie weiter, das wird wahrscheinlich nützlich sein. 🤓
Was ist ein Index?¶
Im Allgemeinen ist ein Index einfach etwas, das wir haben können, um Dinge schneller zu finden. Er funktioniert normalerweise, indem die Dinge geordnet sind. Denken wir über einige reale Beispiele nach, bevor wir überhaupt an Datenbanken und Code denken.
Ein Index und ein Wörterbuch¶
Stellen Sie sich ein Wörterbuch vor, ein Buch mit Wortdefinitionen. 📔 ...nicht ein Python dict. 😅
Nehmen wir an, Sie möchten ein Wort finden, zum Beispiel das Wort "Datenbank". Sie nehmen das Wörterbuch und öffnen es irgendwo, zum Beispiel in der Mitte. Vielleicht sehen Sie Definitionen von Wörtern, die mit m beginnen, wie manuell, also schließen Sie, dass Sie sich im Buchstaben m im Wörterbuch befinden.
Sie wissen, dass im Alphabet der Buchstabe d für Datenbank vor dem Buchstaben m für manuell kommt.
Also wissen Sie, dass Sie im Wörterbuch vor dem aktuellen Punkt suchen müssen. Sie wissen noch nicht genau, wo das Wort Datenbank ist, weil Sie nicht genau wissen, wo der Buchstabe d im Wörterbuch ist, aber Sie wissen, dass er nicht danach kommt. Sie können nun die rechte Hälfte des Wörterbuchs bei Ihrer Suche verwerfen.
Als Nächstes öffnen Sie das Wörterbuch erneut, aber nur unter Berücksichtigung der Hälfte des Wörterbuchs, die das gesuchte Wort enthalten kann, des linken Teils des Wörterbuchs. Sie öffnen es in der Mitte dieses linken Teils und gelangen nun vielleicht zum Buchstaben f.
Sie wissen, dass d von Datenbank vor f kommt. Also muss es davor sein. Aber Sie wissen jetzt, dass "Datenbank" nicht danach kommt und Sie das Wörterbuch ab diesem Punkt verwerfen können.
Jetzt haben Sie einen kleinen Abschnitt des Wörterbuchs zum Durchsuchen (nur ein Viertel des Wörterbuchs kann Ihr Wort enthalten). Sie nehmen dieses Viertel der Seiten am Anfang des Wörterbuchs, die Ihr Wort enthalten können, und öffnen es in der Mitte dieses Abschnitts. Vielleicht gelangen Sie zum Buchstaben c.
Sie wissen, dass das Wort Datenbank danach und nicht davor sein muss, also können Sie den linken Teil dieses Seitenblocks verwerfen.
Sie wiederholen diesen Vorgang noch ein paar Mal und gelangen schließlich zum Buchstaben d. Sie fahren mit demselben Vorgang in diesem Abschnitt für den Buchstaben d fort und finden schließlich das Wort Datenbank. 🎉
Sie mussten das Wörterbuch ein paar Mal öffnen, vielleicht 5 oder 10 Mal. Das ist tatsächlich sehr wenig Arbeit im Vergleich zu dem, was es hätte sein können.
Technische Details
Mögen Sie schicke Wörter? Cool! Programmierer mögen oft schicke Wörter. 😅
Dieser Algorithmus, den ich Ihnen oben gezeigt habe, wird Binäre Suche genannt.
Er heißt so, weil Sie etwas suchen, indem Sie das Wörterbuch (oder jede andere geordnete Liste von Dingen) in zwei ("binär" bedeutet "zwei") Teile aufteilen. Und Sie führen diesen Vorgang mehrmals durch, bis Sie finden, was Sie wollen.
Ein Index und ein Roman¶
Stellen wir uns jetzt vor, Sie lesen einen Roman. Und jemand hat Ihnen gesagt, dass an einer Stelle eine Datenbank erwähnt wird, und Sie wollen dieses Kapitel finden.
Wie finden Sie dort das Wort "Datenbank"? Sie müssen vielleicht das ganze Buch lesen, um herauszufinden, wo das Wort "Datenbank" im Buch steht. Anstatt also das Buch 5 oder 10 Mal zu öffnen, müssten Sie jede der 500 Seiten öffnen und sie einzeln lesen, bis Sie das Wort finden. Sie könnten das Buch aber genießen. 😅
Aber wenn wir nur daran interessiert sind, schnell Informationen zu finden (wie bei der Arbeit mit SQL-Datenbanken), dann ist das Lesen jeder der 500 Seiten zu ineffizient, wenn es eine Option gäbe, das Buch an 5 oder 10 Stellen zu öffnen und das zu finden, was Sie suchen.
Ein Fachbuch mit einem Index¶
Stellen wir uns nun vor, Sie lesen ein Fachbuch. Zum Beispiel mit mehreren Themen über Programmierung. Und es gibt ein paar Abschnitte, in denen es um eine Datenbank geht.
Dieses Buch hat möglicherweise einen Buchindex: einen Abschnitt im Buch, der einige Namen der behandelten Themen und die Seitenzahlen im Buch enthält, auf denen Sie darüber lesen können. Und die Themennamen sind alphabetisch sortiert, ziemlich ähnlich wie ein Wörterbuch (ein Buch mit Wörtern, wie im vorherigen Beispiel).
In diesem Fall können Sie dieses Buch am Ende (oder am Anfang) öffnen, um den Buchindex-Abschnitt zu finden, der nur wenige Seiten umfasst. Und dann können Sie denselben Vorgang wie im Wörterbuch-Beispiel oben durchführen.
Öffnen Sie den Index, und nach 5 oder 10 Schritten finden Sie schnell das Thema "Datenbank" mit den Seitenzahlen, auf denen es behandelt wird, zum Beispiel "Seite 253 in Kapitel 5". Jetzt haben Sie die Wörterbuchtechnik verwendet, um das Thema zu finden, und dieses Thema hat Ihnen eine Seitenzahl geliefert.
Nun wissen Sie, dass Sie "Seite 253" finden müssen. Aber wenn Sie das geschlossene Buch betrachten, wissen Sie immer noch nicht, wo sich diese Seite befindet, also müssen Sie diese Seite finden. Um sie zu finden, können Sie denselben Vorgang erneut durchführen, aber diesmal suchen Sie nicht nach einem Thema im Index, sondern nach einer Seitenzahl im gesamten Buch. Und nach weiteren 5 oder 10 Schritten finden Sie Seite 253 in Kapitel 5.
Danach konnten Sie, obwohl dieses Buch kein Wörterbuch ist und spezifischen Inhalt hat, den Abschnitt im Buch, der über eine "Datenbank" spricht, in wenigen Schritten (sagen wir 10 oder 20, anstatt alle 500 Seiten zu lesen) finden.
Der Hauptpunkt ist, dass der Index sortiert ist, damit wir denselben Prozess wie beim Wörterbuch verwenden können, um das Thema zu finden. Und dann erhalten wir eine Seitenzahl, und die Seitenzahlen sind ebenfalls sortiert! 😅
Wenn wir eine Liste von sortierten Dingen haben, können wir dieselbe Technik anwenden, und das ist der ganze Trick hier. Wir verwenden dieselbe Technik zuerst für die Themen im Index und dann für die Seitenzahlen, um das eigentliche Kapitel zu finden.
So eine Effizienz! 😎
Was sind Datenbankindizes?¶
Datenbankindizes sind sehr ähnlich zu Buchindizes.
Datenbankindizes speichern einige Informationen, einige Schlüssel, auf eine Weise, die sie einfach und schnell zu finden macht (zum Beispiel sortiert), und dann weisen sie auf Daten irgendwo anders in der Datenbank hin.
Sehen wir uns ein klareres Beispiel an. Nehmen wir an, Sie haben diese Tabelle in einer Datenbank
| id | name | secret_name | age |
|---|---|---|---|
| 1 | Deadpond | Dive Wilson | null |
| 2 | Spider-Boy | Pedro Parqueador | null |
| 3 | Rusty-Man | Tommy Sharp | 48 |
Und stellen wir uns vor, Sie haben viel mehr Zeilen, viel mehr Helden. Wahrscheinlich Tausende.
Wenn Sie der SQL-Datenbank befehlen, Ihnen einen Helden anhand eines bestimmten Namens zu geben, zum Beispiel Spider-Boy (indem Sie den name im WHERE-Teil der SQL-Abfrage verwenden), muss die Datenbank alle Helden scannen und einen nach dem anderen überprüfen, um alle mit dem Namen Spider-Boy zu finden.
In diesem Fall gibt es nur einen, aber nichts hindert die Datenbank daran, mehr Datensätze mit demselben Namen zu haben. Und deshalb würde die Datenbank weiter suchen und jeden der Datensätze überprüfen, was sehr langsam wäre.
Aber nehmen wir jetzt an, die Datenbank hat einen Index für die Spalte name. Der Index könnte etwa so aussehen, wir könnten uns vorstellen, dass der Index wie eine zusätzliche spezielle Tabelle ist, die die Datenbank automatisch verwaltet
| name | id |
|---|---|
| Deadpond | 1 |
| Rusty-Man | 3 |
| Spider-Boy | 2 |
Sie hätte jedes name-Feld aus der hero-Tabelle in Ordnung. Sie wäre nicht nach id sortiert, sondern nach name (alphabetisch, da name ein String ist). Zuerst hätte sie Deadpond, dann Rusty-Man und zuletzt Spider-Boy. Sie würde auch die id jedes Helden enthalten. Denken Sie daran, dass dies Tausende von Helden enthalten könnte.
Dann könnte die Datenbank mehr oder weniger dieselben Ideen wie in den obigen Beispielen mit dem Wörterbuch und dem Buchindex verwenden.
Sie könnte irgendwo beginnen (zum Beispiel in der Mitte des Indexes). Sie könnte dort in der Mitte auf einen Helden stoßen, wie Rusty-Man. Und da der Index die name-Felder in Ordnung hat, wüsste die Datenbank, dass sie alle vorherigen Indexzeilen verwerfen und nur in den folgenden Indexzeilen suchen kann.
| name | id |
|---|---|
| Deadpond | 1 |
| Rusty-Man | 3 |
| Spider-Boy | 2 |
Und auf diese Weise, wie im Beispiel mit dem Wörterbuch oben, anstatt Tausende von Helden zu lesen, könnte die Datenbank ein paar Schritte ausführen, sagen wir 5 oder 10 Schritte, und die Zeile des Indexes erreichen, die Spider-Boy enthält, auch wenn die Tabelle (und der Index) Tausende von Zeilen hat.
| name | id |
|---|---|
| Deadpond | 1 |
| Rusty-Man | 3 |
| ✨ Spider-Boy ✨ | 2 |
Dann würde sie anhand dieser Indexzeile wissen, dass die id für Spider-Boy in der hero-Tabelle 2 ist.
Dann könnte sie diese id in der hero-Tabelle mit mehr oder weniger derselben Technik suchen.
Auf diese Weise musste die Datenbank am Ende, anstatt Tausende von Datensätzen zu lesen, nur wenige Schritte ausführen, um den gewünschten Helden zu finden.
Aktualisieren des Indexes¶
Wie Sie sich vorstellen können, muss der Index aktuell mit den Daten in der Datenbank sein, damit all dies funktioniert.
Wenn Sie ihn manuell im Code aktualisieren müssten, wäre das sehr umständlich und fehleranfällig, da es leicht zu einem Zustand kommen könnte, in dem der Index nicht aktuell ist und auf falsche Daten verweist. 😱
Hier ist die gute Nachricht: Wenn Sie in einer SQL-Datenbank einen Index erstellen, kümmert sich die Datenbank automatisch um dessen Aktualisierung, wann immer es notwendig ist. 😎🎉
Wenn Sie neue Datensätze zur hero-Tabelle hinzufügen, wird die Datenbank den Index automatisch aktualisieren. Sie wird denselben Prozess durchlaufen, den richtigen Platz zu finden, um die neuen Indexdaten (diese 5 oder 10 Schritte, die oben beschrieben wurden) einzufügen, und dann dort die neuen Indexinformationen speichern. Dasselbe würde passieren, wenn Sie Daten aktualisieren oder löschen.
Das Definieren und Erstellen eines Indexes ist mit SQL-Datenbanken sehr einfach. Und dann ist die Verwendung noch einfacher... es ist transparent. Die Datenbank ermittelt automatisch, welchen Index sie verwenden soll, die SQL-Abfragen ändern sich nicht einmal.
Also, in SQL-Datenbanken sind Indizes großartig! Und sie sind super einfach zu verwenden. Warum nicht einfach Indizes für alles haben? .....Weil Indizes auch eine "Kosten" an Rechenleistung und Speicherplatz (Festplattenspeicher) haben.
Indexkosten¶
Mit Indizes sind Kosten verbunden. 💰
Wenn Sie keinen Index haben und eine neue Zeile zur hero-Tabelle hinzufügen, muss die Datenbank 1 Operation durchführen, um die neue Heldenzeile am Ende der Tabelle hinzuzufügen.
Aber wenn Sie einen Index für die Hero-Namen haben, muss die Datenbank nun dieselbe 1 Operation durchführen, um diese Zeile plus zusätzliche 5 oder 10 Operationen im Index durchzuführen, um die richtige Stelle für den Namen zu finden und dann diesen Index-Datensatz dort einzufügen.
Und wenn Sie einen Index für den name, einen für das age und einen für den secret_name haben, muss die Datenbank nun dieselbe 1 Operation durchführen, um diese Zeile plus zusätzliche 5 oder 10 Operationen im Index mal 3 durchzuführen, für jeden der Indizes. Das bedeutet, dass das Hinzufügen einer Zeile jetzt etwa 31 Operationen erfordert.
Das bedeutet auch, dass Sie die Zeit für das Lesen von Daten gegen die Zeit für das Schreiben von Daten plus etwas zusätzlichen Speicherplatz in der Datenbank austauschen.
Wenn Sie Abfragen haben, die Daten aus der Datenbank abrufen und jeden dieser Felder vergleichen (z. B. mit WHERE), dann macht es absolut Sinn, für jeden von ihnen Indizes zu haben. Denn 31 Operationen beim Erstellen oder Aktualisieren von Daten (plus der Speicherplatz des Indexes) sind viel, viel besser als die möglichen 500 oder 1000 Operationen, um alle Zeilen zu lesen, um sie mit jedem Feld vergleichen zu können.
Aber wenn Sie nie Abfragen haben, die Datensätze nach dem secret_name finden (Sie verwenden secret_name nie im WHERE-Teil), dann macht es wahrscheinlich keinen Sinn, einen Index für das secret_name-Feld/die Spalte zu haben, da dies die Rechen- und Speicherplatzkosten für das Schreiben und Aktualisieren der Datenbank erhöht.
Einen Index mit SQL erstellen¶
Puh, das war viel Theorie und Erklärungen. 😅
Das Wichtigste bei Indizes ist, sie zu verstehen, wie und wann man sie verwendet.
Sehen wir uns nun die SQL-Syntax zum Erstellen eines Indexes an. Sie ist sehr einfach.
CREATE INDEX ix_hero_name
ON hero (name)
Das bedeutet, mehr oder weniger
Hallo SQL-Datenbank 👋, bitte
ERSTELLEeinenINDEXfür mich.Ich möchte, dass der Name des Indexes
ix_hero_namelautet.Dieser Index soll
ONder Tabelleheroliegen, er bezieht sich auf diese Tabelle.Die Spalte, die Sie dafür verwenden sollen, ist
name.
Indizes mit SQLModel deklarieren¶
Und nun sehen wir, wie man Indizes in SQLModel definiert.
Die Änderung im Code ist unspektakulär, sie ist sehr einfach. 😆
Hier ist das Hero-Modell, das wir vorher hatten
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Hero(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str
secret_name: str
age: int | None = None
# Code below omitted 👇
👀 Vorschau der vollständigen Datei
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Hero(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str
secret_name: str
age: int | None = None
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
def create_heroes():
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)
with Session(engine) as session:
session.add(hero_1)
session.add(hero_2)
session.add(hero_3)
session.commit()
def select_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.name == "Deadpond")
results = session.exec(statement)
for hero in results:
print(hero)
def main():
create_db_and_tables()
create_heroes()
select_heroes()
if __name__ == "__main__":
main()
🤓 Andere Versionen und Varianten
from typing import Optional
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
secret_name: str
age: Optional[int] = None
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
def create_heroes():
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)
with Session(engine) as session:
session.add(hero_1)
session.add(hero_2)
session.add(hero_3)
session.commit()
def select_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.name == "Deadpond")
results = session.exec(statement)
for hero in results:
print(hero)
def main():
create_db_and_tables()
create_heroes()
select_heroes()
if __name__ == "__main__":
main()
Aktualisieren wir es nun, um SQLModel mitzuteilen, einen Index für das Feld name bei der Erstellung der Tabelle zu erstellen
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Hero(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: int | None = Field(default=None, index=True)
# Code below omitted 👇
👀 Vorschau der vollständigen Datei
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Hero(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: int | None = Field(default=None, index=True)
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
def create_heroes():
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)
with Session(engine) as session:
session.add(hero_1)
session.add(hero_2)
session.add(hero_3)
session.commit()
def select_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.name == "Deadpond")
results = session.exec(statement)
for hero in results:
print(hero)
def main():
create_db_and_tables()
create_heroes()
select_heroes()
if __name__ == "__main__":
main()
🤓 Andere Versionen und Varianten
from typing import Optional
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: Optional[int] = Field(default=None, index=True)
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
def create_heroes():
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)
with Session(engine) as session:
session.add(hero_1)
session.add(hero_2)
session.add(hero_3)
session.commit()
def select_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.name == "Deadpond")
results = session.exec(statement)
for hero in results:
print(hero)
def main():
create_db_and_tables()
create_heroes()
select_heroes()
if __name__ == "__main__":
main()
Wir verwenden wieder dasselbe Field() wie zuvor und setzen index=True. Das war's! 🚀
Beachten Sie, dass wir kein Argument wie default=None oder etwas Ähnliches gesetzt haben. Das bedeutet, dass SQLModel (dank Pydantic) es als erforderliches Feld beibehält.
Info
SQLModel (eigentlich SQLAlchemy) wird den Indexnamen automatisch generieren.
In diesem Fall wäre der generierte Name ix_hero_name.
Daten abfragen¶
Um nun die Daten über das Feld name und den neuen Index abzufragen, müssen wir nichts Besonderes oder Anderes im Code tun, es ist einfach derselbe Code.
Die SQL-Datenbank wird das automatisch herausfinden. ✨
Das ist großartig, weil es bedeutet, dass Indizes sehr einfach zu verwenden sind. Es kann sich aber auch anfangs kontraintuitiv anfühlen, da Sie im Code nichts explizit tun, um offensichtlich zu machen, dass der Index nützlich ist, alles geschieht im Hintergrund in der Datenbank.
# Code above omitted 👆
def select_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.name == "Deadpond")
results = session.exec(statement)
for hero in results:
print(hero)
# Code below omitted 👇
👀 Vorschau der vollständigen Datei
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Hero(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: int | None = Field(default=None, index=True)
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
def create_heroes():
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)
with Session(engine) as session:
session.add(hero_1)
session.add(hero_2)
session.add(hero_3)
session.commit()
def select_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.name == "Deadpond")
results = session.exec(statement)
for hero in results:
print(hero)
def main():
create_db_and_tables()
create_heroes()
select_heroes()
if __name__ == "__main__":
main()
🤓 Andere Versionen und Varianten
from typing import Optional
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: Optional[int] = Field(default=None, index=True)
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
def create_heroes():
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)
with Session(engine) as session:
session.add(hero_1)
session.add(hero_2)
session.add(hero_3)
session.commit()
def select_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.name == "Deadpond")
results = session.exec(statement)
for hero in results:
print(hero)
def main():
create_db_and_tables()
create_heroes()
select_heroes()
if __name__ == "__main__":
main()
Dies ist exakt derselbe Code wie zuvor, aber die Datenbank wird ihn nun unter der Haube verwenden.
Das Programm ausführen¶
Wenn Sie das Programm jetzt ausführen, sehen Sie eine Ausgabe wie diese
$ python app.py
// Some boilerplate output omitted 😉
// Create the table
CREATE TABLE hero (
id INTEGER,
name VARCHAR NOT NULL,
secret_name VARCHAR NOT NULL,
age INTEGER,
PRIMARY KEY (id)
)
// Create the index 🤓🎉
CREATE INDEX ix_hero_name ON hero (name)
// The SELECT with WHERE looks the same
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.name = ?
INFO Engine [no key 0.00014s] ('Deadpond',)
// The resulting hero
secret_name='Dive Wilson' age=None id=1 name='Deadpond'
Mehr Indizes¶
Wir werden auch die hero-Tabelle abfragen und Vergleiche auf dem age-Feld durchführen, daher sollten wir auch dafür einen Index definieren
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Hero(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: int | None = Field(default=None, index=True)
# Code below omitted 👇
👀 Vorschau der vollständigen Datei
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Hero(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: int | None = Field(default=None, index=True)
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
def create_heroes():
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)
hero_4 = Hero(name="Tarantula", secret_name="Natalia Roman-on", age=32)
hero_5 = Hero(name="Black Lion", secret_name="Trevor Challa", age=35)
hero_6 = Hero(name="Dr. Weird", secret_name="Steve Weird", age=36)
hero_7 = Hero(name="Captain North America", secret_name="Esteban Rogelios", age=93)
with Session(engine) as session:
session.add(hero_1)
session.add(hero_2)
session.add(hero_3)
session.add(hero_4)
session.add(hero_5)
session.add(hero_6)
session.add(hero_7)
session.commit()
def select_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.age <= 35)
results = session.exec(statement)
for hero in results:
print(hero)
def main():
create_db_and_tables()
create_heroes()
select_heroes()
if __name__ == "__main__":
main()
🤓 Andere Versionen und Varianten
from typing import Optional
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: Optional[int] = Field(default=None, index=True)
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
def create_heroes():
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)
hero_4 = Hero(name="Tarantula", secret_name="Natalia Roman-on", age=32)
hero_5 = Hero(name="Black Lion", secret_name="Trevor Challa", age=35)
hero_6 = Hero(name="Dr. Weird", secret_name="Steve Weird", age=36)
hero_7 = Hero(name="Captain North America", secret_name="Esteban Rogelios", age=93)
with Session(engine) as session:
session.add(hero_1)
session.add(hero_2)
session.add(hero_3)
session.add(hero_4)
session.add(hero_5)
session.add(hero_6)
session.add(hero_7)
session.commit()
def select_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.age <= 35)
results = session.exec(statement)
for hero in results:
print(hero)
def main():
create_db_and_tables()
create_heroes()
select_heroes()
if __name__ == "__main__":
main()
In diesem Fall möchten wir, dass der Standardwert von age weiterhin None ist, also setzen wir default=None, wenn wir Field() verwenden.
Wenn wir nun SQLModel zur Erstellung der Datenbank und Tabellen verwenden, werden auch die Indizes für diese beiden Spalten in der hero-Tabelle erstellt.
Wenn wir also die Datenbank für die hero-Tabelle abfragen und diese beiden Spalten verwenden, um zu definieren, welche Daten wir erhalten, kann die Datenbank diese Indizes verwenden, um die Leseleistung zu verbessern. 🚀
Primärschlüssel und Indizes¶
Sie haben wahrscheinlich bemerkt, dass wir für das id-Feld nicht index=True gesetzt haben.
Da die id bereits der Primärschlüssel ist, erstellt die Datenbank automatisch einen internen Index dafür.
Die Datenbank erstellt automatisch immer einen internen Index für Primärschlüssel, da dies die primäre Methode zur Organisation, Speicherung und Abfrage von Daten ist. 🤓
Aber wenn Sie die SQL-Datenbank häufig für andere Felder abfragen möchten (z. B. indem Sie ein beliebiges anderes Feld im WHERE-Abschnitt verwenden), möchten Sie wahrscheinlich mindestens einen Index dafür haben.
Zusammenfassung¶
Indizes sind sehr wichtig, um die Leseleistung und Geschwindigkeit bei der Abfrage der Datenbank zu verbessern. 🏎
Das Erstellen und Verwenden ist sehr einfach. Der wichtigste Teil ist, wie sie funktionieren, wann sie erstellt werden sollten und für welche Spalten.