Es ist nicht all zu lange her, dass ich begonnen habe mich mit SA:MP Servern genauer zu beschäftigen. Demnach ist es auch nicht all zu lange her, dass ich mich mit MySQL in Verbindung mit SA:MP auseinander gesetzt habe, da es doch tatsächlich die beste Methode ist Daten strukturiert zu speichern und auch von anderen Platformen einfach abzurufen. Dennoch ist es nicht ungefährlich mit MySQL als unerfahrener Programmierer zu spielen, da MySQL durchaus komplex werden kann und auch zu Problemen führen kann. Eines dieser Probleme möchte ich hier genauer erläutern und hoffentlich für den ein oder anderen so schildern, dass es nicht zu einem Servercrash kommt, wenn mal mehr als ca. 20 Spieler auf dem Server aktiv sind. Mehr oder weniger werde ich im Folgenden versuchen, euch zu erläutern, wie ihr die Performance in Sachen Pawno und MySQL noch etwas verbessern könnt und Lags als auch mögliche Servercrashes vermeiden könnt.
Thema: Verwenden von OnQueryFinish - Einen zweiten Thread einarbeiten.
In den meisten MySQL Tutorials wird einfach nach dem ausgeben eines MySQL-Querys einfach im Code weitergeschrieben. Das wird sich nun ändern. Wir werden nach dem Ausführen eines MySQL-Querys in einen anderen Callback wechseln und dort mit unserem Code weitermachen. Hier ein kurzes Beispiel, wie es wahrscheinlich die meisten kennen:
public OnPlayerConnect(playerid) {
new name[MAX_PLAYER_NAME], query[85], var_banned;
GetPlayerName(playerid, name, sizeof(name));
format(query, sizeof(query), "SELECT banned FROM samp_usertable WHERE user = '%s'", name);
mysql_query(query);
mysql_store_result();
var_banned = mysql_fetch_int();
mysql_free_result();
if(var_banned > 0) Kick(playerid);
else
{
/* Weiteres Vorhaben hier ... */
}
}
Dieser kleine Ausschnitt wird nun aus der Tabelle 'samp_usertable' den Datenteil aus der Spalte 'banned' bei User 'name' herausfiltern und via mysql_fetch_int dem Integer var_banned zuordnen. Falls der Integer größer als 0 sein wird, wird der Spieler vom Server geschmissen mithilfe der Funktion Kick(). Doch wo genau ist nun hier der große Nachteil von MySQL bzw. Pawno in Verbindung mit MySQL?
Nachteil:
MySQL benötigt beim Ausführen eines Querys natürlich etwas Verarbeitungszeit. Und genau diese kann bei einer großen Rate an Anfragen zum Verhängnis werden. Pawno ist nicht wie viele andere Programmiersprachen eine Sprache, die Dinge gleichzeitig ausführt sondern, die eins nach dem anderen macht. Demnach wird in der Zeit, in der MySQL-Anfragen verarbeitet werden eigentlich nichts anderes gemacht als gewartet. Demnach müssen wir als Programmierer nun nachhelfen und sozusagen eine zweite Spur eröffnen, dass alles weitergehen kann, während die Anfrage verarbeitet wird. Es muss also ein weiterer Thread benutzt werden. Dies ist in diesem Falle mit Plugins möglich und da MySQL ein solcher ist, können wir uns das doch mal genauer anschauen. Dazu eine kurze Grafik, um dies zu veranschaulichen.
Vorgehen
Was wir nun tun müssen ist, dass wir einen zweiten Thread eröffnen und alles, was wir nach dem Ausführen des Querys tun wollen, in dem zweiten Thread erledigen. Das ganze hört sich viel zu theoretisch und viel zu schwer an, daher möchte ich nun etwas Licht ins Dunkle bringen. Hier die Parameter zu den zwei Funktionen, die wir benötigen werden:
Zitatmysql_query
Parameter: (query[], resultid, extraid, connectionHandle)
query[] - Der Query, der ausgeführt werden soll.
resultid - ID des Querys auf dem neuen Thread.
extraid - Beliebiger Integer, der auf den neuen Thread mitgeliefert werden soll.
connectionHandle - Handle der Datenbank-Verbindung (Hier nicht benötigt).
ZitatOnQueryFinish
Parameter: (query[], resultid, extraid, connectionHandle)
query[] - Der Query, der ausgeführt wurde.
resultid - ID des Querys, der via mysql_query ausgeführt wurde.
extraid - Beliebiger Integer, der über mysql_query mitgeliefert wurde.
connectionHandle - Handle der Datenbank-Verbindung (Hier nicht benötigt).
Nun schreiben wir doch einfach den oben benutzten Codeausschnitt so um, dass er auf einem neuen Thread laufen wird.
#define Thread_OnPlayerConnect (0)
public OnPlayerConnect(playerid) {
new name[MAX_PLAYER_NAME], query[85];
GetPlayerName(playerid, name, sizeof(name));
format(query, sizeof(query), "SELECT banned FROM samp_usertable WHERE user = '%s'", name);
mysql_query(query, Thread_OnPlayerConnect, playerid);
}
Was wir nun hier machen ist eigentlich ganz simple. Wir schreiben unseren Code einfach wieder wie vorhin bis zum formatieren unseres Querys. Bei mysql_query verwenden wir nun aber zwei Parameter mehr die, a) dem Query die QueryID 0 geben und b) dem neuen Thread die playerid mitgeben, da diese später zum rausschmiss des Spielers benötigt wird. Die ConnectionHandle benutzen wir hier nicht, da wir nun mit einer Datenbank arbeiten. Nun ist es möglich mithilfe des Plugins unser Vorhaben, das in Verbindung mit dem Query steht auf einem neuen Thread weiterlaufen zu lassen und auf unserem Hauptthread ganz normal nach dem Ausführen des Querys ohne jegliche Wartezeit weitermachen können. Daher wird es weder zu Lag für andere Spieler führen noch zu einem Servercrash, da alles sehr strukturiert weiterlaufen kann. Dann arbeiten wir doch einfach auf dem zweiten Query weiter ...
public OnQueryFinish(query[], resultid, extraid, connectionHandle) {
switch(resultid) {
case Thread_OnPlayerConnect: {
new var_banned;
mysql_store_result();
var_banned = mysql_fetch_int();
mysql_free_result();
if(var_banned > 0) Kick(extraid);
else
{
/* Weiteres Vorhaben hier ... */
}
}
}
}
Der Callback OnQueryFinish wird nun also aufgerufen und daher schauen wir nach, welcher Query dies verursacht hat, indem wir die resultid (siehe oben) in einem switch vergleichen. Da wir Thread_OnPlayerConnect als 0 definiert haben wird also nun im case 0 operiert. Wir definieren unsere Variablen, die benötigt werden und zwischenspeichern uns die Ergebnisse des Querys. Wir speichern das Ergebnis in dem Integer var_banned und schließen extraid, was playerid entspricht, da wir es via mysql_query mitgeliefert haben, vom Spiel aus.
Fazit
Wir haben also mit sehr wenig Aufwand einen zweiten Thread eröffnet und jeglichen Lag und jegliche Servercrashes, die in Verbindung mit MySQL auftreten können vermieden. Es ist demnach auf jeden Fall zu raten, diese Methode vorzuziehen, falls auf dem Server, den ihr versucht zu programmieren mit MySQL operiert wird und viele Querys verwendet werden müssen, bei großer Spieleranzahl. Meistens wird es bereits bei ca. 20 Spielern zu einem Servercrash kommen, das sind zumindest die Fälle, die mir bekannt sind. Kommt natürlich auch auf mehrere Faktoren drauf an, dennoch wird es bei großem Datenaufkommen sehr schnell auch bei guten Servern zu Probleme kommen.
Edits:
#1: Änderung des Themas (siehe oben).