Question

J'essaie de construire une infrastructure pour exécuter rapidement des régressions à la demande, en tirant les demandes Apache d'une base de données qui contient toute activité historique sur nos serveurs. Pour améliorer la couverture en s'assurant que nous régressons toujours les demandes de nos petits clients, je voudrais assurer une distribution des demandes en récupérant au plus N (pour cette question, disons 10) demandes pour chaque client.

J'ai trouvé un certain nombre de questions similaires répondues ici, dont la plus proche semblait être Requête SQL pour retourner les n lignes des N par identifiant sur une gamme d'ID, mais les réponses étaient pour la plupart des solutions d'agnostiques de performance que j'avais déjà essayées. Par exemple, la fonction analytique Row_number () nous offre exactement les données que nous recherchons:

SELECT
    *
FROM
    (
    SELECT
        dailylogdata.*,
        row_number() over (partition by dailylogdata.contextid order by occurrencedate) rn
    FROM
        dailylogdata
    WHERE
        shorturl in (?)
    )
WHERE
    rn <= 10;

Cependant, étant donné que ce tableau contient des millions d'entrées pour une journée donnée et cette approche nécessite la lecture de toutes les lignes de l'indice qui correspondent à nos critères de sélection afin d'appliquer la fonction analytique ROW_NUMBER, les performances sont terribles. Nous finissons par sélectionner près d'un million de lignes, seulement pour jeter la grande majorité d'entre eux parce que leur row_number a dépassé 10. Statistiques de l'exécution de la requête ci-dessus:

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|| Id  | Operation                            | Name                    | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  | Writes |  OMem |  1Mem | Used-Mem | Used-Tmp||
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
||   0 | SELECT STATEMENT                     |                         |      1 |        |  12222 |00:09:08.94 |     895K|    584K|    301 |       |       |          |         ||
||*  1 |  VIEW                                |                         |      1 |   4427K|  12222 |00:09:08.94 |     895K|    584K|    301 |       |       |          |         ||
||*  2 |   WINDOW SORT PUSHED RANK            |                         |      1 |   4427K|  13536 |00:09:08.94 |     895K|    584K|    301 |  2709K|   743K|   97M (1)|    4096 ||
||   3 |    PARTITION RANGE SINGLE            |                         |      1 |   4427K|    932K|00:22:27.90 |     895K|    584K|      0 |       |       |          |         ||
||   4 |     TABLE ACCESS BY LOCAL INDEX ROWID| DAILYLOGDATA            |      1 |   4427K|    932K|00:22:27.61 |     895K|    584K|      0 |       |       |          |         ||
||*  5 |      INDEX RANGE SCAN                | DAILYLOGDATA_URLCONTEXT |      1 |  17345 |    932K|00:00:00.75 |    1448 |      0 |      0 |       |       |          |         ||
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|                                                                                                                                                                                 |
|Predicate Information (identified by operation id):                                                                                                                              |
|---------------------------------------------------                                                                                                                              |
|                                                                                                                                                                                 |
|   1 - filter("RN"<=:SYS_B_2)                                                                                                                                                    |
|   2 - filter(ROW_NUMBER() OVER ( PARTITION BY "DAILYLOGDATA"."CONTEXTID" ORDER BY "OCCURRENCEDATE")<=:SYS_B_2)                                                                  |
|   5 - access("SHORTURL"=:P1)                                                                                                                                                    |
|                                                                                                                                                                                 |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

Cependant, si à la place, nous interrogeons uniquement les 10 premiers résultats pour un contextide spécifique, nous pouvons exécuter cela radicalement plus rapidement:

SELECT
    *
FROM
    (
    SELECT
        dailylogdata.*
    FROM
        dailylogdata
    WHERE
        shorturl in (?)
        and contextid = ?
    )
WHERE
    rownum <= 10;

Statistiques de l'exécution de cette requête:

|-------------------------------------------------------------------------------------------------------------------------|
|| Id  | Operation                           | Name                    | Starts | E-Rows | A-Rows |   A-Time   | Buffers ||
|-------------------------------------------------------------------------------------------------------------------------|
||   0 | SELECT STATEMENT                    |                         |      1 |        |     10 |00:00:00.01 |      14 ||
||*  1 |  COUNT STOPKEY                      |                         |      1 |        |     10 |00:00:00.01 |      14 ||
||   2 |   PARTITION RANGE SINGLE            |                         |      1 |     10 |     10 |00:00:00.01 |      14 ||
||   3 |    TABLE ACCESS BY LOCAL INDEX ROWID| DAILYLOGDATA            |      1 |     10 |     10 |00:00:00.01 |      14 ||
||*  4 |     INDEX RANGE SCAN                | DAILYLOGDATA_URLCONTEXT |      1 |      1 |     10 |00:00:00.01 |       5 ||
|-------------------------------------------------------------------------------------------------------------------------|
|                                                                                                                         |
|Predicate Information (identified by operation id):                                                                      |
|---------------------------------------------------                                                                      |
|                                                                                                                         |
|   1 - filter(ROWNUM<=10)                                                                                                |
|   4 - access("SHORTURL"=:P1 AND "CONTEXTID"=TO_NUMBER(:P2))                                                             |
|                                                                                                                         |
+-------------------------------------------------------------------------------------------------------------------------+

