ugrás a tartalomhoz

Aszinkron adatbázis lekérdezések

gerzson · 2005. Már. 2. (Sze), 23.59
Aszinkron adatbázis lekérdezések
A PHP újabb verziói támogatnak több adatbáziskezelő interfészén (mysqli, pgsql) eleddig kevéssé használt lehetőséget: az aszinkron adatbázis lekérdezéseket. A mostani, erősen kísérletező cikkben áttekintjük ennek a funkciónak a lényegét. Vigyázat, használni csak saját felelősségre!

Előkép

A mai webalkalmazások legtöbbje elképzelhetetlen adatbázisháttér nélkül, amelyekre a rengeteg információ rendszerezett tárolása és ellenőrzött módosítása miatt van szükség. Ezek az igények azonban olyan robosztus rendszereket, adatbázis kiszolgálókat "szülnek", amelyeknek a teljesítményét legtöbbször egyedi módon kell fokozni. A legszűkebb keresztmetszet általában az idő szokott lenni, és így az adatbázisok gyorsítására igen sok megoldás született. Ezeket általában kettő illetve három nagy csoportba sorolhatjuk.
  • A "rendszer szintű" erőforrás-hangolások nem érintik az általunk írt kódot, hanem az azt futtató rendszer komponensei között osztja el az erőforrásokat optimálisan, mint pl.: nagyobb osztott memóriahasználat, adatbázis fizikailag külön háttértárolón, stb.
  • Az adatbázis lekérdezések optimalizációja a kódunkon belül, amelynek alapvetően két fő csapásiránya mutatkozik: maguknak az SQL utasítások optimalizációja, illetve az interfészhívások észszerűsítése - módosítása. Az első esetben több lekérdezés egybeolvasztásáról lehet szó, vagy azonos eredményt adó alternatívák közül a leggyorsabb kiválasztásáról. A másik esetben azonban magát az SQL kéréseket változatlan formában hagyva magát a programunkat struktúráljuk át olyan módon, hogy az gyorsabban fusson.

A PHP függvények között kutatva találtam rá az aszinkron lekérdezések (asynchronous queries) lehetőségére, amit a PHP 4.2 verziótól felfelé PostgreSQL-hez, illetve PHP 5-től MySQL esetén is használhatunk a mysqli kiterjesztésen keresztül.

Az aszinkron működés

Miről is van szó, és hol lép fel az aszinkronitás? A hagyományos *_query() függvények "megvárják" a kiadott SQL utasítás végeredményét, ezzel szemben az aszinkron *_send_query() csak a parancs elküldésének idejéig "tartóztatja fel" a hívást kezdeményező szkript futását.

A vastag nyilak jelzik a blokkoló függvényhívásokat, míg a vékonyabbak a vezérlés megosztástást és átadását.

Miért jó ez? - elmélet

Az elmélet egyszerű. Az aszinkron működésből kifolyólag párhuzamosítani lehet az adatbázis-műveleteket más PHP-ból indított feladatok végrehajtásával. Az SQL parancs kiadása után a PHP újabb utasításokat végezhet el, majd elég az eredményt akkor lekérni, amikor már annak feldolgozása nem halasztható tovább. Ez utóbbi tipikusan az adatok megjelenítését jelenti a kiküldendő HTML-ben, de látunk példát másféle alkalmazásukra is.
Asztali alkalmazások körében már régóta elterjedt gyakorlat, hogy a felhasználói interaktivitás zavartalansága érdekében a hosszabb, nem megszakítható műveleteket külön programszálak végzik el a háttérben, így nem tűnik úgy, mintha a program nem válaszolna a felhasználó üzeneteire. PHP-ban nincs lehetőség bizonyos műveletek külön szálakon indítani, ennek ellenére itt is valami hasonlóról van szó.

