web-dev-qa-db-fra.com

Que fait vraiment la méthode Statement.setFetchSize (nSize) dans le pilote JDBC SQL Server?

J'ai ce très gros tableau avec quelques millions d'enregistrements chaque jour et à la fin de chaque jour j'extrais tous les enregistrements de la veille. Je fais ça comme:

String SQL =  "select col1, col2, coln from mytable where timecol = yesterday";
Statement.executeQuery(SQL);

Le problème est que ce programme prend comme 2 Go de mémoire car il prend tous les résultats en mémoire puis le traite.

J'ai essayé de définir la Statement.setFetchSize(10) mais cela prend exactement la même mémoire du système d'exploitation, cela ne fait aucune différence. J'utilise pilote JDBC Microsoft SQL Server 2005 pour cela.

Existe-t-il un moyen de lire les résultats en petits morceaux comme le fait le pilote de base de données Oracle lorsque la requête est exécutée pour n'afficher que quelques lignes et que vous faites défiler vers le bas, d'autres résultats s'affichent?

51
Cofi

Dans JDBC, la méthode setFetchSize(int) est très importante pour les performances et la gestion de la mémoire au sein de la JVM car elle contrôle le nombre d'appels réseau de la JVM vers la base de données et, en conséquence, la quantité de RAM utilisé pour le traitement ResultSet.

De manière inhérente, si setFetchSize (10) est appelé et que le pilote l'ignore, il n'y a probablement que deux options:

  1. Essayez un autre pilote JDBC qui respectera l'indice de taille de récupération.
  2. Regardez les propriétés spécifiques au pilote sur la connexion (URL et/ou mappage de propriétés lors de la création de l'instance de connexion).

RESULT-SET est le nombre de lignes rassemblées sur la base de données en réponse à la requête. Le ROW-SET est le bloc de lignes qui sont extraites du RESULT-SET par appel de la JVM vers la base de données. Le nombre de ces appels et le résultat RAM requis pour le traitement dépend du paramètre fetch-size.

Donc, si le RESULT-SET a 100 lignes et la taille de récupération est 10, il y aura 10 appels réseau pour récupérer toutes les données, en utilisant environ 10 * {row-content-size} RAM = à tout moment.

La taille de récupération par défaut est 10, ce qui est plutôt petit. Dans le cas publié, il semblerait que le pilote ignore le paramètre de taille de récupération, récupérant toutes les données en un seul appel (grande RAM, appels réseau minimaux optimaux)).

Ce qui se passe sous ResultSet.next() est qu'il ne récupère pas réellement une ligne à la fois dans RESULT-SET. Il récupère cela à partir du ROW-SET (local) et récupère le ROW-SET suivant (de manière invisible) à partir du serveur lorsqu'il devient épuisé sur le client local.

Tout cela dépend du pilote car le paramètre est juste un `` indice '', mais dans la pratique, j'ai trouvé que cela fonctionnait pour de nombreux pilotes et bases de données (vérifié dans de nombreuses versions d'Oracle, DB2 et MySQL).

62
Darrell Teague

Le paramètre fetchSize est un indice pour le pilote JDBC quant au nombre de lignes à récupérer en une seule fois dans la base de données. Mais le conducteur est libre de l'ignorer et de faire ce qu'il juge bon. Certains pilotes, comme Oracle, récupèrent les lignes par blocs, vous pouvez donc lire des jeux de résultats très volumineux sans avoir besoin de beaucoup de mémoire. D'autres pilotes viennent de lire l'ensemble des résultats en une seule fois, et je suppose que c'est ce que fait votre pilote.

Vous pouvez essayer de mettre à niveau votre pilote vers la version SQL Server 2008 (qui pourrait être meilleure) ou le pilote jTDS open-source.

25
skaffman

Vous devez vous assurer que la validation automatique sur la connexion est désactivée off, ou setFetchSize n'aura aucun effet.

dbConnection.setAutoCommit(false);

