Abstração de SGBDs - Estudo comparativo - Parte 2 Desempenho
Esse post é uma continuação do anterior http://www.especializa.com.br/berardo/2006/11/abstrao-de-sgbds-estudo-comparativo.html. Para saber mais sobre as bibliotecas, visite o link acima.
Estudo comparativo
A partir de agora, apresentaremos um estudo comparativo entre as sete bibliotecas citadas. Não é nosso intuito procurar uma resposta definitiva sobre qual é a melhor, você vai perceber que nos três quesitos que avaliaremos (desempenho, facilidade e portabilidade) nenhuma conseguiu se sobressair completamente. Vale ressaltar aqui que este é um estudo bem inicial, servindo apenas para fomentar a pesquisa antes de tecer qualquer julgamento sobre alguma delas. Sem contar que muitos outros fatores importantes não estão sendo avaliados.
Desempenho
Este certamente é um fator da mais alta importância. Inspirado no blog de Joseph Scott (http://joseph.randomnetworks.com/), que trouxe um comparativo preliminar apenas com três destas bibliotecas, nosso estudo também é inicial.
Avaliaremos o tempo de processamento do PHP para consultar uma base de dados PostgreSQL de cadastro de atores de cinema. Nela, há 542 atores cadastrados e faremos uma consulta para listar seus nomes em ordem alfabética. Primeiramente, não exibiremos seus nomes e colhemos o resultado, depois exibiremos e também coletaremos o resultado. Uma consulta com essa quantidade de registros não é grande o suficiente, portanto, a repetiremos num laço 250 vezes.
Tomaremos como base um simples script procedural com acesso diretamente através das funções nativas do PostgreSQL. Confira no código abaixo:
Código 1 – Benchmark – PostgreSQL direto
1. 2. $time_start = microtime(true);
3. $db = pg_connect("host=localhost dbname=locadora " .
4. "user=postgres password=postgres");
5.
6. $runs = 250;
7. for($i = 0; $i < $runs; $i++) {
8. $sql = "select nome from atores order by nome";
9. $rs = pg_query($db, $sql);
10. while($row = pg_fetch_row($rs)) {
11. echo $row[0];
12. }
13. }
14.
15. $time_end = microtime(true);
16. $time = $time_end - $time_start;
17. print("\n\nTEMPO: {$time}\n");
18. ?>
O primeiro passo foi recuperar o timestamp atual (em $time_start). Depois conectamos ao banco PostgreSQL. A linha que nos interessa é a 11. Nela, exibiremos o resultado carregado em $row. Ao final das 250 consultas idênticas, recuperaremos o timestamp final e exibiremos a diferença.
Os testes foram realizados primeiramente numa máquina modesta. Foram feitas chamadas ao script diretamente da linha de comando (# php teste1.php), ou seja, sem requerer processamento do Apache e envio da resposta ao browser. Invocamos a execução do script omitindo a linha 11, que imprime os nomes. Apesar de não percebermos variações significativas de resultado, colhemos o valor do tempo mais baixo. Depois executamos mais cinco vezes com a linha omitida anteriormente e também colhemos o resultado mais baixo. Para tornar o teste mais real, efetuamos o mesmo procedimento em outra máquina um pouco mais potente. Confira suas configurações:
Máquina 1: Intel Pentium III, 256Mb RAM, SO Linux Slackware 10.2. PHP 5.1.6
Máquina 2: AMD Sempron 64 2800, 512Mb RAM, SO Linux Slackware 10.2. PHP 5.1.6. Confira os resultados na tabela abaixo (valores arredondados em segundos):
Tabela 1 – Benchmark – Resultados PostgreSQL direto
Máquina 1 - Sem impressão | Máquina 1 - Com impressão | Máquina 2 - Sem impressão | Máquina 2 - Com impressão |
---|---|---|---|
2,01268792 | 4,2395219 | 0,9198659 | 2,3729560 |
Agora veja como faríamos na primeira biblioteca citada, Metabase:
Código 2 – Benchmark – Metabase
1. 2. require("metabase/metabase_interface.php");
3. require("metabase/metabase_database.php");
4. ini_set('include_path',
5. ini_get('include_path').':'.
6. dirname(__FILE__).'/metabase');
7. $time_start = microtime(true);
8.
9. MetabaseSetupDatabaseObject(array(
10. "Type" => "pgsql",
11. "User" => "postgres",
12. "Password" => "postgres"),
13. $conn);
14. MetabaseSetDatabase($conn->database, "locadora");
15.
16. $runs = 250;
17. for($i = 0; $i < $runs; $i++) {
18. $sql = "select nome from atores order by nome";
19. $rs = $conn->Query($sql);
20. $rows = $conn->NumberOfRows($rs);
21. for ($x=0; $x<$rows; $x++) {
22. echo $conn->FetchResult($rs, $x, 0);
23. }
24. }
25.
26. $time_end = microtime(true);
27. $time = $time_end - $time_start;
28. print("\n\nTEMPO: {$time}\n");
29. ?>
Aproveitando para explicar o código, inicialmente, incluímos os arquivos necessários e adicionamos o diretório da biblioteca Metabase extraída (sob o próprio diretório dos testes) na include_path. Só a partir daí o script começou a contar o tempo. A função MetabaseSetupDatabaseObject estabeleceu a conexão com o servidor passado como parâmetro e MetabaseSetDatabase definiu a base de dados . Objeto $conn efetuou a consulta ($conn->Query()) e um recuperou o total de linhas ($conn->NumberOfRows()). Dentro do laço, cada linha foi recuperada através de $conn->FetchResult(). No primeiro teste, a linha 22 não foi totalmente omitida. Se fosse, nenhum dado seria carregado na memória, visto que o laço é um simples for de zero até o total de linhas consultadas. O que fizemos foi testar a linha 22 da seguinte forma:
$resultado = $conn->FetchResult($rs, $x, 0);
Trouxemos o resultado para uma variável em vez de imprimir direto. No segundo teste, a linha 22 ficou como está lá. Veja o resultado. Mantivemos os valores do PostgreSQL direto para efeito comparativo. A última linha mostra quanto em valores percentuais os novos tempos excederam os primeiros (valores arredondados):
Tabela 2 – Benchmark – Resultados Metabase
Máquina 1 - Sem impressão | Máquina 1 - Com impressão | Máquina 2 - Sem impressão | Máquina 2 - Com impressão |
---|---|---|---|
2,01268792 | 4,2395219 | 0,9198659 | 2,3729560 |
5,0331959 | 9,8323698 | 2,3078690 | 5,6365590 |
150,07% | 131,92% | 150,89% | 137,53% |
Percebe-se que o PHP passou a sofrer um pouco para recuperar os mesmos dados. Se antes ele precisava de pouco mais de quatro segundos para efetuar uma operação, agora serão necessários mais do dobro. Dando seqüência aos testes, apresentaremos a ADOdb. Confira:
Código 3 – Benchmark ADOdb
1. 2. require_once("adodb/adodb.inc.php");
3. $time_start = microtime(true);
4.
5. $conn = NewADOConnection(
6. "pgsql://postgres:postgres@localhost/locadora");
7. $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
8. $runs = 250;
9. for($i = 0; $i < $runs; $i++) {
10. $sql = "select nome from atores order by nome";
11. $rs = $conn->Execute($sql);
12. while(!$rs->EOF) {
13. echo $rs->fields[0];
14. $rs->MoveNext();
15. }
16. }
17.
18. $time_end = microtime(true);
19. $time = $time_end - $time_start;
20. print("\n\nTEMPO: {$time}\n");
21. ?>
Assim como na Metabase, fizemos o download da ADOdb e extraímos num diretório abaixo do nosso teste. Agora, apenas o arquivo adodb.inc.php foi necessário. Não precisamos adicionar este subdiretório da biblioteca na include_path.
A função NewADOConnection() entregou a $conn o objeto que utilizaremos para efetuar a consulta. Neste momento, também foi mais fácil passar os parâmetros necessário para ela saber qual banco conectar. Parâmetros na forma de uma única URL, no lugar de uma array de parâmetros e um método adicional para escolher a base, simplifica bem as coisas.
A variável definida na linha 8 foi necessária para informar à biblioteca que traga apenas arrays indexados, já que o padrão é trazer arrays com valores indexados e associativos (algo como MYSQL_BOTH que você já conhece).
Na linha 12, a instrução $conn->Execute() efetuou a consulta e devolveu o identificador do resultado para $rs. O atributo $rs->EOF (end of file) será verdadeiro quando o ponteiro de leitura do resultado alcançar o final do resultado. Já o atributo $rs->fields é um array (apenas indexado devido à variável $ADODB_FETCH_MODE ser ADODB_FETCH_NUM) com os campos da consulta.
A linha 14 foi comentada no primeiro teste e recolocada no segundo. O método $rs->MoveNext() foi o responsável para avançar ao próximo registro. Sem ele, entraríamos em loop infinito.
Da mesma maneira que fizemos com a Metabase, confira os resultados:
Tabela 3 – Benchmark – Resultados ADOdb
Máquina 1 - Sem impressão | Máquina 1 - Com impressão | Máquina 2 - Sem impressão | Máquina 2 - Com impressão |
---|---|---|---|
2,01268792 | 4,2395219 | 0,9198659 | 2,3729560 |
5,1899459 | 10,7471740 | 2,2355139 | 6,1193878 |
157,86% | 153,50% | 143,03% | 157,88% |
Note que o resultado final foi ligeiramente mais lento do que a Metabase, chegando ser mais rápida apenas no terceiro teste. Podemos concluir que elas tiveram tecnicamente o mesmo desempenho, com vitória da Metabase no critério de desempate =D.
A próxima a ser testada será a extensão DBX. Confira seu código:
Código 4 – Benchmark DBX
1. 2. $time_start = microtime(true);
3.
4. $conn = dbx_connect("pgsql", "localhost",
5. "locadora", "postgres", "postgres");
6. $runs = 250;
7. for($i = 0; $i < $runs; $i++) {
8. $sql = "select nome from atores order by nome";
9. $rs = dbx_query($conn, $sql, DBX_RESULT_INDEX);
10. for ($x = 0; $x < $rs->rows; $x++) {
11. echo $rs->data[$x][0];
12. }
13. }
14. $time_end = microtime(true);
15. $time = $time_end - $time_start;
16. print("\n\nTEMPO: {$time}\n");
17. ?>
Por se tratar de uma extensão, não foi necessário incluir arquivo algum ao script. A função dbx_connect(), conectou ao nosso banco recebendo as informações cada uma como um parâmetro (banco, host, base, usuário e senha). A consulta ficou a cargo da função dbx_query(), que assim como a pg_query(), recebeu no primeiro argumento, a variável que representa a conexão e no segundo a instrução SQL. O terceiro, DBX_RESULT_INDEX, foi para informar que o resultado será recuperado num array apenas indexado. O mesmo que fizemos com a ADOdb.
Após a consulta, a variável $rs recebeu um objeto de stdClass com alguns atributos populados. $rs->rows corresponde ao total de linhas da consulta. Em $rs->data todo o conteúdo foi lançado como uma matriz. O que fizemos foi varrer essa matriz da maneira mais eficiente (através de um laço for - utilizar um foreach nesse caso degradaria o desempenho desnecessariamente). A linha 11 foi simplesmente comentada no primeiro teste e refeita no segundo. Confira o resultado:
Tabela 4 – Benchmark – Resultados DBX
Máquina 1 - Sem impressão | Máquina 1 - Com impressão | Máquina 2 - Sem impressão | Máquina 2 - Com impressão |
---|---|---|---|
2,01268792 | 4,2395219 | 0,9198659 | 2,3729560 |
4,7955241 | 9,3504899 | 1,9520251 | 4,8769801 |
138,26% | 120,26% | 112,21% | 105,52% |
Houve um pequeno ganho de desempenho. Uma coisa interessante de se notar é a queda considerável de percentual nos testes com impressão do resultado. A explicação é bem simples. A DBX carregou os dados numa matriz, num processo semelhante à pg_fetch_row() que recuperou o resultado num array a cada iteração do laço. O ato de leitura em ambas foi apenas imprimir a posição de um array indexado. Assim a ação de exibição dos dados na tela levou o mesmo tempo em ambas. Em suma, a diferença está exclusivamente no momento de envio da consulta ao SGBD e recuperação do resultado em variável.
A próxima da lista é a PEAR::DB. Acompanhe:
Código 5 – Benchmark PEAR::DB
1. 2. require_once("PEAR.php");
3. require_once("DB.php");
4. $time_start = microtime(true);
5.
6. $conn = DB::Connect(
7. "pgsql://postgres:postgres@localhost/locadora");
8. $runs = 250;
9. for($i = 0; $i < $runs; $i++) {
10. $sql = "select nome from atores order by nome";
11. $rs = $conn->query($sql);
12. while($row = $rs->fetchRow()) {
13. echo $row[0];
14. }
15. }
16.
17. $time_end = microtime(true);
18. $time = $time_end - $time_start;
19. print("\n\nTEMPO: {$time}\n");
20. ?>
Incluímos, no início, os arquivos necessários. PEAR.php para todo e qualquer biblioteca do PEAR (ela seria incluída indiretamente se não fizéssemos, como nosso foco é o desempenho apenas da consulta, a incluímos logo de cara, antes de zerar os cronômetros) e DB.php específico para a PEAR::DB.
A conexão foi efetuada pela instrução DB::Connect(). DB é uma classe utilitária, seu método estático Connect() retorna um objeto do tipo DB_common. Esta última é uma classe extendida pelas classes particulares de cada SGBD suportado. Assim, no nosso caso, a variável $conn recebeu uma instância de DB_ pgsql (que herda de DB_common) devido à informação do protocolo (pgsql://) na URL de conexão.
A consulta se deu através do método $conn->query(), declarado em DB_common e redefinido em cada uma de suas subclasses. Num processo semelhante ao do PostgreSQL direto, fizemos um while onde uma variável $row recebeu o resultado do método $rs->fetchRow(), que por padrão já retorna um array indexado.
Comentando e descomentando a linha 13, obtivemos os seguintes resultados:
Tabela 5 – Benchmark – Resultados PEAR::DB
Máquina 1 - Sem impressão | Máquina 1 - Com impressão | Máquina 2 - Sem impressão | Máquina 2 - Com impressão |
---|---|---|---|
2,01268792 | 4,2395219 | 0,9198659 | 2,3729560 |
7,6816080 | 16,5098081 | 3,4886191 | 9,4957421 |
281,66% | 289,43% | 279,25% | 300,17% |
Repetimos esses testes exaustivamente por simplesmente não acreditarmos que o resultado pudesse ter sido tão ruim. O mais estranho foi o aumento percentual nos testes com impressão do resultado. É como se houvesse uma alocação excessiva de memória, suficiente até para que uma simples exibição de dados nas variáveis também se tornasse uma operação mais custosa do que no diretamente nas funções de acesso ao PostgreSQL.
Com a sua sucessora PEAR::MDB2 o resultado curiosamente só piorou. Veja:
Código 6 – Benchmark PEAR::MDB2
1. 2. require_once("PEAR.php");
3. require_once("MDB2.php");
4. $time_start = microtime(true);
5.
6. $conn = MDB2::Connect(
7. "pgsql://postgres:postgres@localhost/locadora");
8. $runs = 250;
9. for($i = 0; $i < $runs; $i++) {
10. $sql = "select nome from atores order by nome";
11. $rs = $conn->query($sql);
12. while($row = $rs->fetchRow()) {
13. echo $row[0];
14. }
15. }
16.
17. $time_end = microtime(true);
18. $time = $time_end - $time_start;
19. print("\n\nTEMPO: {$time}\n");
20. ?>
Note que o script foi praticamente o mesmo. Os métodos de $conn e $rs não mudaram. Confira o resultado:
Tabela 6 – Benchmark – Resultados PEAR::MDB2
Máquina 1 - Sem impressão | Máquina 1 - Com impressão | Máquina 2 - Sem impressão | Máquina 2 - Com impressão |
---|---|---|---|
2,01268792 | 4,2395219 | 0,9198659 | 2,3729560 |
9,3536999 | 20,7585239 | 4,2592630 | 11,3491249 |
364,74% | 389,64% | 363,03% | 378,27% |
Tornamos a falar que nosso teste não é conclusivo. Haveria uma série de outras coisas a se avaliar. No entanto, numa operação bastante corriqueira em qualquer sistema (simples listagem de dados de uma base), a diferença entre as demais soluções e as do PEAR foram expressivas.
A próxima biblioteca analisada foi a Creole, veja como foi:
Código 7 – Benchmark Creole
1. 2. require_once("creole/Creole.php");
3. $time_start = microtime(true);
4.
5. $conn = Creole::getConnection(
6. "pgsql://postgres:postgres@localhost/locadora");
7. $stmt = $conn->createStatement();
8. $runs = 250;
9. for($i = 0; $i < $runs; $i++) {
10. $sql = "select nome from atores order by nome";
11. $rs = $stmt->executeQuery($sql, ResultSet::FETCHMODE_NUM);
12. while ($rs->next()) {
13. echo $rs->getString(1);
14. }
15. }
16.
17. $time_end = microtime(true);
18. $time = $time_end - $time_start;
19. print("\n\nTEMPO: {$time}\n");
20. ?>
Assim como em Java, $conn é uma instância de uma classe que implementa a interface Connection (no JDBC seria java.sql.Connection), no caso, PgSQLConnection. A partir de $conn, obtivemos um objeto Statement (PgSQLStatement) que armazenamos em $stmt. Objetos são os responsável por efetuar as consultas.
O método $stmt->executeQuery() recuperou um objeto ResultSet, de onde iremos colher as informações. Seu método $rs->next() avança o ponteiro e retorna verdadeiro se houver próximo registro, falso caso contrário.
Seguir à risca a sintaxe do JDBC não rendeu tão bem, se você trocar o laço while das linhas 12 a 14 pelo laço abaixo, terá um resultado mais eficiente:
foreach ($rs as $row) {
echo $row[0];
}
A variável $rs é do tipo PgSQLResultSet, que por sua vez, implementa a interface ResultSet, como seria em Java. O que a Creole fez de interessante foi declarar que ResultSet herda da interface SPL IteratorAggregate. Assim, PgSQLResultSet implementou os métodos homônimos ao ResultSet do JDBC, mais o método getIterator(). No caso da PgSQLResult, o método retorna uma instância de PgSQLResultSetIterator. Assim, foi possível passar $rs no foreach, o PHP já vai saber que deve chamar os métodos de PgSQLResultSetIterator para recuperar cada valor colocado em $row. Finalmente, $row será apenas um array indexado, devido ao parâmetro ResultSet::FETCHMODE_NUM passado na consulta.
A tabela abaixo mostra os resultados das duas metodologias:
Tabela 7 – Benchmark – Resultados Creole
Máquina 1 - Sem impressão | Máquina 1 - Com impressão | Máquina 2 - Sem impressão | Máquina 2 - Com impressão |
---|---|---|---|
2,01268792 | 4,2395219 | 0,9198659 | 2,3729560 |
Método JDBC | |||
3,9955730 | 11,8272559 | 1,7274060 | 7,6208939 |
98,52% | 178,98% | 87,79% | 221,16% |
Método SPL | |||
3,6212871 | 7,4538250 | 1,5732012 | 4,2920520 |
79,92% | 75,85% | 71,03% | 80,87% |
O formato SPL garantiu um melhor desempenho em todos os testes, com destaque para o momento em que exibimos o resultado (Com impressão) que, no método JDBC foi bem mais lento em termos percentuais e no SPL chegou a ser mais rápido na máquina 1 e pouco mais lento na máquina 2. Afinal exibir o valor de um array é mais rápido do que chamar um método que pesquisará por esse valor no resultado da consulta.
A última biblioteca testada foi a PDO, confira:
Código 8 – Benchmark PDO
1. 2. $conn = new PDO("pgsql:host=localhost dbname=locadora " .
3. "user=postgres password=postgres");
4. $runs = 250;
5. for($i = 0; $i < $runs; $i++) {
6. $sql = "select nome from atores order by nome";
7. foreach($conn->query($sql, PDO::FETCH_NUM) as $row) {
8. echo $row[0];
9. }
10. }
11.
12. $time_end = microtime(true);
13. $time = $time_end - $time_start;
14. print("\n\nTEMPO: {$time}\n");
15. ?>
A conexão com o SGBD se dá através da instância de um objeto da classe PDO. Passamos como parâmetro, a mesma string que havíamos passado no pg_connect(), adicionando apenas o rótulo pgsql:. Num exemplo de desatenção à portabilidade, a conexão PDO com o MySQL, não seria desta forma, mas de acordo com a sintaxe da função mysql_connect(), ou seja, receberia três parâmetros, como abaixo:
$conn = new PDO("mysql:host=localhost;dbname=locadora", "root", "");
Nesse caso, as diferenças para a mysql_connect() são apenas o rótulo mysql: e a informação da base de dados, no lugar da chamada à infame mysql_select_db().
Como você deve saber que conexões com o banco devem ser declaradas em um único local para serem incluídas quando necessárias (afinal, trocar de banco só em casos especiais, mas a troca de servidor, usuário ou senha é um procedimento bem comum), a diferença nessa declaração de um SGBD para outro não lhe traria maiores problemas.
A consulta foi feita através da instrução $conn->query() que recebeu a intrução SQL como parâmetro e retornou um objeto do tipo PDOStatement, uma classe da API da PDO que implementa a interface SPL IteratorAggregate, por isso, é transpassável por um foreach como fosse um array. Devido ao segundo parâmetro PDO::FETCH_NUM, o resultado de cada iteração do laço será um array indexado.
O resultado final foi surpreendente. Apesar de já esperarmos que um bom desempenho do PDO por ser uma extensão escrita em C, a boa implementação da SPL proporcionou o melhor resultado, longe dos demais. Observe:
Tabela 8 – Benchmark – Resultados PDO
Máquina 1 - Sem impressão | Máquina 1 - Com impressão | Máquina 2 - Sem impressão | Máquina 2 - Com impressão |
---|---|---|---|
2,01268792 | 4,2395219 | 0,9198659 | 2,3729560 |
2,1494179 | 4,4430050 | 0,9116380 | 1,9495799 |
6,79% | 4,80% | -0,89% | -17,84% |
Em todos os testes, ela andou bastante próxima das chamadas nativas. E por mais estranho que possa parecer, chegou a superá-las na segunda máquina. Não pense que foi um resultado isolado, durante os testes nesta máquina, a PDO andou regularmente abaixo do PostgreSQL direto.
As grades abaixo mostram o acumulado de resultados catalogados aqui, com as bibliotecas na ordem da média do percentual de tempo:
Tabela 9 – Benchmark – Grade Máquina 1
Acesso | Sem impressão (s) | Com impressão (s) | Sem impressão % | Com impressão % |
---|---|---|---|---|
PostgreSQL | 2,01268792152 | 4,23952198029 | 0,00% | 0,00% |
PDO | 2,1494178772 | 4,44300508499 | 6,79% | 4,80% |
Creole SPL | 3,62128710747 | 7,45382499695 | 79,92% | 75,82% |
DBX | 4,79552412033 | 9,35048985481 | 138,26% | 120,56% |
Creole JDBC | 3,99557304382 | 11,8272559643 | 98,52% | 178,98% |
Metabase | 5,03319597244 | 9,83236980438 | 150,07% | 131,92% |
ADOdb | 5,1899459362 | 10,7471740246 | 157,86% | 153,50% |
DB | 7,68160796165 | 16,5098080635 | 281,66% | 289,43% |
MDB2 | 9,35369992256 | 20,758523941 | 364,74% | 389,64% |
Tabela 10 – Benchmark – Grade Máquina 2
Acesso | Sem impressão (s) | Com impressão (s) | Sem impressão % | Com impressão % |
---|---|---|---|---|
PDO | 0,911638021469 | 1,94957995415 | -0,89% | -17,84% |
PostgreSQL | 0,919865846634 | 2,37295603752 | 0,00% | 0,00% |
Creole SPL | 1,5732011795 | 4,29205203056 | 71,03% | 80,07% |
DBX | 1,95202517509 | 4,8769800663 | 112,21% | 105,52% |
Metabase | 2,30786895752 | 5,63655900955 | 150,89% | 137,53% |
ADOdb | 2,23551392555 | 6,11938786507 | 143,03% | 157,88% |
Creole JDBC | 1,72740602493 | 7,62089395523 | 87,79% | 221,16% |
DB | 3,48861908913 | 9,4957420826 | 279,25% | 300,17% |
MDB2 | 4,25926303864 | 11,3491249084 | 363,03% | 378,27% |
Na média geral dos percentuais de tempo, o resultado foi:
Tabela 11 – Grade final – Média percentual dos quatro tempos
Acesso | Média percentual |
---|---|
PDO | -1,79% |
PostgreSQL | 0,00% |
Creole SPL | 76,91% |
DBX | 119,14% |
Metabase | 142,61% |
Creole JDBC | 146,61% |
ADOdb | 153,07% |
PEAR::DB | 287,63% |
PEAR::MDB2 | 373,92% |
No próximo post, entraremos nos próximos quesitos do estudo: facilidade e portabilidade.