Az előző megfogalmazásban a kulcsszó: a párhuzamosítás, amivel kapcsolatban fontos megemlíteni a következőket.
  • Az egész "hadművelet" nem ér semmit, ha nincs mit párhuzamosítani, sőt akár káros is lehet! Ha nincs olyan műveletsor, amit párhuzamosan végre tudna hajtani a PHP, amíg az adatbázis-kiszolgáló végez, akkor végeredményben a szkript így is - úgy is várakozni kényszerül.
  • Nem mellékes, hogy milyen feladatokat és hogyan "párhuzamosítunk". Ez alatt azt értem, hogy ha pl. több egymástól független SQL utasítást "szedünk csokorba", akkor érdemes előzetes felmérést végezni, hogy melyik kérés teljesítése mennyi ideig tart, és eszerint elrendezni azok indítását a programon belül.
  • A folyamatok párhuzamosításával nyert időnek arányban kell lennie az általa okozott többletterheléssel. Az aszinkron lekérdezések bevezetése a PHP források nem kis átalakításával jár, ami rögtön nem elhanyagolható plusz munkaórákat jelent. Emellett az is előfordulhat, hogy többet vesztünk a réven, mint amennyit nyerünk a vámon. Az átalakítás megváltoztatja a program futási paramétereit, mert több függvényhívást jelent már PHP szinten is, ami több interakciót jelent a felszín alatt.(Lásd a fenti ábrát!)

Megvalósítás

Elsőre nem különösebben bonyolult és nem sokban tér el az aszinkron lekérdezés programozása a hagyományostól:
<?php
$conn = pg_connect("dbname=asynctest") or die("nincs kapcsolat!");

// a frissen létrehozott kapcsolatnak használhatónak kell lennie!
assert ('!pg_connection_busy($conn)');

// SQL parancs küldése aszinkron végrehajtásra
pg_send_query($conn, "select * from test;") or die("keres nem sikerult!");

// ... egyéb teendők párhuzamos elvégzése ...

// várakozás az eredmény "elkészültére" + feldolgozás
for ( $res = pg_get_result($conn); $row = pg_fetch_object($res); )
{
	echo $row->id, " ", $row->name, "\n";
}
?>
  • A szokásos pg_query() (illetve mysqli_query()) függvények helyett pg_send_query()-t kell használni. A továbbiakban csak a PostgreSQL-hez tartozó függvényeket említem, mivel azok a most még jobban elterjedt PHP 4 szériában is jelen vannak, valamint további funkciókat is biztosítanak a MySQLi-hez képest.
  • A parancs elküldése előtt meg kell bizonyosodni arról, hogy felhasznált adatbázis-kapcsolat ($conn) szabad, más lekérdezés nem fut rajta, csak ezután lehet elküldeni a végrehajtani kívánt parancsot.
  • Mivel a pg_send_query() visszatérésével még nem áll rendelkezésre az eredmény, ezért a szükséges azonosítót egy új függvényhívással kell megszerezni, erre való a pg_get_result(). Jó tudni azonban, hogy e hívással blokkolja a szkript futását és addig nem tér vissza, amíg a lekérdezés eredménye rendelkezésre nem áll.

Tényleg jó ez? - a gyakorlat

Szép dolog az elmélet, de mindez jelent-e valami előnyt a gyakorlatban. Meglepve tapasztaltam, hogy igen kevés információt találni erről a technikákról az Interneten, a PHP-s a megoldásokról pedig szinte semmit. (Ezúton is kérek mindenkit, aki tud ilyen leírásokról, megjegyzésként biggyessze a cikk után!) Ez a megdöbbentő információhiány már kezdett gyanússá válni.

