Partage
  • Partager sur Facebook
  • Partager sur Twitter

demande conseils pour requetes sql

problème d'optimisation

Sujet résolu
    19 juillet 2010 à 11:51:22

    Bonjour!

    Je suis en train de développer un système de carte pour un site internet (jeu en ligne).
    Or, la carte est vraiment une ressource critique: je me dois de bien l'optimiser! Mais là, je butte sur un problème... car il me semble qu'il n'y a pas moyen de joindre des tables. Enfin, si mais cela n'optimise pas le script lorsque la base a beaucoup de données.

    Le problème est le suivant: Sur la carte, on peut déposer plusieurs sortes d'"objets". ces objets, je les affiche ensuite en interrogeant la base de donnée. Mais il y a pas mal de type d'"objets" différents.
    Il y a:
    - les objets classiques comme les potions ou les boosts.
    - les équipements des personnages
    - les bourses d'or

    à cela viennent s'ajouter les bâtiments et les personnages que je dois bien sur sélectionner dans la bdd pour les afficher, donc, une table en plus que nous appellerons

    - les commerces
    (- les personnages , que je ne reprendrai pas dans les structures de tables pour ne pas vous embrouiller! ^^)

    Voici maintenant la structure des tables correspondantes:

    CREATE TABLE IF NOT EXISTS `equips_terre` (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `numero` smallint(5) unsigned NOT NULL,
      `position_x` tinyint(9) unsigned NOT NULL DEFAULT '0',
      `position_y` tinyint(3) unsigned NOT NULL,
      `map` mediumint(8) unsigned NOT NULL DEFAULT '1',
      `cache` tinyint(3) unsigned NOT NULL,
      PRIMARY KEY (`id`),
      KEY `position` (`position_x`),
      KEY `numero` (`numero`),
      KEY `position_y` (`position_y`)
    ) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=4882 ;
    
    CREATE TABLE IF NOT EXISTS `objets_terre` (
      `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
      `numero` smallint(5) unsigned NOT NULL DEFAULT '0',
      `position_x` tinyint(11) unsigned NOT NULL DEFAULT '0',
      `position_y` tinyint(3) unsigned NOT NULL,
      `map` mediumint(8) unsigned NOT NULL DEFAULT '1',
      `cache` tinyint(3) unsigned NOT NULL,
      PRIMARY KEY (`id`),
      KEY `position_y` (`position_y`),
      KEY `position` (`position_x`),
      KEY `numero` (`numero`)
    ) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=62950 ;
    
    CREATE TABLE IF NOT EXISTS `or_map` (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `position_x` tinyint(5) unsigned NOT NULL,
      `position_y` tinyint(3) unsigned NOT NULL,
      `map` mediumint(8) unsigned NOT NULL,
      `montant` bigint(20) unsigned NOT NULL,
      `cache` tinyint(3) unsigned NOT NULL,
      PRIMARY KEY (`id`),
      KEY `position` (`position_x`),
      KEY `position_y` (`position_y`)
    ) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=96 ;
    
    CREATE TABLE IF NOT EXISTS `commerce_map` (
      `id_batiment_carte` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `type_batiment_carte` tinyint(3) unsigned NOT NULL,
      `nom_batiment_carte` tinytext NOT NULL,
      `descr_batiment_map` tinytext NOT NULL,
      `image_batiment_carte` tinytext NOT NULL,
      `position_x` tinyint(3) unsigned NOT NULL,
      `position_y` tinyint(3) unsigned NOT NULL,
      `map_batiment_carte` mediumint(8) unsigned NOT NULL,
      PRIMARY KEY (`id_batiment_carte`),
      KEY `position_batiment_carte` (`position_x`),
      KEY `position_y` (`position_y`)
    ) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=137 ;
    


    on a donc 4 tables: 'equips_terre' , 'objets_terre', 'or_map' et 'commerce_map'.

    Ma question:
    Est-il possible de joindre ces tables proprement (sans redondance de données) pour n'effectuer qu'une seule requête sql?

    Honnêtement j'en doute!^^ Mais je préfère quand même demander, on ne sait jamais.
    Les champs sur lesquels j'effectue la sélection (et communs à toutes les tables fournies) sont les champs 'position_x', 'position_y' et 'map'.

    Avant, je joignais les tables sql comme ceci (en élaguant la requête pour ne pas vous embrouiller):
    Je ne vous demande pas de corriger la requête qui va suivre. C'est juste pour vous montrer comment je procédais!

    SELECT x,y,z, (etc...)
    FROM perso
    LEFT JOIN or_carte ON perso.position_x = or_carte.position_x AND perso.position_y = or_carte.position_y AND or_carte.map='$map'
    LEFT JOIN equips_terre ON perso.position_x = equips_terre.position_x AND perso.position_y = equips_terre.position_y AND equips_terre.map='$map'
    WHERE perso.position_x ='$position_x' AND perso.position_y = '$position_y' AND perso.map='$map'
    


    Le problème de cette technique est que le jeu de résultat augmente exponentiellement (ou en tout cas très vite!^^) avec les données trouvées! On se retrouve vite avec des vues extra-longues qui comportent des données pas du tout intègres, ce qui demande encore un traitement par après en PHP. J'en conclu donc que c'est une mauvaise manière de faire pour le long terme! :s

    J'ai pensé au mot-clef 'UNION' mais je ne pense pas que cela soit très différent d'exécuter plusieurs requêtes à la suite.

    Je repose donc ma question:
    Existe-il un autre moyen pour sélectionner ces données AUTREMENT qu'en effectuant des requête sur chaque table séparément?


    Ha oui, si jamais ca peut aider: j'utilise l'interface PDO pour mes requêtes!

    Voila... La question peut paraitre difficile mais à mon avis la réponse sera assez simple: "Il y a pas moyen, tu dois effectuer chaque requête séparément!"^^

    Merci!

    edit: vient de voir qu'il a pas posté dans la bonne catégorie... :-°
    • Partager sur Facebook
    • Partager sur Twitter
      20 juillet 2010 à 17:50:39

      Il y a pas moyen, tu dois effectuer chaque requête séparément!
      • Partager sur Facebook
      • Partager sur Twitter
        20 juillet 2010 à 19:11:50

        Ouaip, je m'en doutais... :s

        Mais j'ai pensé à quelque chose qui pourrait peut-être optimiser la chose: les requetes préparée.

        L'idée serait de créer des procédures stockées, par exemple:

        CREATE PROCEDURE perso (IN x INT,IN y INT, IN map INT )
          BEGIN	
             SELECT id,numero FROM equips_terre WHERE position_x=x AND position_y=y AND map=map);
         END|
        


        et d'appeler ensuite cette procédure dans le code:

        <?php
        $test_sql= $modele->req("call perso(1)");
        ?>
        


        J'ai testé, ca fonctionne très bien... mais je ne sais pas si ça en vaut vraiment la peine (point de vue optimisation)! :o

        pouvez-vous apportez vos critiques à cette méthode? :)

        Merci! :)

        edit:

        je viens d'effectuer des tests et c'est très peu concluant... :s. Effectuer des requetes "normalement" (sans procédures stockées) s'avère plus rapide dans tous les cas! :s

        code php exécuté:

        <?php
        
        //lancement de la procédure stockée.
        $temps_debut = microtime(true);
        $i=1;
        while($i<=1000){
        	$valeur= mt_rand(1,500);
        $modele->req("call perso(".$valeur.")");
        $i++;
        }
        $temps_fin = microtime(true);
        echo 'Temps d\'execution : '.round($temps_fin - $temps_debut, 4);
        
        //lancement de la requête en mode "normal".
        $temps_debut = microtime(true);
        $i=1;
        while($i<=1000){
        	$valeur= mt_rand(1,500);
        $modele->req("SELECT pseudo, pass, mail FROM perso WHERE  position_x=".$valeur);
        $i++;
        }
        echo '<br />';
        $temps_fin = microtime(true);
        echo 'Temps d\'execution : '.round($temps_fin - $temps_debut, 4);
        ?>
        


        cela me renvoit:
        Temps d'execution : 0.3936 (temps pour la procédure stockée)
        Temps d'execution : 0.3526 (temps pour la requete normale)
        


        ... Ce n'est donc pas une bonne méthode! :s
        Avez-vous des idées pour optimiser l'exécution du code?
        Comment se fait-il que la procédure stockée ne soit pas plus rapide?
        Est-ce à cause de la requête qui n'est pas assez complexe?


        Merci!
        • Partager sur Facebook
        • Partager sur Twitter
          20 juillet 2010 à 19:57:54

          Qu'est-ce qui te fait penser que ta carte est la ressource critique ? Tu prévois combien de requêtes/s ?
          • Partager sur Facebook
          • Partager sur Twitter
            20 juillet 2010 à 20:10:30

            Parce que c'est la page la plus lente à se charger! ^^

            Bien... si l'on compte les déplacements, on devrait arriver à 15-16 requêtes facilement :s

            Il y a pas mal d'autres choses à sélectionner, donc bon...

            Mais étant donné que ces requêtes sont toujours exécutée selon les même champs de recherche (à savoir: la position et la map). je me demandais si ce n'était pas possible d'exploiter cela!
            • Partager sur Facebook
            • Partager sur Twitter
              20 juillet 2010 à 22:20:22

              Tes positions x et y sont des tinyint donc ta carte ne peut pas avoir plus de 256x256 cases donc elle rentre en RAM... 15 requêtes c'est beaucoup mais bon, des petites requêtes simples comme ça t'en as pour dans les 50 µs pièce donc ça fait moins de 1 ms au total.

              Sinon je pense que tu demandes tout le temps des coordonnées (x,y) et tu as pas d'index sur (x,y)...

              Envoie les temps de requêtes et les explain...
              • Partager sur Facebook
              • Partager sur Twitter
                21 juillet 2010 à 11:55:51

                Exact! Je modifie cela tout de suite.

                mais je ne pense pas avoir besoin d'aide pour les explains et l'optimisation de telles requêtes. Je suis quand même pas une daube de chez daube à ce point ^^!

                ce que je voulais savoir, c'est juste si on pouvais rassembler de telles requêtes.

                Merci! :)
                • Partager sur Facebook
                • Partager sur Twitter
                  21 juillet 2010 à 12:06:33

                  > Je suis quand même pas une daube de chez daube à ce point ^^!

                  XDDDDD mais tu sais ici je présume du pire, lol.

                  Sinon, vu que ta carte est probablement beaucoup plus souvent lue qu'écrite, et probablement par un truc en AJAX qui demande à un script php de lui renvoyer le contenu d'une case quand le mec clique dessus, mettons en format JSON, rien ne t'empêche de mettre en cache le résultat du script, par exemple dans un fichier par case ou dans une table ou dans memcached...
                  • Partager sur Facebook
                  • Partager sur Twitter
                    21 juillet 2010 à 14:21:44

                    J'y avais pensé mais j'y ai renoncé... le contenu d'une case change trop souvent pour qu'un tel système soit mis en place.

                    À la moindre arrivée d'un joueur ou d'un monstre sur une case, le cache doit être mis à jour...

                    Enfin... il est clair que je vais peut-être peaufiner cette idée! Mettre un cache en fonction du changement (et non pas en fonction du temps) sur chaque requête séparément donnerait sans doute des résultats très concluant!

                    Mh... oui! Merci de l'idée en fait! :p
                    • Partager sur Facebook
                    • Partager sur Twitter
                      21 juillet 2010 à 16:12:20

                      Mettons que sur la même case tu aies 2 joueurs et un streum. Les 2 joueurs attaquent le streum en même temps, il faut savoir lequel va lui porter le coup fatal, donc tu as un problème d'accès concurrent. Tu pourrais faire :

                      BEGIN;
                      UPDATE streums SET hp = hp - $coup WHERE id_streum=... AND hp>0;

                      Si ta requête a updaté une ligne alors ça veut dire que le coup a porté, sinon ça veut dire que le streum était déjà mort, mais tu ne peux pas savoir si le coup a tué le streum parce que tu es sous MySQL et que t'as pas UPDATE RETURNING. Il faut refaire un SELECT, heureusement comme tu es dans une transaction et que tu viens d'UPDATE le streum, tu as un lock dessus jusqu'au COMMIT donc tu peux le SELECT sans que sa valeur change entre-temps.

                      Une façon simple de ne pas te prendre le chou est d'avoir une table "map" qui contient toutes les cases (x,y) en un seul exemplaire. Tu l'as probablement déjà. Avant de faire une action sur une case tu démarres une transaction et tu fais un SELECT FOR UPDATE sur la case, ce qui pose un lock dessus jusqu'au COMMIT, ensuite tu es tranquille, et tu n'as pas à te soucier de l'ordre des opérations qui pourrait causer un interblocage.

                      Et quand t'as fait toutes les modifs qu'il faut sur les tables suite à l'action du joueur tu peux MAJ le cache et COMMIT.
                      • Partager sur Facebook
                      • Partager sur Twitter
                        22 juillet 2010 à 11:28:16

                        Mh...Je vois pas pourquoi tu viens me parler de ça! :p

                        Il faut savoir que le jeu a déjà été en ligne auparavant et qu'il n'y avait pas de problème de la sorte. Lorsque 2 personnes attaquaient un même monstre (quasiment) en même temps, l'un recevait l'xp et l'autre recevait un message :"Le monstre est déjà mort".

                        pour ce qui est du système de mise en cache, c'est décidé, je vais l'adopter. Mais j'y ai un peu réfléchis et je pense que je ne vais pas mettre de cache concernant les requête de monstre et de personne. Je pense que cela créerait trop de création/suppression de fichier sur le serveur (à première vue. mais rien ne dit que je ne vais pas changer d'avis). En revanche, pour les autres requêtes, oui. Ce qui concerne quand même:

                        - les forts
                        - les prisons
                        - les batiments
                        - les commerces
                        - les équipements à terre
                        - les objets à terre
                        - l'or à terre
                        - les pnj
                        - les téléporteurs

                        Ce qui représente quand même une économie de 9 requêtes! J'en demande pas plus! :p
                        • Partager sur Facebook
                        • Partager sur Twitter
                          22 juillet 2010 à 13:36:35

                          Surtout que les bâtiment ne doivent pas se déplacer souvent donc tu vas économiser les MAJ du cache...

                          Tu peux aussi mettre le cache dans la BDD, ce qui a l'avantage de pouvoir utiliser les transactions. Pour stocker des variables php utilise serialize().
                          • Partager sur Facebook
                          • Partager sur Twitter
                            22 juillet 2010 à 15:11:53

                            Oui oui, ne t'inquiète pas, j'ai déjà une classe toute prête pour ça! ^^

                            mais tu dis "mettre le cache dans la BDD". Késako?
                            • Partager sur Facebook
                            • Partager sur Twitter
                              22 juillet 2010 à 15:36:38

                              Je parlais de stocker ton cache dans une table au lieu de le mettre dans des fichiers. Pour stocker des petits objets la BDD est mieux.
                              • Partager sur Facebook
                              • Partager sur Twitter
                                22 juillet 2010 à 16:26:23

                                Ha bon! Je ne suis pas tout à fait convaincu par cette méthode! :o

                                Cela nécessitera une requête pour sélectionner les données mise en cache, et même si c'est une petite requête, il me semble que c'est toujours plus gourant en ressource que d'accéder à un fichier (ex: file_get_contentes() ) . Non?
                                • Partager sur Facebook
                                • Partager sur Twitter
                                  22 juillet 2010 à 18:08:49

                                  Ca dépend, par exemple si tu as 1 million d'objets de 1 Ko, dans le filesystem ils prennent 4 ko chaque donc 4Go (plus les extras) ; dans une BDD ils vont prendre quelque chose comme 500 Mo vu que c'est du texte (serialize()) compressé avec gzip...

                                  Donc dans ce cas la BDD prend beaucoup moins de RAM.

                                  L'intérêt du filesystem c'est que tu les sers comme des fichiers statiques donc sans accéder à la base ni au php, mais pour des petits objets c'est pas efficace du tout car les filesystems actuels sont nuls pour les petits fichiers (sauf reiser4)...
                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    28 juillet 2010 à 21:05:32

                                    Juste pour dire que le système de cache semble très bien fonctionner! ^^

                                    Le chargement de mes pages ne dépasse pas 0.03 sec pour le moment ;)

                                    En revanche, c'est vrai que cela peut vite prendre beaucoup de place avec des fichiers séparés... Cependant, je préfère garder ma façon de faire pour le moment, on verra par après ;)
                                    • Partager sur Facebook
                                    • Partager sur Twitter

                                    demande conseils pour requetes sql

                                    × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                    × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                    • Editeur
                                    • Markdown