Edit: N'oubliez pas que lorsque j'ai utilisé ce correctif, il était spécifique à Postgres, mais j'espère qu'il fonctionnera toujours pour SQL Server.

14
jwaddell

Interface de déclaration Doc

RÉSUMÉ: void setFetchSize(int rows) Donne au pilote JDBC une indication du nombre de lignes à extraire de la base de données lorsque davantage de lignes sont nécessaires.

Lire cet ebook J2EE et au-delà By Art Taylor

4
adatapost

On dirait que mssql jdbc met en mémoire tampon l'ensemble des résultats pour vous. Vous pouvez ajouter un paramètre de chaîne de connexion disant selectMode = cursor ou responseBuffering = adaptive. Si vous utilisez la version 2.0+ du pilote 2005 mssql jdbc, la mise en mémoire tampon des réponses devrait par défaut être adaptative.

http://msdn.Microsoft.com/en-us/library/bb879937.aspx

3
Ron

Il me semble que vous vraiment voulez limiter les lignes renvoyées dans votre requête et votre page à travers les résultats. Si oui, vous pouvez faire quelque chose comme:

select * from (select rownum myrow, a.* from TEST1 a )
where myrow between 5 and 10 ;

Il vous suffit de déterminer vos limites.

1
Brian Agnew

Essaye ça:

String SQL = "select col1, col2, coln from mytable where timecol = yesterday";

connection.setAutoCommit(false);
PreparedStatement stmt = connection.prepareStatement(SQL, SQLServerResultSet.TYPE_SS_SERVER_CURSOR_FORWARD_ONLY, SQLServerResultSet.CONCUR_READ_ONLY);
stmt.setFetchSize(2000);

stmt.set....

stmt.execute();
ResultSet rset = stmt.getResultSet();

while (rset.next()) {
    // ......
1
Sergio Sierra

J'ai eu exactement le même problème dans un projet. Le problème est que même si la taille de récupération peut être suffisamment petite, le JDBCTemplate lit tous les résultats de votre requête et les mappe dans une énorme liste qui pourrait faire exploser votre mémoire. J'ai fini par étendre NamedParameterJdbcTemplate pour créer une fonction qui renvoie un flux d'objet. Ce flux est basé sur l'ensemble de résultats normalement renvoyé par JDBC, mais extrait les données de l'ensemble de résultats uniquement lorsque le flux l'exige. Cela fonctionnera si vous ne conservez pas une référence de tous les objets crachés par ce flux. Je me suis beaucoup inspiré de la mise en œuvre de org.springframework.jdbc.core.JdbcTemplate # execute (org.springframework.jdbc.core.ConnectionCallback). La seule vraie différence est de savoir quoi faire avec le ResultSet. J'ai fini par écrire cette fonction pour conclure le ResultSet:

private <T> Stream<T> wrapIntoStream(ResultSet rs, RowMapper<T> mapper) {
    CustomSpliterator<T> spliterator = new CustomSpliterator<T>(rs, mapper, Long.MAX_VALUE, NON-NULL | IMMUTABLE | ORDERED);
    Stream<T> stream = StreamSupport.stream(spliterator, false);
    return stream;
}
private static class CustomSpliterator<T> extends Spliterators.AbstractSpliterator<T> {
    // won't put code for constructor or properties here
    // the idea is to pull for the ResultSet and set into the Stream
    @Override
    public boolean tryAdvance(Consumer<? super T> action) {
        try {
            // you can add some logic to close the stream/Resultset automatically
            if(rs.next()) {
                T mapped = mapper.mapRow(rs, rowNumber++);
                action.accept(mapped);
                return true;
            } else {
                return false;
            }
        } catch (SQLException) {
            // do something with this Exception
        }
    }
}

vous pouvez ajouter un peu de logique pour rendre ce flux "auto-fermable", sinon n'oubliez pas de le fermer lorsque vous avez terminé.

1
ChaudPain