Ez kíváncsivá tett, ezért először megpróbáltam behatárolni, hogy valójában mekkora többletterhet ró ez a rendszerre. Ehhez a fenti rövid mintapéldát és annak szinkronizált párját futtattam néhányszor, mert párhuzamosítható feladat híján valóban a két elérés közti időkülönbség domborodik ki leginkább.
# ab2 -t 60 -n100 -c20
Document Path:          /sync.php
Complete requests:      262
Requests per second:    4.37 [#/sec] (mean)
Time per request:       4581.022 [ms] (mean)
Time per request:       229.051 [ms] (mean, across all concurrent requests)

Document Path:          /async.php
Complete requests:      255
Requests per second:    4.25 [#/sec] (mean)
Time per request:       4706.624 [ms] (mean)
Time per request:       235.331 [ms] (mean, across all concurrent requests)

Szokványos kiépítésű asztali P4 gépen levő alap Debian sarge alatt (PHP 4.3.10 + PostgreSQL 7.4) tesztelve
Mint minden mérés eredménye, számszerű következtetéseket ebből is csak a vizsgált környezet pontos ismeretében szabad levonni, és akkor is leginkább az adott rendszerre vonatkozőan. Ebben az egyszerű stressztesztben jobban szerepelt a szinkron megvalósítás mindenesetre, a különbség nem számottevő, de nem is elhanyagolható.

A másik fontos tudnivaló, hogy PostgreSQL esetében nincs lehetőségünk nem blokkolva várakozni, illetve értesítést kapni a végrehajtás befejeződéséről. Ebben talán lesz változás, mert a libpq, PHP PostreSQL illesztést végző C könyvtár erre lehetőséget biztosít.
PostreSQL használata esetén továbbá lehetőségünk van egyszerre - egy pg_send_query() hívással - több SQL parancsot is elküldeni, majd mindegyik eredményéről külön-külön tájékozódni, de csakis a lekérdezések sorrendjében. Az egy kapcsolaton át küldött lekérdezések egymáshoz képest az adott sorrendben futnak le. Ha több lekérdezést egymástól feltartása nélkül akarunk indítani, akkor azoknak külön adatbázis-kapcsolatokat kell biztosítani (pg_connect()).

Felhasználási tippek

Az elkedvetlenítő kezdés után néhány alkalmazási területen mégis hasznosnak vélem ezt a technikát, ha nem is azonnal, de a közel jövőben. Az első és talán legkézenfekvőbb lassú, nagy bonyolultságú lekérdezések esetén használni. Azt is figyelembe kell venni, hogy az adatbázisnak átadott parancs milyen további parancsok futását indítja be (szabályok, triggerek), amik megnövelik az eredeti lekérdezés teljes futási idejét.

Második legvalószínűbb felhasználási módja a végeredmény tényleges ellenőrzése nélküli SQL parancsok esete, ilyenek lehetnek például: a nem létfontosságú naplóüzenetek rögzítése, és egyéb adatmanipulációs utasítások (UPDATE, DELETE, INSERT) futtatására. Ezzel nem akarok senkit sem arra bátorítani, hogy a kiadott SQL parancsok végkifejletét elfelejtse ellenőrizni! Azon ritka esetekben, amikor ez megtehető, lehet élni ezzel a lehetőséggel.

További potenciális felhasználási terület lehet a rétegekbe szervezett alkalmazások területe. Az alkalmazási rétegben létrehozunk egy objektumot, amiben elindítjuk a lekérdezést. A tényleges adatlekérést azonban a megjelenítésért felelős sablonmotor végezteti el majd a sablon kitöltésekor. Ennek a megoldásnak a gyenge pontja a hibakezelés korlátozottsága illetve, hogy az alkalmazási rétegek közötti határ elmosódik. (Mentségemre legyen mondva, hogy léteznek olyan keretrendszerek, ahol a kód elsősorban nem a rétegezés szerint van felosztva, hanem az adatobjektumok köré szervezve, ahol valami hasonló történik, mint itt.) Az alábbi példa csak az elgondolást szemlélteti, mivel nincs mit párhuzamosítani benne!
<?php
class person {
  var $conn = NULL;
  var $res  = NULL;
  var $row  = NULL;

  function person ($conn, $id) {
    $this->conn = $conn;
    return pg_send_query($conn, "SELECT * FROM person WHERE id=$id;");
  }

  function __get ($property) {
    if ( !isset($this->res) ) {
      $this->res = pg_get_result($this->conn);
      $this->row = pg_fetch_array($this->res);
    }
    return $this->row[$property];
  }
}

$conn = pg_connect("dbname=bigdeal");

$tpl =& new template();
$tpl->register('person', new person($conn, $_GET['id']));

// ... egyéb műveletek párhuzamosan

$tpl->display('person.tpl');
?>
<html>
 <title>person.tpl - a rossz példa sablonja</title>
  <ul>
   <li>Név: {$person->name}
   <li>Cím: {$person->address}
  </ul>
</html>
Ez a még hasznosabb lehet, ha az adatbázis-kiszolgáló és a PHP külön hálózati gépen található, és közöttük a hálózati terheltség miatt lassú a kapcsolat, ami érzékelhetően csökkenti a hagyományos, szinkron adateléréssel működő szkriptek sebességét.

Amint a cikk bevezetőjében írtam, ezzel a cikkel elsősorban ezen új lehetőség bemutatása volt a célom, némi támpontot adva a lehetséges felhasználási területek számbavételével. Mivel sok információ jelenleg nem áll rendelkezésre, aki erre adja a fejét, az sok kísérletezésre számítson! De ha soha nem kísérleteznénk, még mindig a "hello world!"-nél tartanánk! Szerencse fel!
 
1

tök jó

Dohány Tamás · 2005. Már. 3. (Cs), 01.27
Köszi Gerzson, nagyon tetszett a cikk!

Említetted, hogy a technika egyik felhasználási módja az, hogy a nem lényeges parancsok ellenőrzése mellőzhető... Lehet hogy félreértettem, de én úgy gondolom, hogy a stabilítást nem áldozhatjuk fel a gyorsaság oltárán, márpedig a hibakezelés mellőzése egy ilyen dolog...

Azért elég sok veszélyt rejt magában ez a párhuzamosítás, nem? Arra gondolok, hogy ha pl. egy php kód nem elég intelligens és mondjuk táblába is illeszt, meg le is kérdez ugyanabból... Azért azt célszerű lineárisan végezni. ;)

Ez csak piszkálódáképpen, fel ne vedd: írtad valahol hogy a különbség nem számottevő, de nem is elhanyagolható. Akkor milyen? :))

