Sobald ein Discord-Bot beginnt, Nutzerdaten zu speichern, wird die Kombination discord.py SQLite zu einer der praktischsten Antworten: eine konfigurationsfreie Datenbank in einer einzigen Datei, ganz ohne Server. Doch hier lauert eine entscheidende Falle; das Standardmodul sqlite3 ist blockierend, und jede Abfrage friert den Event-Loop des Bots ein und verlangsamt damit alle Befehle. Die Lösung heißt aiosqlite: eine Bibliothek, die SQLite in die Welt von async/await holt und sich perfekt in discord.py 2.x einfügt. In diesem Artikel bauen wir eine Schicht, die Wirtschafts-/Level-Daten asynchron und sicher speichert.
Warum aiosqlite und nicht sqlite3?
discord.py basiert auf einer vollständig asynchronen Architektur: ein Event-Loop in einem einzigen Thread verarbeitet eingehende Nachrichten und Interaktionen nacheinander. Jede Abfrage mit dem Standard-sqlite3 blockiert diesen Loop; während die Datenbank auf die Festplatte schreibt, kann dein Bot nicht einmal auf Pings antworten. aiosqlite hingegen reicht die Abfrage an einen Hintergrund-Thread weiter und liefert das Ergebnis als awaitbare Coroutine zurück. Die praktischen Vorteile:
- Nicht blockierende E/A: eine langsame Abfrage sperrt deine anderen Befehle nicht.
- Natürliche Syntax:
async withundawaitfügen sich direkt in deinen discord.py-Code ein. - Eigenständig: kein separater Server wie MySQL/PostgreSQL nötig; die Datenbank ist eine einzige
.db-Datei.
Die Installation ist eine Zeile: pip install aiosqlite. Das zugrunde liegende SQLite wird bereits mit Python ausgeliefert.
Die Datenbank an den Bot anbinden
Der sauberste Ansatz ist, die Verbindung beim Start des Bots in setup_hook zu öffnen und sie als Attribut an das Bot-Objekt zu hängen. So teilen sich alle Cogs dieselbe Verbindung über self.bot.db. Beim Herunterfahren schließen wir die Verbindung ebenfalls sauber:
import discord
from discord.ext import commands
import aiosqlite
class MyBot(commands.Bot):
def __init__(self):
intents = discord.Intents.default()
super().__init__(command_prefix="!", intents=intents)
self.db: aiosqlite.Connection | None = None
async def setup_hook(self):
self.db = await aiosqlite.connect("data.db")
# Verbessert gleichzeitiges Lesen/Schreiben
await self.db.execute("PRAGMA journal_mode=WAL;")
await self.init_db()
async def init_db(self):
await self.db.execute("""
CREATE TABLE IF NOT EXISTS users (
user_id INTEGER PRIMARY KEY,
balance INTEGER NOT NULL DEFAULT 0,
xp INTEGER NOT NULL DEFAULT 0
)
""")
await self.db.commit()
async def close(self):
if self.db is not None:
await self.db.close()
await super().close()
PRAGMA journal_mode=WAL (Write-Ahead Logging) verhindert, dass Leser den Schreiber blockieren; das macht bei einem Bot mit vielen Nutzern einen spürbaren Unterschied. Dank CREATE TABLE IF NOT EXISTS wird die Tabelle bei jedem Start sicher vorbereitet.
Daten schreiben: parametrisierte Abfragen
Füge Nutzereingaben niemals direkt in den SQL-Text ein; das öffnet die Tür zur SQL-Injection. Verwende stattdessen ?-Platzhalter und ein Tupel mit Parametern. Die INSERT ... ON CONFLICT-Syntax von SQLite erledigt ein Einfügen-falls-fehlend, Aktualisieren-falls-vorhanden (Upsert) in einer einzigen Abfrage:
async def add_balance(db, user_id: int, amount: int):
await db.execute(
"""
INSERT INTO users (user_id, balance)
VALUES (?, ?)
ON CONFLICT(user_id)
DO UPDATE SET balance = balance + excluded.balance
""",
(user_id, amount),
)
await db.commit()
Entscheidender Punkt: aiosqlite macht Änderungen nicht automatisch dauerhaft. Wenn du nach jedem Schreibvorgang nicht await db.commit() aufrufst, werden die Daten nie auf die Festplatte geschrieben und gehen beim Neustart des Bots verloren.
Daten lesen: fetchone und fetchall
Auf der Leseseite arbeitest du über einen Cursor. Ein async with-Block schließt den Cursor automatisch:
async def get_balance(db, user_id: int) -> int:
async with db.execute(
"SELECT balance FROM users WHERE user_id = ?",
(user_id,),
) as cursor:
row = await cursor.fetchone()
return row[0] if row else 0
Für mehrere Zeilen verwendest du fetchall; zum Beispiel eine Level-Bestenliste (Leaderboard):
async def top_users(db, limit: int = 10):
async with db.execute(
"SELECT user_id, xp FROM users ORDER BY xp DESC LIMIT ?",
(limit,),
) as cursor:
return await cursor.fetchall()
Möchtest du auf Ergebnisse per Spaltenname zugreifen, setze db.row_factory = aiosqlite.Row; dann kannst du wie row["balance"] lesen.
Verwendung in einem Befehl
Die Hilfsfunktionen in einen Befehl innerhalb eines Cogs einzubinden ist sehr kurz. Da self.bot.db überall erreichbar ist, konzentriert sich der Befehl nur auf die Geschäftslogik:
from discord.ext import commands
class Economy(commands.Cog):
def __init__(self, bot):
self.bot = bot
@commands.command()
async def daily(self, ctx):
await add_balance(self.bot.db, ctx.author.id, 100)
bal = await get_balance(self.bot.db, ctx.author.id)
await ctx.send(f"100 tägliche Coins erhalten. Kontostand: {bal}")
async def setup(bot):
await bot.add_cog(Economy(bot))
Tipps zu Leistung und Sicherheit
- Eine gemeinsame Verbindung: halte eine einzige Verbindung über die gesamte Laufzeit des Bots, statt pro Befehl eine zu öffnen und zu schließen.
- Indizes hinzufügen: ein
CREATE INDEXauf häufig abgefragten Spalten (z. B.xpzum Sortieren) beschleunigt große Tabellen. - Stapelverarbeitung: beim Verarbeiten vieler Zeilen auf einmal nutze
executemanyund ein einzigescommitam Ende. - Backups: SQLite ist eine Datei; um sie zu kopieren, ohne den Bot zu berühren, ziehe
VACUUM INTOoder die Online-Backup-API in Betracht.
SQLite reicht für mittelgroße Bots auf einem einzelnen Server mehr als aus. Sobald du Hunderttausende Nutzer mit starken gleichzeitigen Schreibvorgängen erreichst, lohnt sich ein Wechsel zu PostgreSQL; bis dahin ist aiosqlite eine schlanke, schnelle Wahl.
Häufige Fragen
Was passiert, wenn ich commit zu rufen vergesse?
Deine geschriebenen Daten bleiben nur innerhalb der Verbindung dieser Sitzung sichtbar, werden aber nie auf die Festplatte geschrieben. Beim Neustart des Bots geht jede Änderung nach dem letzten commit verloren. Mach es dir zur Gewohnheit, nach jedem INSERT/UPDATE/DELETE await db.commit() aufzurufen.
Kann ich nicht einfach das normale sqlite3-Modul verwenden?
Technisch funktioniert es, aber jede Abfrage blockiert den Event-Loop des Bots und verzögert alle Befehle. Bei einem Bot mit wenig Verkehr fällt es vielleicht nicht auf; im echten Einsatz ist aiosqlite die einzig richtige Wahl, weil es zur asynchronen Architektur passt.
Können mehrere Cogs dieselbe Datenbank sicher verwenden?
Ja. Solange du die Verbindung über self.bot.db teilst, nutzen alle Cogs dieselbe einzelne Verbindung. Der WAL-Modus erleichtert gleichzeitiges Lesen; Schreibvorgänge werden von SQLite in eine Warteschlange gestellt und sicher ausgeführt.
Willst du eine solide Datenschicht für deinen Bot aufbauen? Um eine aiosqlite-Architektur zu entwerfen, deinen bestehenden sqlite3-Code auf async umzustellen oder ein Wirtschafts-/Level-System zu bauen, nimm Kontakt mit mir auf.