Der korrekte Umgang mit mySQL

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
  • Guten Tag zusammen,


    da das Tutorial von maddin ja doch schon ein paar Donnerstage zurückliegt und eigentlich eher für Anfänger konzipiert ist, dachte ich mir - da die Methode von maddin nicht wirklich serverfreundlich ist - versuch ich dem Ein oder Anderen hier im Raume zu vermitteln, wie mySQL nun wirklich funktioniert, worauf zu achten ist und welche Fehler vermieden werden sollten. Oftmals sehe ich hier im Forum, dass der Umgang mit mySQL völlig falsch gehandhabt wird, ständig Threads auftauchen wie "Was ist besser? mySQL oder Dini?", dort dann gekonnt mit "mySQL" geantwortet wird und die Halbwahrheit dahinter gut versteckt bleibt.


    Durchaus, so meine Meinung, ist SQL definitiv schneller als jedes Dateisystem, spart Platz und nimmt einem einiges an Arbeit ab - wenn man denn richtig damit umgeht! Ich versuche mich in diesem Tutorial so abstrakt wie möglich, aber dennoch so verständlich wie nötig zu halten, um garnicht groß auf die verschiedenen Plugins einzugehen, sondern mir erschwert auf mySQL zu beziehen.


    Insgesamt müssen wir eigentlich nur 3 Punkte beachten, damit wir das Maximum aus den Vorteilen einer SQL-Datenbank holen können:

    • Versucht, die Datenbank so klein wie möglich zu halten.
    • Lasst die SQL-Datenbank alles übernehmen, was sie übernehmen kann.
    • Versucht Punkt 2 mit so wenig Queries wie möglich zu erzielen.

    1. Versucht, die Datenbank so klein wie möglich zu halten.
    Klingt im Grunde eigentlich ganz einfach, oder? Der Schein trügt! Oftmals bedarf es einer detailverliebten Berechnung und Konzipierung im Vorfeld, damit eine Datenbank wirklich die Leistung erzielt, die sie erzielen könnte. Auch die regelmäßigen Updates einer Datenbankstruktur sind nicht außer Acht zu lassen. Doch für den Anfang beschränken wir uns auf die wohl 2 wichtigsten Regeln im Umgang mit SQL Datenbanken:

    • Eine Spalte sollte immer im richtigen Typus deklariert und niemals größer als der später größtmögliche Inhalt sein.
    • Versucht stets Doppelindizierungen zu vermeiden!

    Punkt 1: Ihr solltet euch im Vorfeld klar machen, was für ein Inhalt eigentlich in die Spalte eurer SQL-Tabelle eingetragen wird. Ist es eine Zahl? Wenn ja, wie groß ist ihr höchster/tiefster Wert? Liegt er über 2.14 Millionen? Liegt er darunter? (Integers können nur Zahlen von -2.147.483.648 bis +2.147.483.647 verarbeiten) - An dieser Stelle ist es ratsam, sich auch mal die Worte "Tinyint", "Smallint", "Mediumint" und "Bigint" anzuschauen - Sie alle erlauben eine chronologisch sortiert größere Zahlenreichweite.

    • Tinyint: 0 bis 255 (1 byte)
    • Smallint: -32.768 bis 32.767 (2 bytes)
    • ...
    • Bigint: -9.223.372.036.854.775.808 bis 9.223.372.036.854.775.807

    Wer also weiß, dass seine Zahl die Zahlengrenze von Smallint nicht überschreiten wird, sollte auch keinen größeren Integer verbrauchen. Selbes System gilt natürlich auch für Floats und Strings - nur ein wenig anders:
    Bei der Erstellung eurer SQL-Tabelle könnt ihr immer maximale Längen angeben (Length/Values in phpMyAdmin): Wenn ihr z.B. ihr braucht für die Koordinaten eines Spielers maximal die 2. Nachkommastelle um genau genug sein zu können,
    wäre die maximale Länge einer Koordinate also 8 (-1234.56) - Zeichen. 8, weil das Minuszeichen ebenso in der Länge relevant ist. Gleiches gilt natürlich für Strings: Die maximale, von SA:MP bestimmte Länge eines Spielernamens ist 24 (ist das noch aktuell?) also braucht eure Spalte auch nur eine Länge von 24, mehr wird niemals möglich sein, da SA:MP das nicht zulässt ;)
    An diesem Punkt kommen wir auf die Aktualisierung der Tabellen: Wenn SA:MP irgendwann mal 32 Zeichen im Namen erlauben sollte, müsst ihr dementsprechend natürlich auch eure Tabellen anpassen, ist ja wohl klar ;)
    Zuletzt sei an dieser Stelle wohl noch der Tabellentypus "boolean" nennenswert. Spalten mit diesem Typus können NUR die Werte true & false enthalten, nichts Anderes. Wer interessiert daran ist, noch detailierter an Größe sparen zu können, sollte sich einfach mal die verschiedenen Typen von SQL-Spalten anschauen und diese Googlen, über die mySQL Dokumentation erfährt mein eigentlich alles relevante, was hier nur die Größe des Tutorials ins unermessliche sprengen würde.
    Punkt 2: Doppelindizierungen sind des Satan's Werk einer jeden SQL-Datenbank und kommen leider auch unter erfahrenen Leuten öfter vor, als man es sich wünscht. Die Doppelindizierung bedeutet, dass ein und der selbe Wert in 2 verschiedenen Spalten/Tabellen unnötig gespeichert wird. Das erschwert die Verarbeitung eines Queries und belastet unnötig die Größe. Das wohl bekannteste Beispiel dafür ist das "Vorname, Nachname" Beispiel:
    Der klischeehafte Laie wird eine Tabelle mit den Werten
    accountId | vorname | nachname | name
    erstellen, in denen die Werte "1", "Max", "Mustermann" und "Max Mustermann" stehen, weil er sich dabei vermutlich denkt: "Brauche ich nur den Vornamen, nehme ich das passende Feld. Brauche ich nur den Nachnamen, nehme ich das andere Feld und brauche ich den vollen Namen habe ich ja das dritte Feld!" - Klingt logisch, ist aber unsinnig. Wenn man den Vornamen und Nachnamen schon irgendwo stehen hat, gibt es schönere Methoden (auch Methoden direkt über SQL) diese miteinander zu verbinden bzw wieder auseinander zu reißen. (Beispiele dafür findet man ebenfalls über Google und die mySQL Doku und werden hier nicht weiter erläutert)


    2. Lasst die SQL-Datenbank alles übernehmen, was sie übernehmen kann!
    Nicht selten sehe ich Codeschnipsel, in denen unnötige Stocks, parasitäre Querys und ausbremsende, logische Vorgänge angewandt werden - Dabei muss die Leistung garnicht so sehr leiden! Ich behaupte hier einfach mal blind, dass mySQL bei 90% aller Server hier schneller rechnen kann, als es das Script auf eurem Server könnte (da das ständig auf die Antwort von mySQL warten muss *g*), wenn der logische Vorgang dahinter sinnvoll ist. Wichtig hierbei zu überlegen ist: Was MUSS ich eigentlich alles haben, um mein Vorhaben umsetzen zu können (Welche Werte)? Muss ich diese Werte wirklich speichern und ausgeben, oder reicht es, wenn sie verarbeitet werden? Was davon kann vllt auch mySQL für mein Script übernehmen?
    Gehen wir mal vom Schlimmsten aus und euer momentaner Query sieht so aus:
    mysql_query("SELECT * FROM accounts");mysql_store_result();while (mysql_fetch_row(result)) { mysql_fetch_field("accountName", result); if (!strcmp(result, "DeinBenutzer")) { // tu dies und das... }}mysql_free_result();
    Dann hat mySQL - vor allem ab einer großen Anzahl an Datenbankeinträgen - unnötig viel Leistung zu erbringen, die man sich ersparen könnte. Gehen wir davon aus, wir wollen das Level eines Spielers mit dem Namen "DeinBenutzer" haben,so würde folgender Query absolut ausreichen:
    mysql_query("SELECT `spielerLevel` FROM `accounts` WHERE `accountName` = 'DeinBenutzer' LIMIT 1");
    mySQL überprüft also in jedem Eintrag nur noch die Spalte "spielerLevel" und liefert das Ergebnis, sobald die Zeile, in der die Spalte "accountName" = "DeinBenutzer" ist. Durch das LIMIT 1 sagen wir mySQL, dass wir mit dem ersten, gefundenen Eintrag zufrieden sind und sich die Datenbank wieder ausruhen darf. Wenden wir dies nicht an, sucht mySQL weiter nach Einträgen, auch wenn der Account schon lange gefunden wurde. (Geht mal davon aus, die 7. Spalte ist ein Treffer und es folgen noch 100.000 weitere, die alle unnötig abgefragt werden)
    Wir wollen diesen Datensatz nun also verarbeiten. Für unser Beispiel wollen wir einfach mal, das Level um 1 erhöhen, wenn der Spieler 1000 Erfahrung erreicht hat. Ein guter Weg wäre demnach einen Query an der Stelle eures Scripts zu platzieren, an dem der User seine Erfahrung erhält:
    mysql_query("UPDATE `accounts` SET `spielerLevel` = `spielerLevel` + 1, `spielerErfahrung` = '0' WHERE `accountName` = 'DeinBenutzer' AND `spielerErfahrung` >= 1000 LIMIT 1");
    Statt also nun erstmal alle Accounts zu überprüfen, dann die Werte auszulesen, zu vergleichen und neu einzutragen, brauchen wir nur noch einen einzigen Query.


    Die hier geführten Beispiele sind natürlich nur firlefanz und werden, abgesehen von Ausnahmefällen, natürlich keinen großen Belang für eure Geschwindigkeiten machen, doch das kann natürlich auch anders aussehen. Überlegen wir uns mal, wir haben 3 Tabellen, in denen 4 Einträge abhängig von einem Datensatz geändert werden sollen: (Für die Übersicht mache ich das jetzt mehrzeilig, was natürlich in PAWN so NICHT funktionieren würde, außer ihr benutzt strcat o.Ä.)


    mysql_query("UPDATE
    `accounts`, `fraktionen`, `staat`
    SET
    `accounts`.`spielerGeld` = `accounts`.`spielerGeld` + (`fraktionen`.`gehalt` * (1 - 0.03)),
    `accounts`.`spielerLevel` = `accounts`.`spielerLevel` + 1,
    `fraktionen`.`kasse` = `fraktionen`.`kasse` - `fraktionen`.`gehalt`,
    `staat`.`kasse` = `staat`.`kasse` + (`fraktionen`.`kasse` * 0.03)
    WHERE
    `accounts`.`accountName` = 'DeinBenutzer` AND
    `accounts`.`fraktion` = `fraktionen`.`fraktionsId` AND
    `staat`.`staatId` = '1'
    ");


    Wir geben in diesem einen Query also nun an, dass der Spieler das Gehalt seiner Fraktion abzüglich 3% Steuern bekommen soll, gleichzeitig das Geld für das Gehalt aus der Fraktionskasse abgerechnet wird und der Staat seine 3% gutgeschrieben bekommt und noch dazu der Spieler einen Levelaufstieg erhält. Das wären, so wie viele es hier bislang machen, vermutlich 6 Querys, den man auf einen reduzieren konnte. (Die meisten SQL Anfänger rufen via SELECT jeweils die Tabelle accounts, fraktionen und staat auf (3 Querys), rechnen dann den Wert der Spalte + den gewünschten Wert und erneuern den Inahlt via UPDATE (3 weitere Querys).
    Das bringt unserem Script schonmal eindeutig mehr Geschwindigkeit und entlastet das System. Wenn man sich anbei das Tutorial von maddin anschaut, in dem nun wirklich JEDES Feld einen eigenen Query bekommt, wären das "[Anzahl der Felder] * 2 (für mysql_get und mysql_set). Bei beispielsweise 16 Feldern, die zu erneuern sind, benötigen wir hier nur noch einen Query statt 32. Das Spiel lässt sich natürlich noch ins Unermessliche weiterspielen und bei mir im Script sind teilweise Queries, die noch einiges mehr tun, als da oben. Doch für die Verständlichkeit, warum man darauf achten sollte, reicht das hier gegebene Beispiel hoffentlich.
    3. Versucht Punkt 2 mit so wenig Queries wie möglich zu erzielen.
    In diesem Sinne, solltet ihr also versuchen, die Anzahl eurer Queries immer so gering wie möglich zu halten. Denn EIN Query mit 128 Änderungen ist immer noch produktiver und schneller als 128 Queries mit EINER Änderung. Anfangs bedarf dies sicherlich etwas Übung, aber hier hilft oftmals die altbewährte Methode mit dem Griff zu Stift und Papier und sich den logischen Vorgang der Queries erstmal aufzuschreiben.
    Ich hoffe, ich konnte dem Ein oder Anderen die Grundlagen der SQL-Verwaltung etwas näher bringen und wünsche euch viel Spaß mit einem schnelleren Script ;)


    Mein CS:GO Server: 62.75.168.39:27016


    Ich bin so hungrig, dass ich vor lauter Durst nicht weiß, was ich rauchen soll - so müde bin ich!
    Freedom is just another word for 'Nothing left to lose'

  • Nett gemacht ;)


    Wenn du jetzt noch erklärst wie man aus 2 Tabellen werte laden kann, dann würdest du einigen helfen.
    Ich quäl damit gerne welche da die nicht wissen was ich da mache :D

    All in all it's just another brick in the wall

  • Stimmt, allerdings rentiert sich da schon wieder ein weiteres Tutorial. Denn allein die JOIN-Funktion ist schon so komplex, dass man sie detailiert erklären muss. Dazu könnte man dann noch CONCAT etc nehmen, um ein ausgeschweifteres Tutorial zu nehmen.
    Aber nachdem ich mir die Threads anschaue, die seit meines Tutorials so geposted werden, entnehme ich daraus, dass sich sowieso keiner auch nur einen Dreck darum schert :p


    Mein CS:GO Server: 62.75.168.39:27016


    Ich bin so hungrig, dass ich vor lauter Durst nicht weiß, was ich rauchen soll - so müde bin ich!
    Freedom is just another word for 'Nothing left to lose'

  • Das liegt leider an maddin sein Tutorial jedoch würd ich es weiterführen wie du auch schon schriebst mit contact etc denn solche Tutorials fehlen hier die nicht nur kopiert werden.
    Das Tutorial sollte jeder der wirklich den Umgang mit MySQL lernen will lesen und drüber nachdenken was eigendlich was ist


    Naja ich denke ich bin vom Thema abgewichen aber eine Verbesserung hab ich noch.


    Beende deine querys mit einen ;

    All in all it's just another brick in the wall

  • Finde ich richtig gut und mit ein wenig logischen denken selbst für einen Anfänger wie mir gut zu verstehen.


    //edit:


    Habe mal versucht das maddin speicherzeugs in einer einzigen query zu speichern, allerdings speichert er es nicht... :/


    mysql_query("UPDATE `accounts` SET `Level` = 'SpielerInfo[playerid][pLevel]', `Geld` = 'SpielerInfo[playerid][pGeld]', `Kills` = 'SpielerInfo[playerid][pKills]', `Tode` = 'SpielerInfo[playerid][pTode]', `Adminlevel` = 'SpielerInfo[playerid][pAdminlevel], `Health` = 'SpielerInfo[playerid][pHealth]' WHERE `Name` = 'SpielerInfo[playerid][pName]'");


    Oder muss ich mit strings arbeiten?

    Mit freundlichen Grüßen

    #define



    Einmal editiert, zuletzt von #define ()

  • Also quasi so?


    new querystring[128];
    format(querystring,sizeof(querystring),"UPDATE `accounts` SET `Level` = '%s', `Geld` = '%s', `Kills` = '%s', `Tode` = '%s', `Adminlevel` = '%s, `Health` = '%s' WHERE `Name` = '%s'",SpielerInfo[playerid][pLevel],SpielerInfo[playerid][pGeld],SpielerInfo[playerid][pKills],SpielerInfo[playerid][pTode],SpielerInfo[playerid][pAdminlevel],SpielerInfo[playerid][pHealth],SpielerInfo[playerid][pName]);
    mysql_query(querystring);

    Mit freundlichen Grüßen

    #define



  • Also quasi so?


    new querystring[128];
    format(querystring,sizeof(querystring),"UPDATE `accounts` SET `Level` = '%s', `Geld` = '%s', `Kills` = '%s', `Tode` = '%s', `Adminlevel` = '%s, `Health` = '%s' WHERE `Name` = '%s'",SpielerInfo[playerid][pLevel],SpielerInfo[playerid][pGeld],SpielerInfo[playerid][pKills],SpielerInfo[playerid][pTode],SpielerInfo[playerid][pAdminlevel],SpielerInfo[playerid][pHealth],SpielerInfo[playerid][pName]);
    mysql_query(querystring);


    Ja genau.