[ dtamas ]
3

reflekt

gerzson · 2005. Már. 3. (Cs), 08.31
Haladjunk sorjában!

Az említettt részben -- de a cikk egészében -- parancs alatt az SQL lekérdezést/utasítást értettem. Remélem, sehol nem használtam PHP utasításokra vagy kódokra érthetően. Amikor tehát ezt írtam, akkor tulajdonképpen arra gondoltam, hogy nem vizsgáljuk meg az SQL resultset-et, hanem bízunk valamiben. Természetesen egyet értek veled, szerintem sincs értelme és helye az SQL parancsok szabadjára engedésének egy robosztus alkalmazásban - általában. (Sajnos, azonban már sok mindent láttam.)
Most egyetlen lehetséges és valamennyire észszerű felhasználási módja jut ennek eszembe: PHP munkamenet-kezelő szemétgyűjtő algoritmusa (DELETE FROM session ...). Itt talán nem olyan vészes, ha ezt megengedjük.
Ha a sablonos példa kapcsán említett korlátolt hibakezelésre gondolsz, az viszont teljességgel megoldható, csak ízlés kérdése, ki hogyan teszi. Ezért és mivel nem ez volt a téma, ezzel nem foglalkoztam.

Párhuzamosítani bármit lehet, amiknek a bemenetei egymástól függetlenek, és lehetőleg nem azonos erőforráson kell osztozniuk (lásd $conn - kapcsolatazonosító). Van pontosabb definíció is, de ez kb. megfelel. Ez azt is jelenti, hogy míg az adatbázisba írunk vagy onnan kérdezük bármilyen egyéb tevékenységgel - legyen az levélküldés, kép- vagy grafikonrajzolás, feltöltött fájlok $_FILE[] feldolgozása, backup mentés SQLite-ba, RPC hívások - elüthetjük az időt. Ugyanazt a táblát is lehet bogarászni, mivel a valódi adatbázis kiszolgálók fel vannak készítve a konkurrens elérésre. (A PostgreSQL ebben is nagy.) Az viszont már rajtad áll, h. mit kezdesz az eredménnyel!

A különbség pedig figyelembe veendő ;) Viccet félretéve az utolsóra nincs mentségem, és tervezem, hogy a hétvégén mellékelek a cikkhez még több mérési adatot. (A késés oka, h. nem minden esetben volt egyértelmű az eredmény, és ez elbizonytalanított. Inkább tesztelem még, minthogy még nagyobb hülyeségeket írjak ;) Tehát várható a jövő hét folyamán itt egy hosszú, példaprogramokkal és mérési adatokkal teli megjegyzés.
5

TCP - UDP analógia

Hodicska Gergely · 2005. Már. 3. (Cs), 10.15
a stabilítást nem áldozhatjuk fel a gyorsaság oltárán

A fenti dolog is arról szól, hogy a TCP használata esetén biztosított, hogy a csomagok a megfelelő sorrendben, hibátlanul eljussanak a címzetthez. Vszont vannak olyan alkalmazások, ahol megengedhető, hogy néhány csomag elvesszen, cserébe nagyobb az átviteli sebesség. Ennek mintájára elképzelhetőnek tartom, hogy van létjogosultsága ennek a módszernek.

