[MYSQL] Wie Queries zusammenfügen

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • [MYSQL] Wie Queries zusammenfügen

    Hallo ihr,

    wenn es Sachen gibt womit ich mich schwer tue, dann zählt SQL/MySQL dazu. Als Beispiel, ich habe eine MySQL Datenbank und dort drin derzeit 4 Tabellen. Desweiteren habe ich eine DatenManager Klasse welche alle Daten während das Script läuft hält und zu bestimmten Ereignissen Daten nachlädt oder speichert. Nun habe ich ja gelernt dass viele Datenbankaufrufe sehr Performancelastig sind und nun frage ich euch wie ich diese minimieren kann.

    Ich frage mal anhand eines Beispieles:

    LUA-Quellcode

    1. for key, value in pairs(mapStats) do
    2. self:RunExec("UPDATE toptimes SET " .. key .. " = '" .. value .. "' WHERE mapname = '" .. mapName .. "'")
    3. end
    Hier gehe ich zum Beispiel eine Tabelle durch und schreibe Werte in die Datenbank. Wenn ich es richtig verstanden habe, dann ist jedes ...

    self:RunExec(...)

    ... eine eigene Query. Wenn nun 20 Sachen in der Tabelle sind wären das schonmal 20 Queries nur für EINE Sache.

    Wie kann ich das besser lösen? Die Idee wäre die Query Aufrufe erstmal alle in einem größeren String zu sammeln und dann erst gesammelt abzuschießen. Geht das und wie lang darf so ein String maximal sein?


    Viele Grüße,
    Sam
  • Zum Hilfreichsten Beitrag springen

  • Eine Sache wäre natürlich, alle Strings zusammenzufassen und das am Schluss mit einem Query zu senden.

    Eine weitere Sache, die ich damals auch mal durch Zufall entdeckt habe, ist eine Transaktion vorzubereiten (oder wie man das auch immer genau nennt).
    Das heißt zuerst sagst du das dem Datenbankserver mit folgendem Query:

    SQL-Abfrage

    1. START TRANSACTION;

    Danach schickst du alles deinem Datenbankserver. Zum Beispiel dein obigen Code.
    Wenn alles erfolgreich war, kannst du mit dem folgendem Query übernehmen:

    SQL-Abfrage

    1. COMMIT;

    Sollte irgendwas schief gelaufen sein, kannst du mit dem Query "ROLLBACK;" die Änderungen einfach wieder rückgängig machen oder nicht übernehmen lassen.
    Das wurde z.B. in der register Methode von iConnect genutzt: github.com/HorrorClown/iConnec…ster/iConnect.lua#L65-L91

    Diese Funktion hätte mir damals viel Ärger erspart :D Leider erst zu spät kennengelernt^^. Vielleicht kanntest du das ja auch noch nicht :P

    #Edit: Für weitere Performance Verbesserungen kann dir Justus bestimmt mehr Tipps geben^^.
  • Soweit ich PewX' Post verstanden habe, schickst du dem Datenbankserver zwar jeweils die Querys einzeln, jedoch werden diese erst auf Kommando verarbeitet. Das heißt, im Prinzip passiert noch nichts außer die Entgegennahme der Querys. Vermutlich ist es für die DB performanter die Querys direkt hintereinander abzuarbeiten als ständig neue Query reinzubekommen.

    Hadrev schrieb:

    Ich glaube wir haben den Mailserver mit MySQL gemacht.
  • Zweiteres ist nur eine Funktion, die im Falle eines Fehlers alles problemlos wieder rückgängig machen kann.
    Du kannst halt, was ich mit erstem meinte, bevor du den Query ausführst, den Query String zu einen zusammenfassen und dann eben nur diesen einen übertragen. Musst halt alles mit einem Semikolon trennen.

    Ich denke da lässt sich aber bestimmt noch mehr rausholen als nur den String zusammen zu fassen :D
  • Ich habe nochmal ein kleines Problem, es funktioniert zwar alles und ich kann keine Auffälligkeiten entdecken aber nach einer Weile kommen paar Warnungen:

    Quellcode

    1. [2016-04-02 10:58:23] WARNING: [MarioKart]\MarioKart\server\handler\MySQLHandlerS.lua:111: Database result uncollected after 5 minutes. [Query: START TRANSACTION;]
    2. [2016-04-02 10:58:33] WARNING: [MarioKart]\MarioKart\server\handler\MySQLHandlerS.lua:130: Database result uncollected after 5 minutes. [Query: COMMIT;]

    Ich wette der @PewX weiß was ich tun muss um das weg zu bekommen? 8)

    Hier mal ein Beispielcode wie ich es gerade anwende:

    LUA-Quellcode

    1. function MySQLHandlerS:saveMapStats(mapName, mapStats)
    2. if (mapName) and (mapStats) then
    3. self:establishConnection()
    4. self.dbConnection:query("START TRANSACTION;")
    5. local result = self:getDatabaseQuery("toptimes", "mapname", "*", mapName)
    6. if (#result < 1) then
    7. result = self:RunExec("INSERT toptimes SET mapname = '".. mapName .."'")
    8. end
    9. if (result ~= false) then
    10. for key, value in pairs(mapStats) do
    11. result = self:RunExec("UPDATE toptimes SET " .. key .. " = '" .. value .. "' WHERE mapname = '" .. mapName .. "'")
    12. if (result == false) then
    13. self.dbConnection:query("ROLLBACK;")
    14. return false
    15. end
    16. end
    17. end
    18. self.dbConnection:query("COMMIT;")
    19. return true
    20. end
    21. end
    Alles anzeigen

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von [email protected] ()

  • Danke, ja das wars. So funktioniert es jetzt:

    LUA-Quellcode

    1. function MySQLHandlerS:saveMapVotes(mapName, mapVotes)
    2. if (mapName) and (mapVotes) then
    3. self:establishConnection()
    4. self.dbConnection:exec("START TRANSACTION;")
    5. local result = self:getDatabaseQuery("mapvotes", "mapname", "*", mapName)
    6. if (#result < 1) then
    7. result = self:RunExec("INSERT mapvotes SET mapname = '".. mapName .."'")
    8. end
    9. if (result ~= false) then
    10. for key, value in pairs(mapVotes) do
    11. result = self:RunExec("UPDATE mapvotes SET " .. key .. " = '" .. value .. "' WHERE mapname = '" .. mapName .. "'")
    12. if (result == false) then
    13. self.dbConnection:exec("ROLLBACK;")
    14. return false
    15. end
    16. end
    17. end
    18. self.dbConnection:exec("COMMIT;")
    19. return true
    20. end
    21. end
    Alles anzeigen
  • Ein paar Anmerkungen dazu noch:

    1. Da die MySQL Datenbanken hier eher klein sind, ist der Flaschenhals weniger der MySQL Server, sondern mehr die Verbindung zu ihm aufzunehmen. Dementsprechend werden die Queries an sich womöglich zwar schneller ausgeführt (als wenn du sie einzeln senden würdest), trotzdem hast du dadurch aber sogar noch mehr Verbindungen zum MySQL Server als du ohne Transaktionen gehabt hättest.
    Ein bisschen relativiert wird es jedoch dadurch, dass die Abfragen von dbExec parallel in seinem separaten Thread abgearbeitet werden, sodass du zumindest bei der Scriptausführung keine Blocks feststellen wirst.

    Dennoch ist die weitaus bessere Variante alles in eine INSERT/UPDATE Anfrage zu legen.
    Ich kenne den genauen Aufbau der "mapvotes" Tabelle (insbesondere das was key und value im Einzelfall ist) zwar nicht, aber vielleicht ist es sogar möglich das Ganze noch ein wenig so abzuändern, dass es generell etwas einfacher ist.

    2.

    LUA-Quellcode

    1. self:RunExec("UPDATE mapvotes SET " .. key .. " = '" .. value .. "' WHERE mapname = '" .. mapName .. "'")

    String-Verknüpfungen sind nicht nur langsam, sondern diese Zeile ist auch stark anfällig für SQL Injection. Stattdessen solltest du Prepared Statements wie folgt nutzen:

    LUA-Quellcode

    1. self:RunExec("UPDATE mapvotes SET `?` = ? WHERE mapname = ?", key, value, mapname)
  • Ich danke euch schonmal allen. Scheint nun nach viel rumprobieren alles zu funktionieren. Das ist der derzeitige Code für eine Tabelle. kann man da noch was verbessern?

    LUA-Quellcode

    1. function MySQLHandlerS:saveMapVotes(mapVotesTable)
    2. if (mapVotesTable) then
    3. self:establishConnection()
    4. self.dbConnection:exec("START TRANSACTION;")
    5. for index, mapVote in pairs(mapVotesTable) do
    6. if (mapVote) then
    7. if (mapVote.map) and (mapVote.player) and (mapVote.value) then
    8. if (mapVote.map:len() > 0) and (mapVote.player:len() > 0) and (mapVote.value > 0) then
    9. local tempQuery = self.dbConnection:query("SELECT `mapname` FROM mapvotes WHERE mapname = ? AND player = ?", mapVote.map, mapVote.player)
    10. local poll = tempQuery:poll(self.queryAttemps)
    11. local result = false
    12. if (#poll > 0) then
    13. result = self.dbConnection:exec("UPDATE mapvotes SET vote = ? WHERE mapname = ? AND player = ?", mapVote.value, mapVote.map, mapVote.player)
    14. else
    15. result = self.dbConnection:exec("INSERT INTO mapvotes (mapname, player, vote) VALUES (?,?,?)", mapVote.map, mapVote.player, mapVote.value)
    16. end
    17. if (result == false) then
    18. self.dbConnection:exec("ROLLBACK;")
    19. break
    20. end
    21. end
    22. end
    23. end
    24. end
    25. self.dbConnection:exec("COMMIT;")
    26. end
    27. end
    Alles anzeigen
  • Du könntest die ifs zusammenführen

    Bei


    LUA-Quellcode

    1. if a:test() and b:test() then ... end
    ist garantiert, dass b:test() nur dann ausgeführt wird, wenn a:test() true zurückgibt.

    Ansonsten könntest du noch deinen tempQuery für alle Spieler zusammenführen (Bedingung "AND (Player = ? OR Player = ?)")
    Mich per PN bezüglich Freischaltungen zu nerven ist der beste Weg eine Freischaltung zu verhindern.

    neon-gaming.de