2009-06-08 10 views
1

J'ai une table sqlite actions qui ressemble à ceci:optimisation des requêtes SQLite

uuid varchar (36) 
actiondate int 
username varchar (16) 
mood int 
bonus int 
status varchar (80) 
... bunch of other similar fields (all short varchar or int fields) 

Cette conception semble être suffisamment performant pour la plupart des types de requêtes, mais les luttes un peu avec un scénario particulier, où J'ai besoin d'obtenir des données sur la dernière action effectuée par chaque utilisateur à une date donnée.

J'espérais pouvoir faire quelque chose comme ceci:

SELECT status, actiondate 
FROM actions WHERE actiondate < 20061231 
GROUP BY username 
ORDER BY actiondate DESC 

Cependant, l'agrégation ne se fait pas par rapport à la clause de l'ordre, la clause de commande détermine simplement quel ordre les résultats sont retournés dans, ce qui a du sens.

Alors, j'ai ceci:

SELECT actiondate, status FROM actions 
WHERE actiondate < 20061231 and 
uuid = (SELECT uuid from actions as alt 
     WHERE alt.username = actions.username 
     ORDER BY actiondate DESC LIMIT 1) 

Y at-il une meilleure façon de faire ce genre de requête? Une meilleure disposition de la table? Actuellement, ce type de requête prend ~ 400ms sur ma boîte de développement, et ce serait bien si je pouvais raser 100ms environ (mon temps cible est en fait de 100ms, mais je suis sceptique quant à savoir si c'est gérable). J'ai évidemment des index sur le nom d'utilisateur et la date (j'en ai plusieurs: un qui correspond assez bien à la requête lente: un sur le nom d'utilisateur, un sur la date ASC, un sur la date DESC et un sur uuid). FWIW, la table action est susceptible d'avoir entre 100 et 30 000 lignes.

Répondre

1

Votre index doit couvrir toutes les colonnes utilisées dans la requête pour une performance maximale.

Je ne suis pas sûr de la performance de la requête imbriquée dans ce cas. Je préférerais rejoindre une sous-requête si le plan d'exécution ne montre pas qu'il est en train de le convertir en une bonne jointure imbriquée.

Pour quelque chose comme ça, je pourrais éviter le UUID si possible, et sinon, je veillerai à ce que cela augmente, vous pouvez donc écrire:

SELECT actiondate 
    ,status 
FROM actions 
INNER JOIN (
    SELECT username 
     ,MAX(uuid) as last_uuid from actions 
    WHERE actiondate < 20061231 
    GROUP BY username 
) AS last_occur 
    ON last_occur.username = actions.username 
    AND last_occur.last_uuid = actions.uuid 
WHERE actiondate < 20061231 

Je pense que cela devrait bien fonctionner avec un indice sur le nom d'utilisateur ASC, uuid DESC, INCLUDE (actiondate) et et index sur actiondate DESC, nom d'utilisateur ASC, INCLUDE (statut), mais regardez évidemment le plan de requête. Sans les uuids croissantes, vous aurez besoin d'une sorte de règle pour vous assurer que vous sélectionnez la dernière action pour une personne, puisque sauf nom d'utilisateur, actiondate est unique, il n'y a rien dans votre ORDER BY actiondate DESC limite 1 pour assurer vous choisissez la rangée correcte chaque fois. Si le nom d'utilisateur, actiondate est unique, vous pouvez utiliser les éléments suivants:

SELECT actiondate 
    ,status 
FROM actions 
INNER JOIN (
    SELECT username 
     ,MAX(actiondate) as last_actiondate from actions 
    WHERE actiondate < 20061231 
    GROUP BY username 
) AS last_occur 
    ON last_occur.username = actions.username 
    AND last_occur.last_actiondate = actions.actiondate 
WHERE actiondate < 20061231 

Si ce n'est pas unique, il fonctionne toujours, mais vous obtiendrez de multiples actions pour une personne sur leur dernière actiondate. Les index recommandés seraient également différents dans ce cas (et mieux), car le grand uuid n'est pas nécessaire.

+0

Avec ces index, la durée de la requête est réduite à environ 273 ms, mais la contrainte sur l'augmentation des uuids n'est pas simple. Pensera à cette solution - merci! –

+0

Mettez à jour la réponse pour inclure une discussion sur la distinction entre le «dernier enregistrement» et l'utilisateur. –

+0

Merci - temps de requête jusqu'à environ 90ms avec vos modifications! J'aurais pu faire quelques changements qui pourraient ralentir un peu pour supprimer les «actions multiples pour une personne lors de leur dernière actiondate», mais cela semble vraiment prometteur. –

2

Correctness avant la vitesse - votre requête:

SELECT actiondate, status FROM actions 
WHERE actiondate < 20061231 and 
uuid = (SELECT uuid from actions as alt 
     WHERE alt.username = actions.username 
     ORDER BY actiondate DESC LIMIT 1) 

ne pas effectuer la tâche que vous décrivez - l'intérieur de sélection peut renvoyer un uuid pour une action qui est plus tard que 2.061.231, la sélection externe donnera Aucun résultat pour ce nom d'utilisateur. Je pense que vous pouvez le corriger en déplaçant le contrôle WHERE sur actiondate en tant que AND dans la sélection imbriquée. (Je doute que cela accélère les choses, mais au moins cela devrait rendre le comportement correct - laissez-nous savoir comment cela affecte la vitesse!).

+0

Oui, j'ai remarqué cela et l'ai corrigé dans ma suggestion. –

+0

Merci, cela corrige les résultats et le rend une fraction plus rapide (temps moyen jusqu'à ~ 325ms) –

+0

Hmmm ... En fait, sur une enquête plus approfondie, cela a changé le temps moyen de vitesse sur 10 requêtes de ~ 390ms à ~ 387ms . Merci d'avoir signalé l'erreur. –

Questions connexes