márpedig a hibakezelés mellőzése egy ilyen dolog

Ráadásul ha jobban megnézed itt nem erről van szó. Csak időben szétválasztódnak a dolgok. Ugyanúgy van lehetőséged ellenőrizni, csak közben egy csomó egyéb hasznos dolgot megtehetsz.

Érdekes lehet még a téma kapcsán mysql esetén a mysql_unbuffered_query() parancs.


Felhő
2

Marha jó

Anonymous · 2005. Már. 3. (Cs), 08.19
Nekün pl ez jól jöhet akkor, amikor egy bazi nagy lekérdezést akarunk megjeleníteni táblázatba. A mi gridünknek egy java tömböt kell összerekani és átadni. Azt magától kirakja a képernyőre.
Ha az oldalon a grid előtt kisebb adatokat akarok még kirakni, akkor pont jó, hogy a nagy lekérdezést a teljes oldal összeállítása után kell csak kiolvasni.

Köszönjük néked
4

Na, erre nem is számítottam

gerzson · 2005. Már. 3. (Cs), 08.34
Na, erre nem is számítottam, hogy ilyen hamar kapóra jön valakinek!
Jó lenne, ha egy valós alkalmazás kapcsán a tapasztalataidról be tudnál majd számolni! Hozott-e valami javulást vagy sem?
6

db párhuzamos

adamkof · 2005. Már. 3. (Cs), 22.36
Az a kérdésem, hogy ha az adatbázis, egy egy procis gépen van, akkor az dbms csak a kérdések érkezési sorrendjében csinálja meg a feldolgozást. Hol lesz itt a párhuzamosság? Ez természetesen akkor is igaz, ha több connection-t csinálok. Tehát nem igazán látom a párhuzamosítást. Legfeljebb annyit, hogy esetleg az eredmény készletek sorrendját variálom, de a dbms akkor is a lekérdezések sorrendjáben adja vissza az eredményeket, nem? Hülyeség amit kérdezek?
7

másról van szó

gerzson · 2005. Már. 3. (Cs), 23.27
Ha a cikkben említett szoftvereket használod, akkor ún. multitaszkos operációs rendszert futtatsz. Ezeket pontosan azért találták ki, h. a kritikus erőforrásokat, amikből mindig kevés van és soha nem elég belőlük (memória, CPU idő, stb.), megosszák több futó program között. Adott időpillanatban természetesen csak egy program utasításait lehet futtatni egy processzoron (legalábbis egy olyanon, mint amilyen a mi gépeinkben szokott lenni ;).
A programok azonban sokszor várakozni kényszerülnek valamilyen lassabb periférián (merevlemez, hálózati csatoló, stb.) keresztül érkező adatra. Ilyenkor az operációs rendszer "feladatot vált" (taszkot vált), és egy másik program utasításait futtatja a CPU-n, és így cserélgeti őket állandóan. (Bocs a pongyolaságért, de remélem, érthető volt a lényeg.) Ez azt jelenti, h. egy processzoros gépen is van létjogosultsága ezeknek a módszereknek.

A lekérdezések végrehajtási sorrendje főként a DBMS-től függ. Mivel ezeket a szoftvereket is nagyszámú konkurrens -- egyidőben fellépő -- művelet elvégzésére tervezték, nem fognak egy egyszerű gyors lekérdezést "hátrébbb sorolni", csak mert később futott be. Az operációs rendszer lehetőségeit kihasználva, a lekérdezéseket elkezdik egymástól függetlenül végrehajtani, és amelyik "végrehajtó programja" hamarabb végez, az lesz kész előbb. szerintem ;)
9

Gondolkodván a válasz vála

adamkof · 2005. Már. 4. (P), 14.03
Gondolkodván a válasz válaszán :-), a gondolat menetem végén megint csak arra jutok, hogy ez a technika akkor igazán hatásos, ha több procis gépről van szó, vagy a egy olyan felépítésen, hogy 1 vagy több gépen van a dbms, másik 1 vagy több gépen van a köztes réteg, de igazából ténylegesen 1 gépes környezetben nem történik párhuzamosság. Persze ettől a technika még jó, de az adminisztráció miatt én azt látom ehhez hozzá kell tervezni a hardver környezetet is. Tehát amikor már olyan a hardver felépítés, olyan az adatkérések mennyisége, hogy a fenti folyamatok adminisztrációjának az ára lényegesen kevesebb, mint a kapott eredmény ára. Szóval szerintem nem lehet azon csodálkozni, hogy egy 1 gépes környezetben nem hoz igazán jó eredményeket ez a módszer.
Egyébként a dbms belső párhuzamosításáról csak annyit, hogy minden rendszer megérzi egy nagy lekérdezés futását a szerveren. Szinte látod, hogy eddig szaladt minden aztán Ágika elindította az este 6 után futtatható lekérdezést napközben és megállt a pénztár :-)
10