Dans ce cas, Oracle est suffisamment intelligent pour arrêter de récupérer les données après avoir obtenu 10 résultats. je pourrait Rassemblez un ensemble complet de contextides et générez programmatiquement une requête composée d'une instance de cette requête pour chaque contextide et union all L'ensemble des gâchis ensemble, mais étant donné le nombre de contextides, nous pourrions rencontrer une limitation interne d'oracle, et même sinon, cette approche recommence de Kludge.

Quelqu'un connaît-il une approche qui maintient la simplicité de la première requête, tout en conservant des performances proportionnelles à la deuxième requête? Notez également que je ne me soucie pas vraiment de récupérer un ensemble stable de lignes; Tant qu'ils satisfont à mes critères, ils vont bien aux fins d'une régression.

Éditer: La suggestion d'Adam Musch a fait l'affaire. J'apprends les résultats des performances avec ses changements ici car je ne peux pas les intégrer à une réponse de commentaire à sa réponse. J'utilise également un ensemble de données plus grand pour tester cette fois, voici les statistiques (mises en cache) de mon approche ROW_NUMBER d'origine pour la comparaison:

|-------------------------------------------------------------------------------------------------------------------------------------------------|
|| Id  | Operation                     | Name              | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |  OMem |  1Mem | Used-Mem ||
|-------------------------------------------------------------------------------------------------------------------------------------------------|
||   0 | SELECT STATEMENT              |                   |      1 |        |  12624 |00:00:22.34 |    1186K|    931K|       |       |          ||
||*  1 |  VIEW                         |                   |      1 |   1163K|  12624 |00:00:22.34 |    1186K|    931K|       |       |          ||
||*  2 |   WINDOW NOSORT               |                   |      1 |   1163K|   1213K|00:00:21.82 |    1186K|    931K|  3036M|    17M|          ||
||   3 |    TABLE ACCESS BY INDEX ROWID| TWTEST            |      1 |   1163K|   1213K|00:00:20.41 |    1186K|    931K|       |       |          ||
||*  4 |     INDEX RANGE SCAN          | TWTEST_URLCONTEXT |      1 |   1163K|   1213K|00:00:00.81 |    8568 |      0 |       |       |          ||
|-------------------------------------------------------------------------------------------------------------------------------------------------|
|                                                                                                                                                 |
|Predicate Information (identified by operation id):                                                                                              |
|---------------------------------------------------                                                                                              |
|                                                                                                                                                 |
|   1 - filter("RN"<=10)                                                                                                                          |
|   2 - filter(ROW_NUMBER() OVER ( PARTITION BY "CONTEXTID" ORDER BY  NULL )<=10)                                                                 |
|   4 - access("SHORTURL"=:P1)                                                                                                                    |
+-------------------------------------------------------------------------------------------------------------------------------------------------+

J'ai pris la liberté de faire bouillir un peu la suggestion d'Adam; Voici la requête modifiée ...

select
    *
from
    twtest
where
    rowid in (
    select
            rowid
    from (
            select
                    rowid,
                    shorturl,
                    row_number() over (partition by shorturl, contextid
                                                      order by null) rn
            from
                    twtest
    )
    where rn <= 10
    and shorturl in (?)
);

... et statistiques de son évaluation (mise en cache):

|--------------------------------------------------------------------------------------------------------------------------------------|
|| Id  | Operation                   | Name              | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem ||
|--------------------------------------------------------------------------------------------------------------------------------------|
||   0 | SELECT STATEMENT            |                   |      1 |        |  12624 |00:00:01.33 |   19391 |       |       |          ||
||   1 |  NESTED LOOPS               |                   |      1 |      1 |  12624 |00:00:01.33 |   19391 |       |       |          ||
||   2 |   VIEW                      | VW_NSO_1          |      1 |   1163K|  12624 |00:00:01.27 |    6770 |       |       |          ||
||   3 |    HASH UNIQUE              |                   |      1 |      1 |  12624 |00:00:01.27 |    6770 |  1377K|  1377K| 5065K (0)||
||*  4 |     VIEW                    |                   |      1 |   1163K|  12624 |00:00:01.25 |    6770 |       |       |          ||
||*  5 |      WINDOW NOSORT          |                   |      1 |   1163K|   1213K|00:00:01.09 |    6770 |   283M|  5598K|          ||
||*  6 |       INDEX RANGE SCAN      | TWTEST_URLCONTEXT |      1 |   1163K|   1213K|00:00:00.40 |    6770 |       |       |          ||
||   7 |   TABLE ACCESS BY USER ROWID| TWTEST            |  12624 |      1 |  12624 |00:00:00.04 |   12621 |       |       |          ||
|--------------------------------------------------------------------------------------------------------------------------------------|
|                                                                                                                                      |
|Predicate Information (identified by operation id):                                                                                   |
|---------------------------------------------------                                                                                   |
|                                                                                                                                      |
|   4 - filter("RN"<=10)                                                                                                               |
|   5 - filter(ROW_NUMBER() OVER ( PARTITION BY "SHORTURL","CONTEXTID" ORDER BY NULL NULL )<=10)                                       |
|   6 - access("SHORTURL"=:P1)                                                                                                         |
|                                                                                                                                      |
|Note                                                                                                                                  |
|-----                                                                                                                                 |
|   - dynamic sampling used for this statement (level=2)                                                                               |
|                                                                                                                                      |
+--------------------------------------------------------------------------------------------------------------------------------------+

