Leistung und Optimierung in TypeORM
Diese Seite wurde von PageTurner AI übersetzt (Beta). Nicht offiziell vom Projekt unterstützt. Fehler gefunden? Problem melden →
1. Einführung in die Leistungsoptimierung
-
In Anwendungen, die ORM wie TypeORM verwenden, ist Leistungsoptimierung entscheidend, um einen reibungslosen Systembetrieb zu gewährleisten, Latenzzeiten zu minimieren und Ressourcen effizient zu nutzen.
-
Häufige Herausforderungen bei der ORM-Nutzung sind unnötige Datenabfragen, N+1-Query-Probleme sowie die Nichtnutzung von Optimierungswerkzeugen wie Indizierung oder Caching.
-
Die Hauptziele der Optimierung umfassen:
- Reduzierung der an die Datenbank gesendeten SQL-Abfragen.
- Optimierung komplexer Abfragen für schnellere Ausführung.
- Nutzung von Caching und Indizes zur Beschleunigung des Datenabrufs.
- Effizienter Datenabruf durch geeignete Lademethoden (Lazy vs. Eager Loading).
2. Effiziente Nutzung des Query Builders
2.1. Vermeidung des N+1-Query-Problems
-
Das N+1-Query-Problem tritt auf, wenn das System zu viele Unterabfragen für jede abgerufene Datenzeile ausführt.
-
Zur Vermeidung können
leftJoinAndSelectoderinnerJoinAndSelectverwendet werden, um Tabellen in einer einzigen Abfrage zu kombinieren statt mehrere Abfragen auszuführen.
const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.posts", "post")
.getMany()
- Hier ermöglicht
leftJoinAndSelectden Abruf aller Benutzerbeiträge in einer einzigen Abfrage statt vieler kleiner Abfragen.
2.2. getRawMany() bei reinen Rohdaten nutzen
- Falls keine vollständigen Objekte benötigt werden, kann
getRawMany()verwendet werden, um Rohdaten abzurufen und die Verarbeitung überflüssiger Informationen durch TypeORM zu vermeiden.
const rawPosts = await dataSource
.getRepository(Post)
.createQueryBuilder("post")
.select("post.title, post.createdAt")
.getRawMany()
2.3. Feldbegrenzung mit select
- Zur Optimierung des Speicherverbrauchs und Reduzierung unnötiger Daten sollten nur benötigte Felder mittels
selectausgewählt werden.
const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.select(["user.name", "user.email"])
.getMany()
3. Nutzung von Indizes
- Indizes beschleunigen die Abfrageperformance in der Datenbank, indem sie die gescannte Datenmenge reduzieren. TypeORM unterstützt die Indexerstellung auf Tabellenspalten mittels
@Index-Decorator.
3.1. Indexerstellung
- Indizes können direkt in Entitäten über den
@Index-Decorator angelegt werden.
import { Entity, Column, Index } from "typeorm"
@Entity()
@Index(["firstName", "lastName"]) // Composite index
export class User {
@Column()
firstName: string
@Column()
lastName: string
}
3.2. Eindeutiger Index
- Eindeutige Indizes können erstellt werden, um doppelte Werte in einer Spalte zu verhindern.
@Index(["email"], { unique: true })
4. Lazy Loading und Eager Loading
TypeORM bietet zwei Hauptmethoden zum Laden von Datenbeziehungen: Lazy Loading und Eager Loading. Jede hat unterschiedliche Auswirkungen auf die Anwendungsleistung.
4.1. Lazy Loading
- Lazy Loading lädt Beziehungsdaten nur bei Bedarf nach und reduziert so die Datenbanklast, wenn nicht immer alle verknüpften Daten benötigt werden.
@Entity()
export class User {
@OneToMany(() => Post, (post) => post.user, { lazy: true })
posts: Promise<Post[]>
}
- Bei Bedarf kann die Daten einfach durch Aufruf abgerufen werden.
const user = await userRepository.findOne(userId)
const posts = await user.posts
-
Vorteile:
- Ressourceneffizienz: Lädt nur benötigte Daten bei tatsächlichem Bedarf, reduziert Abfragekosten und Speicherverbrauch.
- Ideal für selektive Datennutzung: Geeignet für Szenarien, in denen nicht alle verknüpften Daten benötigt werden.
-
Nachteile:
- Erhöhte Abfragekomplexität: Jeder Zugriff auf Beziehungsdaten löst eine zusätzliche Datenbankabfrage aus, was bei falscher Handhabung die Latenz erhöhen kann.
- Schwierige Nachverfolgung: Kann bei unvorsichtiger Nutzung zum N+1-Query-Problem führen.
4.2. Eager Loading
- Eager Loading ruft automatisch alle verknüpften Daten bei Ausführung der Hauptabfrage ab. Dies ist praktisch, kann jedoch bei zu vielen komplexen Beziehungen zu Performance-Problemen führen.
@Entity()
export class User {
@OneToMany(() => Post, (post) => post.user, { eager: true })
posts: Post[]
}
-
In diesem Fall werden Beiträge sofort geladen, sobald die Benutzerdaten abgerufen werden.
-
Vorteile:
- Lädt Beziehungsdaten automatisch nach und erleichtert den Zugriff ohne zusätzliche Abfragen.
- Vermeidet das N+1-Query-Problem: Da alle Daten in einer Abfrage abgerufen werden, entfällt das Risiko unnötiger Mehrfachabfragen.
-
Nachteile:
- Das Abrufen aller verknüpften Daten auf einmal kann zu großen Abfragen führen, selbst wenn nicht alle Daten benötigt werden.
- Ungeeignet für Szenarien, in denen nur ein Teil der Beziehungsdaten benötigt wird, da dies zu ineffizienter Datennutzung führen kann.
-
Weitere Details und Beispiele zur Konfiguration von Lazy- und Eager-Beziehungen finden Sie in der offiziellen TypeORM-Dokumentation: Eager und Lazy Relations
5. Erweiterte Optimierung
5.1. Verwendung von Query Hints
-
Query Hints sind Anweisungen, die zusammen mit SQL-Abfragen gesendet werden und der Datenbank helfen, effizientere Ausführungsstrategien zu wählen.
-
Unterschiedliche RDBMS-Systeme unterstützen verschiedene Hint-Typen, etwa Empfehlungen zur Indexnutzung oder Wahl geeigneter JOIN-Typen.
await dataSource.query(`
SELECT /*+ MAX_EXECUTION_TIME(1000) */ *
FROM user
WHERE email = 'example@example.com'
`)
- Im obigen Beispiel weist
MAX_EXECUTION_TIME(1000)MySQL an, die Abfrage nach mehr als 1 Sekunde abzubrechen.
5.2. Paginierung
-
Paginierung ist eine entscheidende Technik zur Leistungsverbesserung beim Abruf großer Datenmengen. Statt aller Daten auf einmal werden diese in kleinere Seiten unterteilt, was die Datenbanklast reduziert und den Speicherverbrauch optimiert.
-
In TypeORM können dafür
limitundoffsetverwendet werden.
const users = await userRepository
.createQueryBuilder("user")
.limit(10) // Number of records to fetch per page
.offset(20) // Skip the first 20 records
.getMany()
- Pagination verhindert das Abrufen großer Datenmengen auf einmal, minimiert Latenzzeiten und optimiert die Speichernutzung. Bei der Implementierung von Pagination sollten Pagination-Cursors für eine effizientere Handhabung dynamischer Daten in Betracht gezogen werden.
5.3. Caching
-
Caching ist eine Technik, bei der Abfrageergebnisse oder Daten vorübergehend gespeichert werden, um sie in zukünftigen Anfragen zu verwenden, ohne jedes Mal die Datenbank abfragen zu müssen.
-
TypeORM bietet integrierte Caching-Unterstützung, und Sie können anpassen, wie das Caching genutzt wird.
const users = await userRepository
.createQueryBuilder("user")
.cache(true) // Enable caching
.getMany()
- Zusätzlich können Sie die Cache-Dauer konfigurieren oder externe Caching-Tools wie Redis für bessere Effizienz nutzen.
const dataSource = new DataSource({
type: "mysql",
host: "localhost",
port: 3306,
username: "test",
password: "test",
database: "test",
cache: {
type: "redis",
options: {
host: "localhost",
port: 6379
}
}
});