nem jól látod

gerzson · 2005. Már. 5. (Szo), 08.25
Nem kell hozzá több processzor akár ugyanazon a gépen, akár egy másikban, mert egy jó op.rendszeren az ütemező azonnal kidobja azt a processzt (taskot), amelyik tétlenül várakozik például egy beolvasandó lemezszektor tartalmára. Márpedig a DBMS valószínű fog a lemezről olvasni, és ebben a pillanatban egy másik program utasításait már lehet is futtatni. Természetesen rengeteg más program fut egy ilyen szerveren, tehát enélkül is találna valamit, amit futtatni lehet. Ha azonban a PHP csak blokkolva várakozik, az biztos, hogy sok mindent nem fog tudni csinálni.

Amit te feszegetsz az a valódi párhuzamoság és az ilyen ki sgépeken megvalósított "konkurrencia" közti különbség. (Bár párhuzamosításnak általában egy processz egymástól független, azaz párhuzamosítható utasításait szokták nevezni, és ennek valóban fontos feltétele, h. több CPU álljon rendelkezésre, amin ezek a részek egymás mellett tudnak futni.)

A mérési eredményeket majd közlöm később. A cikk megjelenésekor azért nem kerültek be, mert a pg_conncetion_busy() fv-re épültek, de ennek a teljesítménye csalódást jelentett, és szerintem nem a valós képet mutatja. Az azonban kizárt, hogy ez csak a hardware kiépítettségtől függjön.
8

Nem a lekérdezések párhuzamosak

Hodicska Gergely · 2005. Már. 4. (P), 10.16
Szia!

Az a kérdésem, hogy ha az adatbázis, egy egy procis gépen van, akkor az dbms csak a kérdések érkezési sorrendjében csinálja meg a feldolgozást. Hol lesz itt a párhuzamosság?

Itt most nem igazán a lekérdezések párhuzamosításáról van szó. A lényeg, hogy amíg az adatbázis a lekérdzés eredményének összeállításán dolgozik, addig te egyéb dolgokat is tehetsz. A kettő együttes futtatása adott esetben jobb erőforrás kihasználtságot eredményezhet.


Felhő
11

Teszteredmények

gerzson · 2005. Már. 6. (V), 22.44
Az alábbi méréseket ab2 programmal végeztem el -n100 paraméterrel, ebben az esetben mellőztem a kokurrens terhelésvizsgálatot.

