[MySQL] OnQueryFinish - Performance verbessern!

Wichtiger Hinweis: Bitte ändert nicht manuell die Schriftfarbe auf schwarz sondern belasst es bei der Standardeinstellung. Somit tragt ihr dazu bei dass euer Text auch bei Verwendung unseren dunklen Forenstils noch lesbar ist!

Tipp: Ihr wollt längere Codeausschnitte oder Logfiles bereitstellen? Benutzt unseren eigenen PasteBin-Dienst Link
  • 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:


    Zitat

    mysql_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).


    Zitat

    OnQueryFinish
    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).

  • Schönes Tutorial.
    Kann dein Fazit mit dem Crashes etc, aber definitiv (auch aus Erfahrung) nicht zustimmen.

  • Schönes Tutorial.
    Kann dein Fazit mit dem Crashes etc, aber definitiv (auch aus Erfahrung) nicht zustimmen.


    Kann aus eigener Erfahrung dem Fazit zustimmen, da ich dachte, als ich meinen ersten Server starte, dass niemals eine so große Anzahl an Spieler anwesend sein würden. Daher ist mein Server mehrmals gecrasht und musste somit einige Tage geschlossen werden. Nachdem ich dann alles auf einem zweiten Thread laufen gelassen habe lief alles wieder perfekt. Hierzu ein Zitat aus dem englischen Forum von krisk:

    Zitat

    Then, if your DB is lagging, a big query, lots of shit being returned etc. you don't have to freeze the main thread so everybody lags, you simply just execute the query in a separate thread and call pawn with the result when it's done, this decreased any lag issue on LS:RP a lot, considering we have lots of shit running on SQL and relying on SQL, when I moved over lots to use another thread than the main thread instead, the lag was gone.

  • Solche probleme hatte ich ansich noch nie, da ich den Server immer mit mind. 40 Bots austeste, würde dir empfehelen das gleiche zu machen, und somit die "Belaßtbarkeit" vom Server zu testen.


    Bots führen dennoch nicht konstant Commands aus etc. und benutzen daher eigentlich garkeine wirklichen Funktionen. Sie tun ja hauptsächlich eigentlich nur das, was in den Dateien für den entsprechenden Bot steht. Daher ist es nicht wirklich wirklichkeitsgetreu mit Bots die Belastbarkeit zu testen. Es benötigt ja ebenfalls eine bestimmte Zeit, bis ein solches Datenvolumen aufkommt, dass es nicht mehr tragbar für den Server ist und somit der Hauptthread einfriert.


  • Bots führen dennoch nicht konstant Commands aus etc. und benutzen daher eigentlich garkeine wirklichen Funktionen. Sie tun ja hauptsächlich eigentlich nur das, was in der recordings Datei steht. Daher ist es nicht wirklich wirklichkeitsgetreu mit Bots die Belastbarkeit zu testen.


    Nicht die "SA-MP Internen" Bots sondern einen externen Bot der sich am anfang registriert einloggt, und danach eine reihe der Commands ausführt.
    /E: Das kann man im Hintergrund ne weile laufen lassen, aber ich denke diese Disskusion sollten wir per PM austragen, sonst wirds hier voll und wir kommen noch mit ner Verwarnung davon...

  • Ich benutze zwar MySQL, aber das wusste ich noch gar nicht.
    Danke für die Information, scheint ja doch relativ wichtig zu sein.
    Es wird dann aber leider ziemlich unübersichtlich, wenn der Code an einer völlig anderen Stelle weiter geht. Aber da kann man wohl nicht wirklich etwas machen.


    LG FlasH

    Professioneller Webentwickler.

  • Guten Abend,
    Ich muss sagen, gutes Tutorial, benutze diese Methode auch schon länger und bin zufrieden.
    Jedoch würde ich zum Tutorial noch dazu schreiben das dies nur auf StrickenKids MySQL Plugin bezogen ist, da bei BlueG alles wieder ein wenig anders ist.


    Lg, Zunno


  • Kann aus eigener Erfahrung dem Fazit zustimmen, da ich dachte, als ich meinen ersten Server starte, dass niemals eine so große Anzahl an Spieler anwesend sein würden. Daher ist mein Server mehrmals gecrasht und musste somit einige Tage geschlossen werden. Nachdem ich dann alles auf einem zweiten Thread laufen gelassen habe lief alles wieder perfekt. Hierzu ein Zitat aus dem englischen Forum von krisk:

    Dann hattest Du wahrscheinlich zu diesem Zeitpunk in punkto SQL null Erfahrung ;) - that's are just my two cents.

  • Man muss auch wissen, dass man nicht immer diese Methode benutzen kann, denn sonst kommt es, wie bei mir einst, zu den härtesten Verbindungen von Query zu Query und Callback zu Callback.
    Ihr solltet auch noch die ältere Methode benutzen.

  • Gute Erklärung. Ich hab mit unthreaded Queries auch schlechte Erfahrungen gemacht. Lad mal 2000 MySQL Datensätze mit je 50 Feldern und rechne damit, da kommt der Server in einen wirklich spührbaren Lag (Datenbank wurde optimiert, sprich Datengrössen, Datenlängen etc)

  • Ich habe hierzu ein paar Fragen, haben mir es jetzt nicht ganz genau durchgelesen, daher könnte ich es uebersehen haben.


    1. Muss das Callback OnQueryEnd genannt werden, oder kann ich es z.B. OnSthEnd nennen.
    2. Kann ich das ganze auch anderweitig verwenden, also in ein anderes Callback weiterleiten-
    3. Wie kann ich das denn bei den mysql Funktion von maddins Tutorial machen (mysql_setint ...)
    4. Wird das ganze schon bei der r5 benötigt, da war ja das mysql_query anders. Wurde es da automatisch ausgeführt, oder war es unthreaded?
    5. Ich muss dafür die r7 haben, oder?

  • Dominik.
    1. Nein du musst das Callback OnQueryEnd nennen.
    2. Du kannst in dem Callback natürlich ein anderes aufrufen, klar warum nicht.
    3. Natürlich kannst du deine MySQL Funktionen die "INSERT, UPDATE oder DELETE" ausführen, umschreiben so das diese Multi Threaded sind.


    Zu 3, 4 und 5 ist zu beachten das es sich hier um StrickenKids MySQL Plugin handelt und nicht um das von BlueG.
    Daher können die Funktionen von Maddins Tutorial auch nicht kompatible mit diesem Plugin sein.



    Gruß, Zunno