2010-01-06 4 views
1

J'ai une base de données MS Access de 800 Mo que j'ai migrée vers SQLite. La structure de la base de données est la suivante (la base de données SQLite, après la migration, est d'environ 330 Mo):La requête SQLite s'exécute 10 fois plus lentement que la requête MSAccess

La table Occurrence contient 1 600 000 enregistrements. Le tableau ressemble à:

CREATE TABLE Occurrence 
(
SimulationID INTEGER, SimRunID INTEGER, OccurrenceID INTEGER, 
OccurrenceTypeID INTEGER, Period INTEGER, HasSucceeded BOOL, 
PRIMARY KEY (SimulationID, SimRunID, OccurrenceID) 
) 

Il a les indices suivants:

CREATE INDEX "Occurrence_HasSucceeded_idx" ON "Occurrence" ("HasSucceeded" ASC) 

CREATE INDEX "Occurrence_OccurrenceID_idx" ON "Occurrence" ("OccurrenceID" ASC) 

CREATE INDEX "Occurrence_SimRunID_idx" ON "Occurrence" ("SimRunID" ASC) 

CREATE INDEX "Occurrence_SimulationID_idx" ON "Occurrence" ("SimulationID" ASC) 

Le tableau OccurrenceParticipant a 3.400.000 enregistrements. Le tableau ressemble à:

CREATE TABLE OccurrenceParticipant 
(
SimulationID INTEGER,  SimRunID INTEGER, OccurrenceID  INTEGER, 
RoleTypeID  INTEGER,  ParticipantID INTEGER 
) 

Il a les indices suivants:

CREATE INDEX "OccurrenceParticipant_OccurrenceID_idx" ON "OccurrenceParticipant" ("OccurrenceID" ASC) 

CREATE INDEX "OccurrenceParticipant_ParticipantID_idx" ON "OccurrenceParticipant" ("ParticipantID" ASC) 

CREATE INDEX "OccurrenceParticipant_RoleType_idx" ON "OccurrenceParticipant" ("RoleTypeID" ASC) 

CREATE INDEX "OccurrenceParticipant_SimRunID_idx" ON "OccurrenceParticipant" ("SimRunID" ASC) 

CREATE INDEX "OccurrenceParticipant_SimulationID_idx" ON "OccurrenceParticipant" ("SimulationID" ASC) 

Le tableau InitialParticipant a 130 dossiers. La structure de la table est

CREATE TABLE InitialParticipant 
(
ParticipantID INTEGER PRIMARY KEY,  ParticipantTypeID INTEGER, 
ParticipantGroupID  INTEGER 
) 

Le tableau présente les indices suivants:

CREATE INDEX "initialpart_participantTypeID_idx" ON "InitialParticipant" ("ParticipantGroupID" ASC) 

CREATE INDEX "initialpart_ParticipantID_idx" ON "InitialParticipant" ("ParticipantID" ASC) 

Le tableau ParticipantGroup a 22 dossiers. Il semble que

CREATE TABLE ParticipantGroup (
ParticipantGroupID INTEGER, ParticipantGroupTypeID  INTEGER, 
Description varchar (50),  PRIMARY KEY( ParticipantGroupID ) 
) 

Le tableau a l'index suivant: CREATE INDEX "ParticipantGroup_ParticipantGroupID_idx" ON "ParticipantGroup" ("ParticipantGroupID" ASC)

Le tableau tmpSimArgs a 18 dossiers. Il a la structure suivante:

CREATE TABLE tmpSimArgs (SimulationID varchar, SimRunID int(10)) 

Et les indices suivants:

CREATE INDEX tmpSimArgs_SimRunID_idx ON tmpSimArgs(SimRunID ASC) 

CREATE INDEX tmpSimArgs_SimulationID_idx ON tmpSimArgs(SimulationID ASC) 

Le tableau « tmpPartArgs » a 80 dossiers. Il a la structure ci-dessous:

CREATE TABLE tmpPartArgs(participantID INT) 

Et l'index ci-dessous:

CREATE INDEX tmpPartArgs_participantID_idx ON tmpPartArgs(participantID ASC) 

J'ai une requête qui implique plusieurs INNER JOIN et le problème que je suis confronté est la version d'accès de la requête prend environ une deuxième alors que la version SQLite de la même requête prend 10 secondes (environ 10 fois plus lent!) Il m'est impossible de revenir à Access et SQLite est ma seule option.

Je suis nouveau à écrire des requêtes de base de données, par conséquent ces requêtes peuvent sembler stupides, alors s'il vous plaît donner des conseils sur tout ce que vous voyez défectueux ou kid-plat.

La requête dans Access est (toute requête prend 1 seconde pour exécuter):

SELECT ParticipantGroup.Description, Occurrence.SimulationID, Occurrence.SimRunID, Occurrence.Period, Count(OccurrenceParticipant.ParticipantID) AS CountOfParticipantID FROM 
( 
    ParticipantGroup INNER JOIN InitialParticipant ON ParticipantGroup.ParticipantGroupID = InitialParticipant.ParticipantGroupID 
) INNER JOIN 
(
tmpPartArgs INNER JOIN 
    (
    (
     tmpSimArgs INNER JOIN Occurrence ON (tmpSimArgs.SimRunID = Occurrence.SimRunID) AND (tmpSimArgs.SimulationID = Occurrence.SimulationID) 
    ) INNER JOIN OccurrenceParticipant ON (Occurrence.OccurrenceID = OccurrenceParticipant.OccurrenceID) AND (Occurrence.SimRunID = OccurrenceParticipant.SimRunID) AND (Occurrence.SimulationID = OccurrenceParticipant.SimulationID) 
) ON tmpPartArgs.participantID = OccurrenceParticipant.ParticipantID 
) ON InitialParticipant.ParticipantID = OccurrenceParticipant.ParticipantID WHERE (((OccurrenceParticipant.RoleTypeID)=52 Or (OccurrenceParticipant.RoleTypeID)=49)) AND Occurrence.HasSucceeded = True GROUP BY ParticipantGroup.Description, Occurrence.SimulationID, Occurrence.SimRunID, Occurrence.Period; 

La requête SQLite est la suivante (cette requête prend environ 10 secondes):

SELECT ij1.Description, ij2.occSimulationID, ij2.occSimRunID, ij2.Period, Count(ij2.occpParticipantID) AS CountOfParticipantID FROM 
(
    SELECT ip.ParticipantGroupID AS ipParticipantGroupID, ip.ParticipantID AS ipParticipantID, ip.ParticipantTypeID, pg.ParticipantGroupID AS pgParticipantGroupID, pg.ParticipantGroupTypeID, pg.Description FROM ParticipantGroup as pg INNER JOIN InitialParticipant AS ip ON pg.ParticipantGroupID = ip.ParticipantGroupID 
) AS ij1 INNER JOIN 
(
    SELECT tpa.participantID AS tpaParticipantID, ij3.* FROM tmpPartArgs AS tpa INNER JOIN 
    (
     SELECT ij4.*, occp.SimulationID as occpSimulationID, occp.SimRunID AS occpSimRunID, occp.OccurrenceID AS occpOccurrenceID, occp.ParticipantID AS occpParticipantID, occp.RoleTypeID FROM 
      (
       SELECT tsa.SimulationID AS tsaSimulationID, tsa.SimRunID AS tsaSimRunID, occ.SimulationID AS occSimulationID, occ.SimRunID AS occSimRunID, occ.OccurrenceID AS occOccurrenceID, occ.OccurrenceTypeID, occ.Period, occ.HasSucceeded FROM tmpSimArgs AS tsa INNER JOIN Occurrence AS occ ON (tsa.SimRunID = occ.SimRunID) AND (tsa.SimulationID = occ.SimulationID) 
     ) AS ij4 INNER JOIN OccurrenceParticipant AS occp ON (occOccurrenceID =  occpOccurrenceID) AND (occSimRunID = occpSimRunID) AND (occSimulationID = occpSimulationID) 
    ) AS ij3 ON tpa.participantID = ij3.occpParticipantID 
) AS ij2 ON ij1.ipParticipantID = ij2.occpParticipantID WHERE (((ij2.RoleTypeID)=52 Or (ij2.RoleTypeID)=49)) AND ij2.HasSucceeded = 1 GROUP BY ij1.Description, ij2.occSimulationID, ij2.occSimRunID, ij2.Period; 

I Je ne sais pas ce que je fais mal ici.J'ai tous les index mais je pense que je suis absent en déclarant un index clé qui fera l'affaire pour moi. La chose intéressante est que, avant la migration, ma «recherche» sur SQLite a montré que SQLite est plus rapide, plus petit et meilleur dans tous les aspects que Access. Mais je ne peux pas sembler que SQLite fonctionne plus vite qu'Access en terme d'interrogation. Je réitère que je suis nouveau à SQLite et évidemment n'ai pas beaucoup d'idée aussi bien que l'expérience ainsi si n'importe quelle âme savante pourrait m'aider avec ceci, elle sera très appréciée.

+1

Cette requête me fait mal à la tête. Je ne vois pas pourquoi vous faites toutes les requêtes dérivées (sous-sélections). Pouvez-vous expliquer en anglais (au lieu de SQL) ce que vous essayez de renvoyer de la requête? Cela faciliterait la réponse à votre question. – JohnFx

+0

Je vais vous expliquer ce que fait chaque déclaration de sous-sélection en anglais .. Puisque cette boîte de commentaire ne peut contenir que 600 caractères, je poste les explications comme réponse à ma question .. –

Répondre

0

J'ai présenté une version réduite de ma requête. J'espère que c'est plus clair et plus lisible que mon précédent.

SELECT5 * FROM 
(
SELECT4 FROM ParticipantGroup as pg INNER JOIN InitialParticipant AS ip ON pg.ParticipantGroupID = ip.ParticipantGroupID 
) AS ij1 INNER JOIN 
(
    SELECT3 * FROM tmpPartArgs AS tpa INNER JOIN 
     (
      SELECT2 * FROM 
       (
        SELECT1 * FROM tmpSimArgs AS tsa INNER JOIN Occurrence AS occ ON (tsa.SimRunID = occ.SimRunID) AND (tsa.SimulationID = occ.SimulationID) 
      ) AS ij4 INNER JOIN OccurrenceParticipant AS occp ON (occOccurrenceID =  occpOccurrenceID) AND (occSimRunID = occpSimRunID) AND (occSimulationID = occpSimulationID) 
    ) AS ij3 ON tpa.participantID = ij3.occpParticipantID 
) AS ij2 ON ij1.ipParticipantID = ij2.occpParticipantID WHERE (((ij2.RoleTypeID)=52 Or (ij2.RoleTypeID)=49)) AND ij2.HasSucceeded = 1 

L'application que je travaille est une application de simulation et afin de comprendre le contexte de la requête ci-dessus je pensais qu'il est nécessaire de donner une brève explication de l'application. Supposons qu'il y ait une planète avec des ressources initiales et des agents vivants. La planète est autorisée à exister pendant 1000 ans et les actions effectuées par les agents sont surveillées et stockées dans la base de données. Après 1000 ans, la planète est détruite et recréée avec le même ensemble de ressources initiales et d'agents vivants que la première fois. Ceci (la création et la destruction) est répété 18 fois et toutes les actions des agents effectuées pendant ces 1000 ans sont stockées dans la base de données. Ainsi, toute notre expérience consiste en 18 re-créations qui sont appelées «Simulation». Chacune des 18 fois que la planète est recréée est appelée une course et chacune des 1000 années d'une course s'appelle une période. Donc, une 'Simulation' consiste en 18 runs et chaque run consiste en 1000 périodes. Au début de chaque série, nous affectons à la 'Simulation' un ensemble initial d'éléments de connaissances et d'agents dynamiques qui interagissent entre eux et avec les éléments. Un élément de connaissance est stocké par un agent dans un magasin de connaissances. Le magasin de connaissances est également considéré comme une entité participante dans notre simulation. Mais ce concept (concernant les magasins de connaissances) n'est pas important. J'ai essayé d'être détaillé sur chaque instruction SELECT et les tables impliquées.

SELECT1: Je pense que cette requête pourrait être remplacée par la table 'Occurrence', car elle ne fait rien. La table Occurrence stocke les différentes actions effectuées par les agents, à chaque période de chaque simulation d'une «Simulation» particulière. Normalement chaque 'Simulation' consiste en 18 passages. Et chaque course se compose de 1000 périodes. Un agent est autorisé à effectuer une action à chaque période de chaque passage de la 'Simulation'. Mais la table Occurrence ne stocke aucun détail sur les agents qui effectuent les actions. La table Occurrence peut stocker des données liées à plusieurs 'Simulations'. SELECT2: Cette requête renvoie simplement les détails des actions effectuées dans chaque période de chaque exécution d'une «Simulation» avec les détails de tous les participants de la «Simulation» comme leurs identifiants Participant respectifs. La table OccurrenceParticipant stocke les enregistrements pour chaque entité participante de la simulation et comprend les agents, les banques de connaissances, les éléments de connaissances, etc.

SELECT3: Cette requête renvoie uniquement les enregistrements de la pseudo-table ij3 dus aux agents et aux éléments de connaissances . Tous les enregistrements de ij3 concernant les éléments de connaissances seront filtrés. SELECT4: Cette requête attache le champ 'Description' à chaque enregistrement de 'InitialParticipant'. Veuillez noter que la colonne 'Description' est une colonne de sortie de la requête entière. Le tableau InitialParticipant contient un enregistrement pour chaque agent et chaque élément de connaissance qui est initialement attribué à la « Simulation »

SELECT5: Cette requête finale renvoie tous les enregistrements de la table pseudo ij2 pour laquelle la RoleType de l'entité participante (qui peut soit être un agent ou un élément de connaissance) est 49 ou 52.

+3

pourquoi ne pas simplement éditer votre question au lieu de cette 'réponse '? –

2

J'ai reformatage votre code (en utilisant mon home-brew sql formatter) pour faire en sorte qu'il soit plus facile pour les autres de lire.

Reformatted Requête:

SELECT 
    ij1.Description, 
    ij2.occSimulationID, 
    ij2.occSimRunID, 
    ij2.Period, 
    Count(ij2.occpParticipantID) AS CountOfParticipantID 

FROM (

    SELECT 
     ip.ParticipantGroupID AS ipParticipantGroupID, 
     ip.ParticipantID AS ipParticipantID, 
     ip.ParticipantTypeID, 
     pg.ParticipantGroupID AS pgParticipantGroupID, 
     pg.ParticipantGroupTypeID, 
     pg.Description 

    FROM ParticipantGroup AS pg 

    INNER JOIN InitialParticipant AS ip 
      ON pg.ParticipantGroupID = ip.ParticipantGroupID 

) AS ij1 

INNER JOIN (

    SELECT 
     tpa.participantID AS tpaParticipantID, 
     ij3.* 

    FROM tmpPartArgs AS tpa 

    INNER JOIN (

     SELECT 
      ij4.*, 
      occp.SimulationID AS occpSimulationID, 
      occp.SimRunID AS occpSimRunID, 
      occp.OccurrenceID AS occpOccurrenceID, 
      occp.ParticipantID AS occpParticipantID, 
      occp.RoleTypeID 

     FROM (

      SELECT 
       tsa.SimulationID AS tsaSimulationID, 
       tsa.SimRunID AS tsaSimRunID, 
       occ.SimulationID AS occSimulationID, 
       occ.SimRunID AS occSimRunID, 
       occ.OccurrenceID AS occOccurrenceID, 
       occ.OccurrenceTypeID, 
       occ.Period, 
       occ.HasSucceeded 

      FROM tmpSimArgs AS tsa 

      INNER JOIN Occurrence AS occ 
        ON (tsa.SimRunID = occ.SimRunID) 
        AND (tsa.SimulationID = occ.SimulationID) 

     ) AS ij4 

     INNER JOIN OccurrenceParticipant AS occp 
       ON (occOccurrenceID = occpOccurrenceID) 
       AND (occSimRunID = occpSimRunID) 
       AND (occSimulationID = occpSimulationID) 

    ) AS ij3 
     ON tpa.participantID = ij3.occpParticipantID 

) AS ij2 
    ON ij1.ipParticipantID = ij2.occpParticipantID 

WHERE (

    (

     (ij2.RoleTypeID) = 52 
     OR 
     (ij2.RoleTypeID) = 49 

    ) 

) 
    AND ij2.HasSucceeded = 1 

GROUP BY 
    ij1.Description, 
    ij2.occSimulationID, 
    ij2.occSimRunID, 
    ij2.Period; 

Comme par JohnFx (ci-dessus), je suis confus par les vues dérivées. Je pense qu'il n'y en a pas vraiment besoin, d'autant plus que ce sont toutes des jointures internes. Donc, ci-dessous j'ai essayé de réduire la complexité. S'il vous plaît examiner et tester la performance. J'ai dû faire une jointure croisée avec tmpSimArgs puisqu'il est seulement joint à Occurence - je suppose que c'est le comportement désiré.

SELECT 
    pg.Description, 
    occ.SimulationID, 
    occ.SimRunID, 
    occ.Period, 
    COUNT(occp.ParticipantID) AS CountOfParticipantID 

FROM ParticipantGroup AS pg 

INNER JOIN InitialParticipant AS ip 
     ON pg.ParticipantGroupID = ip.ParticipantGroupID 

CROSS JOIN tmpSimArgs AS tsa 

INNER JOIN Occurrence AS occ 
     ON tsa.SimRunID = occ.SimRunID 
     AND tsa.SimulationID = occ.SimulationID 

INNER JOIN OccurrenceParticipant AS occp 
     ON occ.OccurrenceID = occp.OccurrenceID 
     AND occ.SimRunID = occp.SimRunID 
     AND occ.SimulationID = occp.SimulationID 

INNER JOIN tmpPartArgs AS tpa 
     ON tpa.participantID = occp.ParticipantID 

WHERE occ.HasSucceeded = 1 
    AND (occp.RoleTypeID = 52 OR occp.RoleTypeID = 49) 

GROUP BY 
    pg.Description, 
    occ.SimulationID, 
    occ.SimRunID, 
    occ.Period; 
0

Je suggère de déplacer le filtrage ij2.RoleTypeID de la requête la plus externe à ij3, Emploient au lieu de OR et déplacer la requête HasSucceeded à ij4.

Questions connexes