Az első összehasonlításban kizárólag a $res lekérdezés azonosító megszerzése volt a cél. A lehető legyszerűbb lekérdezést SELECT * FROM test futtatva jelentős eltérés nem tapasztalható:
# sync.php
Requests per second:    5.05 [#/sec] (mean)
Requests per second:    5.65 [#/sec] (mean)
# async.php
Requests per second:    5.01 [#/sec] (mean)
Requests per second:    5.68 [#/sec] (mean)

Ez ellentétben áll a cikkben korábban írt véleményemmel. Ezt az eltérést a tesztprogram első verziójában használt pg_connection_busy() függvény okozta. Ezzel lehet ellenőrizni, hogy a használni kívánt adatbázis-kapcsolat foglalt-e. A tesztprogram ugyanazon a kapcsolaton keresztül bonyolított le több SQL parancsot, és egy várakozó ciklus biztosította, hogy az előző SQL már véget ért:
<?php
while ( pg_connection_busy($conn) )
    usleep(1); // elengedjük a CPU-t a lehető legkisebb időre
A cikkben megfogalmazott, homályos és tartozkódó megállapítás alapja ez a kényszerű várakozó ciklus hatása volt, mert eredetileg INSERT utasításokkal teszteltem az aszinkron elérést. (Hiba volt.)
A következő példa tipikus feladatot szimulál, először az adott feltételnek megfelelő rekordok számát kérdezzük le, aztán ugyanazon rekordok némelyikét:
<?php
pg_send_query($conn,
    "SELECT COUNT(*) FROM test WHERE name ILIKE '%A%B%C%';");

while ( pg_connection_busy($conn) )
    usleep(1);

$res1 = pg_get_result($conn);
pg_send_query($conn,
    "SELECT * FROM test WHERE name ILIKE '%A%B%C%' LIMIT 10;");
$res2 = pg_get_result($conn);
Az alábbiakban összehasonlítjuk, hogy a pg_connection_busy() váarkozó ciklussal milyen hatással van a tesztprogram teljesítményére:
# async.php - üres ciklusmaggal
Requests per second:    6.54 [#/sec] (mean)
Time per request:       152.866 [ms] (mean)

# async.php - usleep(1) ciklusmaggal
Requests per second:    13.28 [#/sec] (mean)
Time per request:       75.328 [ms] (mean)

# async.php  - csak pg_get_result() a várakozó ciklus helyett
Requests per second:    16.26 [#/sec] (mean)
Time per request:       61.498 [ms] (mean)

# sync.php
Requests per second:    15.98 [#/sec] (mean)
Time per request:       62.594 [ms] (mean)

Ebből látható, hogy a pg_connection_busy()-t csak usleep() függvénnyel együtt érdemes használni várakozó ciklusban, mert így a PHP-t futtató processz nem köt le felesleges erőforrásokat. Némi eltérés mutatkozik a várakozó ciklus nélküli, csak pg_get_result()-ot tartalmazó megoldáshoz képest.
Ebből arról következtetek, hogy a pg_connection_busy()-t akkor érdemes használni, ha van olyan feladat, amit a kapcsolat állapotától függően még az eredmények lekérdezése előtt érdemes megtenni, de az sem baj, ha ez csak az eredmények megérkezése után történik. Várakozásra megfelel a cikkben említett pg_get_result(), ami azonban blokkolja a PHP futását. (A várakozó ciklus ugyanezt teszi persze.) Ez az aszinkron használat és a szinkron időeredményei közel azonosak voltak több futtatás után, hol az egyik hol a másik bizonyult gyorsabbnak.

Ha két külön adatbáziskapcsolaton keresztül kértem le a két lekérdezés eredményét, akkor nagy(obb :) átlagban jobban megközelítette az aszinkron a szinkron elérés teljesítményét
<?php
$conn1 = pg_connect("dbname=asynctest") or die("nincs kapcsolat!");
$conn2 = pg_connect("dbname=asynctest") or die("nincs kapcsolat!");

pg_send_query($conn1,
    "SELECT COUNT(*) FROM test WHERE name ILIKE '%A%B%C%';");
pg_send_query($conn2,
    "SELECT * FROM test WHERE name ILIKE '%A%B%C%' LIMIT 10;");

$res1 = pg_get_result($conn1);
$res2 = pg_get_result($conn2);
Ebben az esetben azonban számolni kell az adatbázis-kapcsolat növekedés hatásaival is (kapcsolatok maximális száma, stb). A felesleg kapcsolatfoglalást azonban elkerülhetjük az eleddig "feketebárány" pg_connection_busy() használatával:
<?php
$conn1 = pg_connect("dbname=asynctest") or die("nincs kapcsolat!");
pg_send_query($conn1,
    "SELECT COUNT(*) FROM test WHERE name ILIKE '%A%B%C%';");

if ( pg_connection_busy($conn1) ) {
  // $conn1 foglalt, új kapcsolat kell
  $conn2 = pg_connect("dbname=asynctest") or die("nincs kapcsolat!");
} else {
  // result-set megszerzése, mielőtt újrahasznosítjuk a kapcsolatot
  $res1  = pg_get_result($conn1);
  $conn2 = $conn1;
}

pg_send_query($conn2,
    "SELECT * FROM test WHERE name ILIKE '%A%B%C%' LIMIT 10;");

// ha az előbb $res1 be lett állítva, akkor nem kérjük le még egyszer
// máskülönben a második lekérdezés eredményét tartalmazná!
if ( !isset($res1) || !is_resource($res1) )
  $res1 = pg_get_result($conn1);

$res2 = pg_get_result($conn2);