Doit-on jouer mille fois une requête ou une fois mille requêtes ?

development database
database

Contexte

Il s’agit d’un cas classique de développement web : nous avons besoin de paginer les résultats d’une requête SQL.

À partir d’une requête de recherche (sur une base MySQL), nous voulons :

Stratégies testées

Utiliser deux requêtes

Utiliser un curseur

Comparaison des stratégies

Quelle est alors la stratégie la plus rapide ?

execution de la requête

En mesurant le temps d’exécution d’une requête REST de recherche, via des contrôlleurs Spring WebFlux sur une table de plus de cinq cent mille lignes, on obtient les résultats suivants :

Temps des deux requêtes (ms) Temps du curseur (ms)
310 2218
19 2077
21 2057
19 2386
18 2085
18 1977
19 2000
17 1988
Moyenne 55,13 Moyenne 2098,50


des résultats étonnants

Le curseur s’avère plus lent que l’exécution de deux requêtes en suivant.

Cela s’explique, car le curseur charge les données ligne par ligne, alors que les requêtes SQL les chargent par ensemble de lignes.

Conclusion

Nous sommes partis sur l’exécution de deux requêtes (select et count), car il s’agit de la méthode la plus performante.

On préfère exécuter mille fois une requête.

Annexe : Code utilisé pour les tests

La base de données est accédée en Java via la librairie JOOQ.

Utiliser deux requêtes

final int offset = 10;
final int limit = 5;

final List<Record> content = dslContext.selectFrom(Tables.USER)
        .where(Tables.USER.MAIL.like("%@martoni.com"))
        .addOffset(offset)
        .addLimit(limit)
        .fetch();

final int totalElements = dslContext.fetchCount(
        dslContext.selectFrom(Tables.USER)
            .where(Tables.USER.MAIL.like("%@martoni.com"))
);
// (...)

Utiliser un curseur

final int baseIndex = 9;
final int nbElements = 5;

final ResultQuery<Record> query = dslContext.selectFrom(Tables.USER)
        .where(Tables.USER.MAIL.like("%@martoni.com"));

try (Cursor<Record> cursor = selectQuery
        .resultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE)
        .resultSetConcurrency(ResultSet.CONCUR_READ_ONLY)
        .fetchLazy()){

    // Page content
    final List<Record> content = new ArrayList<>();
    final ResultSet resultSet = cursor.resultSet();
    resultSet.absolute(baseIndex);
    int i = 0;
    while (cursor.hasNext() && i < nbElements) {
        content.add(cursor.fetchNext());
        ++i;
    }

    // Total number of elements
    resultSet.last();
    final int totalElements = resultSet.getRow();
    // (...)
}