2009-05-05 7 views
11

J'ai une requête SQL qui ressemble à ceci:Comment puis-je accélérer row_number dans Oracle?

SELECT * FROM(
    SELECT 
     ..., 
     row_number() OVER(ORDER BY ID) rn 
    FROM 
     ... 
) WHERE rn between :start and :end 

Essentiellement, c'est la partie ORDER BY qui est ralentir les choses. Si je devais l'enlever, le coût EXPLAIN diminue d'un ordre de grandeur (plus de 1000x). J'ai essayé ceci:

SELECT 
    ... 
FROM 
    ... 
WHERE 
    rownum between :start and :end 

Mais cela ne donne pas des résultats corrects. Y a-t-il un moyen facile d'accélérer cela? Ou devrais-je passer plus de temps avec l'outil EXPLAIN?

Répondre

12

ROW_NUMBER est très inefficace dans Oracle.

Voir l'article dans mon blog pour plus de détails de performance:

Pour votre requête spécifique, je vous recommande de le remplacer par ROWNUM et assurez-vous que l'indice est utilisé :

SELECT * 
FROM (
     SELECT /*+ INDEX_ASC(t index_on_column) NOPARALLEL_INDEX(t index_on_column) */ 
       t.*, ROWNUM AS rn 
     FROM table t 
     ORDER BY 
       column 
     ) 
WHERE rn >= :start 
     AND rownum <= :end - :start + 1 

Cette requête utilisera COUNT STOPKEY

Également soit assurez-vous que column n'est pas NULL, ou ajouter WHERE column IS NOT NULL condition.

Sinon, l'index ne peut pas être utilisé pour récupérer toutes les valeurs.

Notez que vous ne pouvez pas utiliser ROWNUM BETWEEN :start and :end sans sous-requête.

ROWNUM est toujours affecté dernière et vérifié dernière, que son chemin ROWNUM « s viennent toujours pour sans lacunes.

Si vous utilisez ROWNUM BETWEEN 10 and 20, la première ligne qui satisfait toutes les autres conditions deviendra candidate au renvoi, affectée temporairement à ROWNUM = 1 et échouera au test ROWNUM BETWEEN 10 AND 20.

Ensuite, la ligne suivante sera candidate, affectée de ROWNUM = 1 et échouera, etc., donc, finalement, aucune ligne ne sera renvoyée du tout.

Cela devrait être contourné en mettant ROWNUM dans la sous-requête.

+0

Fonctionne comme un Cependant, les conseils de l'optimiseur ne semblaient pas faire une différence appréciable: –

+3

Cela signifiait que CBO était assez intelligent pour récupérer les index, mais que c'était ROWNUM plutôt que ROW_NUMBER qui importait ici. – Quassnoi

+0

Mais je laisserais toujours les conseils ou créer un PLAN, juste au cas où le CBO changera d'avis :) – Quassnoi

1

Votre colonne ORDER BY est-elle indexée? Si ce n'est pas un bon endroit pour commencer.

+0

En fait, ce n'était pas le cas. Mais le changer pour une ligne indexée n'aide pas. Merci d'avoir fait la suggestion évidente si. :-) –

+1

Un index ne ferait qu'améliorer ORDER BY si le chemin d'accès pouvait utiliser cet index (c'est-à-dire que vous recherchiez une plage d'ID). –

0

Passez plus de temps avec l'outil EXPLAIN PLAN. Si vous voyez un SCAN TABLE, vous devez modifier votre requête.

Votre requête a peu de sens pour moi. Interroger sur un ROWID semble demander des ennuis. Il n'y a pas d'informations relationnelles dans cette requête. Est-ce la vraie question avec laquelle vous avez des problèmes ou un exemple que vous avez créé pour illustrer votre problème?

+0

C'est la pagination. Et c'est essentiellement ce que la requête fait au moins avec la pagination. Je viens de retirer le reste de la requête (principalement parce que c'est non trivial). Toutes les ellipses sont là où j'ai coupé des choses pour la brièveté. –

4

Cela ressemble à une requête de pagination pour moi.

De cet article AskTom (environ 90% en bas de la page):

You need to order by something unique for these pagination queries, so that ROW_NUMBER is assigned deterministically to the rows each and every time.

également vos requêtes sont pas près de la même façon je ne suis pas sûr de ce que l'avantage de comparer les coûts d'un à l'autre est.

+1

En fait, cet article m'a aidé à écrire la requête. Je n'ai pas remarqué la partie sur la commande par des identifiants uniques cependant. De plus, il y a un indice d'optimiseur de requêtes que j'ai raté. Je vais essayer au travail demain! –

+0

;) a pensé que cela semblait familier. first_rows peut être génial avec des requêtes de pagination. – David

+0

Avec le conseil de Quassnoi, ma requête a été presque constante! J'aimerais pouvoir choisir deux réponses. :-( –

1

Une partie du problème est de savoir quelle est la taille du début et de la fin et où ils vivent. Supposons que vous ayez un million de lignes dans la table, et que vous vouliez les lignes 567,890 à 567,900, alors vous allez devoir vivre avec le fait qu'il va falloir passer par toute la table, trier à peu près tout cela par id et déterminez quelles rangées tombent dans cette plage.

Bref, c'est beaucoup de travail, c'est pourquoi l'optimiseur lui donne un coût élevé.

Ce n'est pas aussi quelque chose qu'un index peut aider avec beaucoup. Un index donnerait l'ordre, mais au mieux, cela vous donne un endroit pour commencer et ensuite vous continuez à lire jusqu'à ce que vous arriviez à la 567 900e entrée.

Si vous montrez à votre utilisateur final 10 éléments à la fois, cela peut valoir la peine de saisir le top 100 de la base de données, puis de faire en sorte que l'application divise ce nombre en dix morceaux.

+0

Cela semble approprié.Je tire vraiment environ 15 000 enregistrements sur ~ 2 millions d'enregistrements.Nous sommes limités sur le temps qu'une requête peut prendre, et en tirant tous les enregistrements de 15k à la fois entraînait des délais d'attente. Je pense que cela signifie simplement que je vais devoir passer par le cauchemar bureaucratique de la demande d'un délai plus long –

+0

J'espère que vous n'envoyez pas 15 000 lignes à l'utilisateur! –

Questions connexes