Deblin: SELECT in einer Schleife schlecht

Hallo Forum,

ich plane derzeit ein neues Projekt und stehe gerade vor einer Entscheidung, wie ich etwas löse. Geplant ist ein Browser-Spiel.

Man tritt "gegeneinander an". Es gibt eine Tabelle in der Statistiken jedes Users gespiechert sind. Z.B: Angriff, Abwehr.

So jetzt möchte ich es so machen, dass man während des Tages sich einen Gegner aussuchen kann und wenn dieser bestätigt die Gegenüberstellung in eine Kampf-Tabelle  eingetragen wird. (MySQl-DB)

Felder könnte ich mir so vorstellen:

Tabelle Gegenüberstellung: id, u1_id, u2_id, gew_id

Nachts soll die Gegenüberstellung per Cronjob ausgeführt werden (Aufrufen einer PHP-Datei) und da weiß ich nicht so genau wie ich das lösen könnte.

Es sollen quasi für jede Gegenüberstellung die entsprechenden Statistiken der User ausgelesen werden, verarbeitet werden und anschließend die Tabelle Gegenüberstellung aktualisiert werden(Update).

Was ich aber aufgrund von Performance-Problemen unbedingt vermeiden möchte ist, dass ich in eine Schleife SELECT und UPDATE Anweisungen dafür schreiben muss.

Sowas möchte ich NICHT:

  
- Auslesen der Gegenüberstellungen  
  
<Start-Schleife solange bis alle Gegenüberstellungen>  
- Auslesen Stats User1 aus DB (SELECT)  
- Auslesen Stats User2 aus DB (SELECT)  
- Verarbeitung in PHP  
- Aktualisierung der DB (UPDATE)  
<Ende-Schleife>  

Kann ich die Abfragen in der Schleife irgendwie umgehen bzw. optimieren?

