PostgreSQL La base de donnees la plus sophistiquee au monde.

Forums PostgreSQL.fr

Le forum officiel de la communauté francophone de PostgreSQL

Vous n'êtes pas identifié(e).

#1 27/08/2013 16:46:37

thomasfr
Membre

utilisation de PQsetSingleRowMode

Bonjour,

J'aurais besoin d'un éclaircissement sur la fonction PQsetSingleRowMode.
Je l'utilise afin de lire des enregistrements dans une base après un appel à PQsendQueryPrepared.

Une fois l'enregistrement trouvé, j'aimerais débuter une nouvelle requête. La documentation indique que l'on doit appeler PQgetResult successivement jusqu'à ce que cette fonction retourne NULL avant de pouvoir envoyer une autre requête au serveur via PQsendQueryPrepared.

Ma question est : Comment éviter les appels successif à PGgetResult jusqu'à ce qu'il retourne NULL, peut-t-on annuler cette requête afin de ne pas avoir une boucle extrêmement longue du style :

res = PQgetResult(conn);
while (res!=NULL)
{
	PQclear(res);
	res = PQgetResult(conn);
}
PQclear(res);

J'ai essayé PQcancel, PQconsumeInput et PQflush sans succès.

Y-a-t-il un moyen d'annuler ce mode RowByRow au cours de la requête?

Cordialement,
Thomas

Hors ligne

#2 28/08/2013 23:04:57

gleu
Administrateur

Re : utilisation de PQsetSingleRowMode

Non, vous ne pouvez pas l'annuler à ma connaissance.

Hors ligne

#3 29/08/2013 11:28:01

thomasfr
Membre

Re : utilisation de PQsetSingleRowMode

Bonjour,

Loin de moi la volonté de critiquer... Mais c'est bien dommage !
Pour moi le rowbyrow mode a été implémenté pour décharger la mémoire vive du client en traitant un à un les enregistrements renvoyés par le serveur.

Une fois l'enregistrement trouvé, je n'ai aucune raison (dans mon cas) de continuer à itérer sur les enregistrements suivants.

Du coup si je comprends bien on n'utilise plus la mémoire vive mais on perds en temps d'exécution le temps d'itérer sur le reste des enregistrements?

Cordialement,
Thomas

Dernière modification par thomasfr (29/08/2013 11:31:37)

Hors ligne

#4 29/08/2013 22:45:35

gleu
Administrateur

Re : utilisation de PQsetSingleRowMode

De votre problématique, je ne vois que deux possibilités :

* vous cherchez un enregistrement particulier : dans ce cas, mettez une clause WHERE dans votre requête pour ne récupérer que les lignes qui vous intéressent
* vous cherchez à vous déplacer dans un ensemble de résultats pour n'en lire qu'une partie : dans ce cas, utilisez un curseur

Hors ligne

#5 30/08/2013 10:50:09

thomasfr
Membre

Re : utilisation de PQsetSingleRowMode

Bonjour,

Premièrement merci de répondre si rapidement et de façon si pertinente ! En effet j'avais commencé l'implémentation de mon ensemble de fonctions avec un curseur (Je cherche bien à me déplacer dans un ensemble de résultats). Chose que j'ai vite abandonnée pour des questions de performance.

Je pense qu'il est temps que j'explique un peu plus le contexte...