Comme annoncé, nous n'accordes que la table DailylogData pour les lignes entièrement filtrées. Je crains que ça apparaît Pour toujours faire une analyse complète de l'index URLContext en fonction du nombre de lignes qu'il prétend sélectionner (1213k), mais étant donné qu'il utilise uniquement en utilisant 6770 tampons (ce nombre reste constant même si j'augmente le nombre de contextes spécifiques au contexte Résultats) Cela peut être trompeur.

Était-ce utile?

La solution

C'est une sorte de solution janky, mais c'est semble Pour faire ce que vous voulez: raccourcir la numérisation d'index dès que possible, et ne pas lire les données tant qu'elle n'est pas qualifiée à la fois en filtrant le conditionnement et les critères de requête TOP-N.

Notez qu'il a été testé avec un shorturl = condition, pas un shorturl IN condition.

with rowid_list as
(select rowid
   from (select *
           from (select rowid,
                        row_number() over (partition by shorturl, contextid
                                           order by null) rn
                   from dailylogdata
                )
          where rn <= 10
        )
  where shorturl = ? 
)
select * 
  from dailylogdata
 where rowid in (select rowid from rowid_list)

La with La clause saisit les 10 premiers rowids filtrant une fenêtre Nosorte pour chaque combinaison unique de shorturl et contextid qui répond à vos critères. Ensuite, il boucle sur cet ensemble de rowids, récupérant chacun par Rowid.

----------------------------------------------------------------------------------------------------
| Id  | Operation                   | Name                 | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |                      |     1 |   286 |  1536   (1)| 00:00:19 |
|   1 |  NESTED LOOPS               |                      |     1 |   286 |  1536   (1)| 00:00:19 |
|   2 |   VIEW                      | VW_NSO_1             |   136K|  1596K|   910   (1)| 00:00:11 |
|   3 |    HASH UNIQUE              |                      |     1 |  3326K|            |          |
|*  4 |     VIEW                    |                      |   136K|  3326K|   910   (1)| 00:00:11 |
|*  5 |      WINDOW NOSORT          |                      |   136K|  2794K|   910   (1)| 00:00:11 |
|*  6 |       INDEX RANGE SCAN      | TABLE_REDACTED_INDEX |   136K|  2794K|   910   (1)| 00:00:11 |
|   7 |   TABLE ACCESS BY USER ROWID| TABLE_REDACTED       |     1 |   274 |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   4 - filter("RN"<=10)
   5 - filter(ROW_NUMBER() OVER ( PARTITION BY "CLIENT_ID","SCE_ID" ORDER BY NULL NULL
              )<=10)
   6 - access("TABLE_REDACTED"."SHORTURL"=:b1)

Autres conseils

Cela semble être le genre qui prend tout le temps. Est occurrenceDate Votre index en cluster, et sinon, est-ce beaucoup plus rapide si vous passez à la commande par votre index cluster? C'est-à-dire s'il est regroupé par un ID séquentiel, alors commandez par cela.

La dernière fois que j'ai simplement mis en cache dernière les plus intéressantes dans une petite table. Avec ma distribution de données, il était moins cher de mettre à jour la table de cache sur chaque insert plutôt que de demander la table en vrac.

Je pense que vous devriez également vérifier d'autres façons / requêtes pour obtenir le même ensemble de résultats.


Self-JOIN / GROUP BY

SELECT
    d.*
  , COUNT(*) AS rn

FROM 
        dailylogdata AS d 
    LEFT OUTER JOIN
        dailylogdata AS d2 
            ON  d.contextid = d2.contextid 
            AND d.occurrencedate >= d2.occurrencedate) 
            AND d2.shorturl IN (?)

WHERE
    d.shorturl IN (?)

GROUP BY 
    d.* 

HAVING 
    COUNT(*) <= 10

Et un autre que je n'ai aucune idée si cela fonctionne correctement:

SELECT
    d.*
  , COUNT(*) AS rn

FROM 
        ( SELECT DISTINCT
              contextid
          FROM 
              dailylogdata 
          WHERE
              shorturl IN (?)
        ) AS dd 
    JOIN
        dailylogdata AS d
            ON  d.PK IN 
                ( SELECT
                      d10.PK
                  FROM
                      dailylogdata AS d10  
                  WHERE
                      d10.contextid = dd.contextid 
                    AND
                      d10.shorturl IN (?)
                    AND
                      rownum <= 10
                  ORDER BY 
                      d10.occurrencedate
                )
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top