Danke im Vorraus

  1. yo,

    Kann ich die Abfragen in der Schleife irgendwie umgehen bzw. optimieren?

    das kommt drauf an, welche logik sich hinter dem php script verbirgt. wenn du es mit einem uodate lösen willst, dann muss auch dort diese lpgik abgebildert werden. aber ohne genau zu wissen, was dabei passiert,  ist es schwer, dir deine frage zu beantworten.

    Ilja

    1. das kommt drauf an, welche logik sich hinter dem php script verbirgt. wenn du es mit einem uodate lösen willst, dann muss auch dort diese lpgik abgebildert werden. aber ohne genau zu wissen, was dabei passiert,  ist es schwer, dir deine frage zu beantworten.

      Ilja

      Dachte ich hätte das mit dem Stück Pseudocode ein wenig verdeutlicht. Ist denn klar, was ich genau erreichen möchte?

      Es sollen aus einer Tabelle alle Datensätze ausgelesen werden, die während des Tages zusammengekommen sind.

      Anhand der ausgelesenen User-IDs brauche ich Statistiken (sagen wir das sind z.B. Rohstoffe in einem Strategiespiel .. Holz, Stein, Eisen).

      Jetzt sind die Statistiken ausgelesen und werden verarbeitet.

      z.B.:
      User1 Holz = 200
      User2 Holz = 300

      Da User2 mehr als User1 hat bekommt User2 1 Punkt.... so läuft die ganze Verarbeitung ab, bis ein Gewinner feststeht. Dann wird ein Update in der Datenbank gemacht und der KAmpf für den User2 gewertet, da dieser isngesamt "gewonnen" hat.

      Also hier nochmal wie ich mir das vorstellen könnte:

      Tabelle User:

      id (PK)
      name (Unique)
      ...

      Tabelle Stats:

      id (PK)
      u_id (FK)
      eisen
      holz
      stein

      Tabelle Kampf

      id (PK)
      u1 (FK)
      u2 (FK)
      gewinnerkampf_id

      Das ist also die vereinfachte Version. Jetzt soll Nachts um 04:32 z.B. wenn alle normalerweise brav schlafen die Tabelle Kampf ausgelesen werden und die User die sich gegenüberstehen einen "Kampf" haben bei dem die Tabelle Stats mit einbezogen wird.

      Verarbeitung PHP-Seitig ist nebensächlich... da werden einige Additionen, Subtraktionen, usw. gemacht. Das was mir wichtig ist, ist das so hinzubekommen, dass ich innerhalb einer Schleife keine Update- oder SELECT-Abfragen habe.

      Weil kann mir vorstellen, dass bei 2000 Datensätzen in Tabelle Kampf und dann pro Datensatz 2 SELECT Abfragen  um die Stats auszulesen + 1 UPDATE Abfrage der Server erstmal gut am Arbeiten wäre.

      Kann man das geschickter lösen?

      1. yo,

        wenn sich die kämpfe eines tages nicht gegenseitig beeinflussen, dann kann ich mir vorstellen, dass man es mit einem update lösen kann. wenn sie allerdings abhängig voneinander sind, sprich wenn user1 den ersten kampf gewonnen hat und das den nächsten kampf irgendwie beinflussen kann, dann nicht. sprich, alle kämpfe müssen von dem ausgang anderer kämpfe unabhänhgig sein. wie bereits gesagt, die logik wer als gewinner aus dem jeweiligen kampf hervorgeht, muss auch in die update-anweisung rein.

        Tabelle User:

        id (PK)
        name (Unique)
        ...

        Tabelle Stats:

        id (PK)
        u_id (FK)
        eisen
        holz
        stein

        das scheint mir nicht ganz optimal zu sein. kann den ein user mehrere einträge in der tabelle stats haben ? sieht so aus, als wenn zwischen den tabllen eine 1:1 beziehung existiert.

        Ilja

        1. wenn sich die kämpfe eines tages nicht gegenseitig beeinflussen, dann kann ich mir vorstellen, dass man es mit einem update lösen kann. wenn sie allerdings abhängig voneinander sind, sprich wenn user1 den ersten kampf gewonnen hat und das den nächsten kampf irgendwie beinflussen kann, dann nicht. sprich, alle kämpfe müssen von dem ausgang anderer kämpfe unabhänhgig sein. wie bereits gesagt, die logik wer als gewinner aus dem jeweiligen kampf hervorgeht, muss auch in die update-anweisung rein.

          Die Kämpfe wären unabhängig voneinander. Aber müsste ich dann nicht x-Updates an die Datenbank schicken? (x - Anzahld er Kämpfe)

          Das würde doch den Server erstmal auf 100% Auslastung für ne Zeit lang bringen.

          das scheint mir nicht ganz optimal zu sein. kann den ein user mehrere einträge in der tabelle stats haben ? sieht so aus, als wenn zwischen den tabllen eine 1:1 beziehung existiert.

          Richtig - mein Fehler. Ich habe es natürlich so, dass eine 1:1 Beziehung besteht und in der Stats-Tabelle das Feld u_id nicht vorhanden sein wird.

          1. yo,

            Aber müsste ich dann nicht x-Updates an die Datenbank schicken? (x - Anzahld er Kämpfe)

            ich kann mich nur wiederholen, was ich dir schon zweimal gesagt habe. wenn du die logik, die entscheidet wer gewonnen hat, in die update-anweisung mit reinbekommst, dann geht es mit einer anweisung. wie genau die anweisung aussieht, das geht nur, wenn wir ganz genau wissen, wie sie aussieht.

            noch ein anmerkung zu deinen tabellen. wenn es eine 1:1 beziehung zwischen dem user und den stats gibt, warum lagerst du dann die stats in eine zweite tabelle aus, anstele sie allei in einer tabelle unterzubringen ?

            Ilja

            1. noch ein anmerkung zu deinen tabellen. wenn es eine 1:1 beziehung zwischen dem user und den stats gibt, warum lagerst du dann die stats in eine zweite tabelle aus, anstele sie allei in einer tabelle unterzubringen ?

              An der Stats-Tabelle werden häufig UPDATE-Anweisungen gemacht. D.h. dass der Query-Cache da recht selten benutzt werden kann.

              Bei den Werten in der User-Tabelle ändert sich dagegen eher selten was.

              Ich wollte als Tabellentyp bei der Stats-Tabelle auch MEMORY nehmen. Die liegt dann so wie ich es verstanden habe im RAM und der Zugriff müsste dann dementsprechend schneller sein?!?? Liege ich da richtig?

              1. Hallo,

                meiner Meinung nach solltest du von fortgeschrittenen Konzepten, wie du sie hier aufführst (Memory Tabellen, Auslagerungen für die Optimierung des Query Caches), Abstand zu nehmen und deine Softwareprozesse erstmal grundlegend solide aufzubauen.

                RAM Daten sind überdurchschnittlich volatil gegenüber physikalisch gespeicherten Daten. :)

                Ich stelle nicht in Frage, dass eine Auslagerung von Daten aus dem von dir genannten Grunde legitim ist, sondern dass du solche Optimierungen erst später in Angriff nehmen solltest. Wir bzw. Du reden sicherlich nicht von überdurchschnittlich vielen Daten, die nicht mehr von üblicher Providerhardware zu händeln sind.

                Cheers, Frank

                1. Ich stelle nicht in Frage, dass eine Auslagerung von Daten aus dem von dir genannten Grunde legitim ist, sondern dass du solche Optimierungen erst später in Angriff nehmen solltest. Wir bzw. Du reden sicherlich nicht von überdurchschnittlich vielen Daten, die nicht mehr von üblicher Providerhardware zu händeln sind.

                  Ich kenne das nur von einem anderen Projekt, da sind jetzt knapp 14.000 User angemeldet und da habe ich ein ähnliches Szenario mit einer Tabelle user in der auch die Statistiken sind, die sich häufig ändern.

                  Da habe ich spaßeshalber mal eine Tabelle erstellt unabhängig von der Usertabelle und gängige Abfragen gemacht. Die Abfragen liefen deutlich schneller ab, wenn die Tabelle mit den häufig ändernden Werten ausgelagert war.

                  Deswegen würde ich das gerne in eine einzelne Tabelle packen. Aber wenn ihr mir das später raten würdet, dann werde ich das so machen. Danke

                  PS: Indizes sind gesetzt :)

                2. RAM Daten sind überdurchschnittlich volatil gegenüber physikalisch gespeicherten Daten. :)

                  Die Frage mit der Datensicherung in dem Fall habe ich mir auch schon gestellt - wie man das am besten lösen sollte :)

                  Vor allem nachdem mein Serveranbieter mal einfach den Strom vor ca. 2-3 Wochen bei einem meiner Server ohne Warnung abgestellt hatte. =)

              2. yo,

                tuning ist sicherlich immer ein aspekt, der auch in datenmodellierung eingehen kann. allerdings sollte man das erst tun, wenn überhaupt bedarf vorliegt.

                zum anderen ist tuning das schwierigste thema überhaupt, was datenbanken betrifft, da man sich dann schon ein wenig mehr mit den interen gegebenheiten beschäftigen muss. Ich kenne mich da bei mysql zuwenig aus, komme aus der oracle ecke. aber ich will mich da dem rat vom frank anschließen, mache erst einmal ein "normales" datendesign.

                Ilja

      2. Mahlzeit,

        Kann man das geschickter lösen?

        Wie wär's damit:

        Schreib das Ergebnis des Kampfes in eine separate Tabelle - wenn deine Datenbank dann ineinanderverschachtelte Abfragen unterstützt, könnte irgendwas in folgender Art vielleicht klappen (Pseudo-SQL-Code, klappt bestimmt nicht ohne dran rumzuschrauben):

        -----8<-----8<-----8<-----

        INSERT INTO ergebnis
               (kampf_id, gewinner_id)
        VALUES (SELECT id AS kampf_id,
                       (/* hier lustige Berechnungen in SQL, wobei als Ergebnis die ID des Gewinners der Begegnung rauskommen muss */) AS gewinner_id
                  FROM kampf k
                  JOIN stats s1 ON k.u1 = s1.u_id
                  JOIN stats s2 ON k.u2 = s2.u_id
               )

        ----->8----->8----->8-----

        So könntest Du die Berechnungen direkt in der Datenbank machen (lassen), hättest keine (unnötigen) Schleifen in PHP und wärst vielleicht sogar schneller (wenn du es schaffst, die Berechnungen in SQL abzubilden - aber du sagtest ja, dass das einfache Arithmetik ist).

        MfG,
        EKKi

        1. Wie wär's damit:

          Schreib das Ergebnis des Kampfes in eine separate Tabelle - wenn deine Datenbank dann ineinanderverschachtelte Abfragen unterstützt, könnte irgendwas in folgender Art vielleicht klappen (Pseudo-SQL-Code, klappt bestimmt nicht ohne dran rumzuschrauben):

          Danke erstmal an sowas habe ich ungefähr gedacht aber du hattest es im Beitrag bereits geschrieben. Richtig - es war stark vereinfacht.

          Es sollen insgesamt 10 Statistikwerte pro User berechnet werden, wobei in die Berechnung auch noch 3 Statische Werte je gewählter Kampf-Charakter des Users sowie eine Zufallszahl(Tagesform) mit einbezogen werden.

          Solch komplexe Berechnungen würden den mysqld-Dienst doch erstmal lahmlegen.

          Hmm ich könnte das auch so machen, dass man pro Tag nur einen Kampf haben darf und dieser direkt stattfindet, wenn ein User eine Herausforderung annimmt. Das ganze würde dann aber die Idee von "Am Tag aufbauen und in der Nacht die Schlacht" nicht verfolgen.

          Ok ich sag mal so: Für INSERTs gibts ja bereits eine performante Lösung indem ich innerhalb der Schleife lediglich einen String erweitere und nach der Schleife den String an die DB schicke.

          Sowas hier ungefähr:

            
          $sql = "INSERT INTO kampf VALUES ";  
            
          for($i=0;$i<$anz_kampf;$i++) {  
           $sql .= " ('', $var1, $var2, ...)";  
           if($i<$anz_kampf-1) $sql .= ", ";  
          }  
          mysql_query($sql)  
            
          
          

          Das würde ja erstmal gehen. Aber wie bekomme ich in die Schleife die entsprechenden Werte/Statistiken für den jeweiligen User, ohne jeweils eine SELECT-Abfrage mit in die Schleife zu packen?

          1. Mahlzeit,

            Das würde ja erstmal gehen. Aber wie bekomme ich in die Schleife die entsprechenden Werte/Statistiken für den jeweiligen User, ohne jeweils eine SELECT-Abfrage mit in die Schleife zu packen?

            Wieso jeweils eine? Mach EINE Abfrage a la "SELECT s1.*, s2.* FROM kampf k JOIN stats s1 ON k.u1_id = s1.u_id JOIN stats s2 ON k.u2_id = s2.u_id" (oder wie auch immer die Tabellen und Felder heißen - wirst es schon hinbekommen :-)) und du hast innerhalb des Ergebnisses pro Datensatz alles, was du brauchst. Da gehst du einfach in ner Schleife durch, berechnest das Ergebnis des jeweiligen Kampfes und bastelst dir - wie du schon geschrieben hast - die jeweiligen Werte für das INSERT-Statement in die Ergebnis-Tabelle zusammen ...

            MfG,
            EKKi

            1. Wieso jeweils eine? Mach EINE Abfrage a la "SELECT s1.*, s2.* FROM kampf k JOIN stats s1 ON k.u1_id = s1.u_id JOIN stats s2 ON k.u2_id = s2.u_id" (oder wie auch immer die Tabellen und Felder heißen - wirst es schon hinbekommen :-))

              Richtig ein blöder Gedankenfehler von mir, jetzt verstehe ich wie ich es hinbekomme. Mich hatte nur gestört, dass es 2 Verknüpfungen mit der Usertabelle (da 2 User) geben würde. Besten Dank für die Erleuchtung