J'ai une application qui intéragit avec une base Oracle via une DLL (surcouche de l'API OCI). Je suis en train de migrer cette DLL vers PostgreSQL. La contrainte est triple :
* Garder les mêmes signatures de fonctions pour les deux DLLs (Utilisation transparente pour l'application)
* Avoir des performances semblables
* Avoir un comportement iso-fonctionnel.


J'ai notamment dans mon application trois fonctions importantes :
LitRecherche : en gros un select dans une requête préparée (avec stockage de la requête dans une structure particulière, si la requête suivante possède des paramètres semblables, -> on effectue seulement le binding)
LitSuivant : permet d'itérer sur les enregistrments
LitChamp : permet de lire la valeur d'un attribut particulier de l'enregistrement


Mon souci est de faire un PQsendQuery sur un deuxième LitRecherche (il me jette en me disant qu'une requête est déjà en cours) sans avoir "flusher" le resultat de la précédente.

La solution du curseur paraît en effet beaucoup plus adaptée d'un point de vue théorique, mais les performances ne sont malheureusmeent pas au rdv ! De plus l'utilisation des transactions avec les curseurs semble ralentir les traitements.

Pour information l'API OCI d'Oracle permet d'allouer un 'handle' sur une requête indépendamment de la connexion utilisée alors que l'API libpq via PQsendQuery (seul le mode asynchrone fonctionne avec RowByRow mode) récupere le résultat d'une requête avec pour seul paramètre le PGconn.

PS : Le bon point pour l'instant est que même avec cette longue boucle (cf. premier post), j'arrive à être compétitif face à Oracle. Mais ce n'est pas super propre et je ne connaît pas encore le comportement sur des tables ayant des millions d'enregistrements... tests en cours...

Cordialement,
Thomas

Hors ligne

#6 30/08/2013 20:09:27

gleu
Administrateur

Re : utilisation de PQsetSingleRowMode

Avec vos trois contraintes, permettez-moi de vous souhaiter bon courage. PostgreSQL n'est pas Oracle, et inversement. Penser qu'on peut intervertir l'un avec l'autre, et que rien ne change est bien naïf.

Concernant votre problème actuel, vous n'aurez pas d'autre choix que de passer par les curseurs. Il serait donc intéressant d'en savoir plus sur votre problème de performances.

Hors ligne

#7 06/09/2013 15:54:06

thomasfr
Membre

Re : utilisation de PQsetSingleRowMode

Bonjour
Avec quelques difficultés à cause de la gestion des transactions, j'ai finalement réimplémenté une version avec curseurs...
Je ne trouve pas beaucoup d'informations sur internet pour les deux questions suivantes :

1. Peut-on utiliser des curseurs génériques avec libpq.dll ?
Le code suivant fonctionne :

PQexec(conn, "BEGIN");
PQexec(conn, "DECLARE mycur CURSOR FOR [...]");
PQexec(conn, "END");

Mais celui-ci ne fonctionne pas :

PQexec(conn, "BEGIN");
PQexec(conn, "DECLARE mycur REFCURSOR");
PQexec(conn, "END");

2. Existe-t-il un équivalent au mode prefetch de la lib oci.dll pour libpq.dll ?

OCIAttrSet((dvoid *) requete->stmt, OCI_HTYPE_STMT, (dvoid *) &nb_ligne, (ub4) 0, OCI_ATTR_PREFETCH_ROWS,errhp)

Cordialement,
Thomas

Hors ligne

#8 11/09/2013 23:30:51

thomasfr
Membre

Re : utilisation de PQsetSingleRowMode

Bonsoir,

Personne pour répondre ?
Un autre problème...

Quand on execute un "DELETE FROM table1 WHERE CURRENT OF curTable;"
Pourquoi le curseur est désalloué?

Cordialement,
Thomas

Hors ligne

#9 12/09/2013 00:11:09

gleu
Administrateur

Re : utilisation de PQsetSingleRowMode

1. refcursor est un type de données. Je ne vois pas comment votre déclaration de curseur pourrait fonctionner ainsi. Le seul moyen de le faire est dans la partie DECLARE d'une procédure stockée en PL/pgsql.

2. Aucune idée de ce que fait OCIAttrSet...

3. J'ai testé de mon côté et il n'est pas désalloué. Avez-vous un jeu de tests qui montrerait vos dires ?

Hors ligne

#10 12/09/2013 09:43:12

thomasfr
Membre

Re : utilisation de PQsetSingleRowMode

Bonjour Guillaume,

Merci pour tes réponses.
1. Je ne comprends toujours pas pourquoi je peux déclarer un curseur paramétré mais pas un curseur générique.
2. La fonction Oracle ci-dessus permet de définir un mode de fonctionnement spéciale à la fonction (OCIStmtFetch()). Si celle-ci "fetch" les lignes une par une (FETCH NEXT), alors le nombre de communication entre le client et le serveur sera égal aux nombre de lignes. Le mode PREFECTH de Oracle permet de définir un nombre de ligne à fetché en mémoire pour limiter ce nombre de communication.
3. Autant pour moi, j'utilisais la combinaison BEGIN/DO QUERY/COMMIT/DO QUERY/COMMIT/DO OTHER QUERY/.../END. Sans les COMMIT je n'ai plus de désallocation du curseur. Par contre j'ai pas mal de problèmes de performances... Comment les communications avec le serveur évolue dans une transaction ?

Cordialement,
Thomas

Hors ligne

#11 12/09/2013 11:31:08

thomasfr
Membre

Re : utilisation de PQsetSingleRowMode

Concernant mon problème de performance... J'ai monitoré mon application et j'ai découvert ceci :

PQExec(conn, "DECLARE F60c3 CURSOR FOR SELECT OID, F60.* FROM F60 WHERE F60TYPE>$1 ORDER BY F60TYPE, F60N_PERS");

Ce select contient environ 3 000 000 d'enregistrements.
Si je fais un :

PQExec(conn, "FETCH FIRST FROM F60c3");

PQExec ne rend la main que 15 secondes plus tard...
Si quelqu'un a une explication je suis preneur smile

EDIT :
Je pense que je dois utilisé un LIMIT afin de limiter les résultats de la première exécution du fetch. (exemple LIMIT 50)
Pour passer aux 50 enregistrements suivant je devrais donc associer OFFSET et LIMIT. Le probleme c'est que je ne peux pas redeclarer mon curseur sans perdre le curseur...

Il me faudrait donc un retour de la part de PQExec qui me dise si je suis en fin de table ou seulement à la fin de l'exécution contraint par LIMIT.
Existe-t-il un tel retour?

Thomas.

Dernière modification par thomasfr (18/09/2013 09:23:33)

Hors ligne

#12 12/09/2013 23:06:55

gleu
Administrateur

Re : utilisation de PQsetSingleRowMode

1. Qu'est-ce que vous appelez un curseur générique et paramétré ?

2. À ma connaissance, cela n'existe pas avec PostgreSQL. Vous pouvez seulement indiquer à PostgreSQL que vous comptez récupérer un certain pourcentage de lignes (paramètre cursor_tuple_fraction), ce qui aide le planificateur à choisir entre un plan récupérant rapidement les premières lignes ou un plan récupérant rapidement la totalité.

3. Les messages serveur/client sont identiques avec ou sans transaction.

4. Le premier FETCH exécute la requête. Donc, suivant la requête, ça peut prendre du temps (notamment trier 3 millions d'enregistrements est long). Les FETCH NEXT suivants sont immédiats vu que le résultat est déjà connu. Et dans ce cas particulier, un index sur F60TYPE et F60N_PERS (dans cet ordre) permettrait d'avoir un résultat immédiat (en tout cas sur les dernières versions).

5. Si vous placez un LIMIT 50 dans la requête du DECLARE, vous devrez de nouveau faire un DECLARE pour les 50 suivants. Je ne vois pas bien le gain...

Hors ligne

#13 12/09/2013 23:26:33

thomasfr
Membre

Re : utilisation de PQsetSingleRowMode

Bonsoir,
4. Je vérifierai demain les indexs, ils sont normalement présents.

Pour le 5. Le gain est de pouvoir récupérer les 50 premières lignes et les afficher dans une IHM par exemple.

J'éditerai demain ce message pour répondre aux autres questions.

Merci beaucoup !!

Thomas

Hors ligne

#14 12/09/2013 23:34:33

gleu
Administrateur

Re : utilisation de PQsetSingleRowMode

Concernant les index, s'ils sont présents, avez-vous fait un VACUUM ANALYZE sur la table récemment ? et quelle version de PostgreSQL avez-vous ?

Hors ligne

#15 12/09/2013 23:35:57

thomasfr
Membre

Re : utilisation de PQsetSingleRowMode

Je ne connais pas VACUUM ANALYZE, la version est la 9.2

Hors ligne

#16 13/09/2013 11:53:22

gleu
Administrateur

Re : utilisation de PQsetSingleRowMode

Si vous êtes en 9.2, un VACUUM sur la table permettra de s'assurer qu'un Index Only Scan est possible. L'option ANALYZE permet de mettre à jour les statistiques sur les données, essentiel pour que le planificateur fasse bien son boulot.

Hors ligne

#17 18/09/2013 09:23:10

thomasfr
Membre

Re : utilisation de PQsetSingleRowMode

Bonjour,

Je reprends un peu le fil de la conversation, j'ai toujours des problèmes de performances. J'ai remarqué des différences notables entre Oracle et PostGre. Sur une table de plusieurs millions d'enregistrements, je remarque une différence notable entre les requêtes suivantes :
Oracle (via oci.dll, 8 sec, avec un forçage pour l'utilisation de l'index)

SELECT /*+ index_asc(F60 if60a) */  F60.*, ROWID FROM F60 WHERE f60type>' ' ORDER BY f60type, f60n_pers

PostGre (via libpq, 30sec)

BEGIN
DECLARE F60c1 CURSOR WITH HOLD FOR SELECT F60.*, OID FROM F60 WHERE f60type>' ' ORDER BY f60type, f60n_pers
FETCH 1 FROM F60c1
COMMIT

Dans la librairie oci.dll, on peut itérer sur un résultat via des fonctions spécifiques (OCIfetch), c'est pourquoi je n'utilise pas de curseur en Oracle.

VACUUM ANALYSE effectué sur toute la table.
Pour Postgre, la requête est exécutée lors de l'appel au premier FETCH, est-ce que le fait de passer par un curseur ralentit les performances ?
Est-ce que je dois me tourner vers la configuration des indexs? Optimiser la configuration de la base Postgre?

Ci-dessous la configuration de mes indexs :

Oracle

-INDEX-	-uniqueness-	-status-	-type-	-temporary-	-partitioned-	-join index-	-columns-
IF60A	NONUNIQUE	VALID		NORMAL	N		NO		NO		F60TYPE, F60N_PERS	
IF60B	NONUNIQUE	VALID		NORMAL	N		NO		NO		F60TYPE, F60ATOME	

PostGre

CREATE INDEX if60a
  ON f60
  USING btree
  (f60type COLLATE pg_catalog."default", f60n_pers COLLATE pg_catalog."default");

Cordialement,
Thomas

Dernière modification par thomasfr (18/09/2013 16:57:22)

Hors ligne

Pied de page des forums