No he utilizado de forma profunda demasiados motores de bases de datos. Sí he probado con mayor o menor intensidad algunos: desde DBase III, todas las versiones de Access (aunque haya quien no los considerará bases de datos), Informix, las famosas Paradox usadas en Delphi y algún otro. Sin embargo y sin dudarlo, soy un amante (y porqué no decirlo, experto) de Microsoft SQL Server (todas desde su versión 7.0), su implantación en entornos de desarrollo es fantástico y su rendimiento (aun sin compararlo con otros motores) siempre me ha parecido fabuloso. Y por supuesto sus herramientas, sobre todo el análisis de consultas, analizador de rendimiento y otros te permiten gestionar de forma eficaz tus bases de datos. Por poner una pega, a partir de la versión Microsoft SQL Server 2005, la han fastidiado con el Administrador Corporativo... esperemos lo vayan arreglando. Por último, nunca he trasteado con Oracle, siempre me ha dado pereza y salvo satisfacer la curiosidad de comparar "paradigmas" no me ofrece ninguna ventaja. Tiempo habrá.
Apenas he empezado a sobrevolar MySql. Hace tiempo trastee un poco con él, pero me dejó bastante indiferente, era pobre y raquítico. Sin embargo ahora (MySQL Server 5.0) sí me parece que haya crecido, de momento sigue siendo libre y eso ya lo hace atractivo (hace poco que lo ha comprado Sun Microsystems). Aun admitiendo que no podré ser del todo objetivo, intentaré evaluarlo sin que interfiera mi desconocimiento sobre él (pues nuestra mente siempre es perezosa al cambio) y mi gran aprecio por Microsoft SQL Server.
Así, de forma impulsiva, destacar algunos inconvenientes de MySql más o menos relevantes:
En fin, seguiremos analizándolo, pero en cualquier caso, tiene buena pinta.
La primera patada en la frente. Estaba yo tan contento haciendo unas cuentas pruebas con MySql y ya tenía listos mis procedimientos almacenados (siempre, siempre, salvo extrañísimas excepciones hay que encapsular nuestra gestión de base de datos mediante procedimientos almacenados; otras alternativas "parecerán" más productivas [como usar recordset actualizables] pero en realidad no lo son) y al realizar las llamadas desde PHP me fijo que no funciona una llamada a $result->close() que libere los recursos de la consulta para poder ejecutar otra posterior.
Concretamente, no funciona:
// Inicialización conexión con base de datos: $this->cnx = new mysqli( MSPW_SITE_DB_Host, MSPW_SITE_DB_User, MSPW_SITE_DB_Pass, MSPW_SITE_DB_Data ); // Preparamos una consulta: $q1 = $this->cnx->query( 'CALL cmp_node_list( \'pedro\', NULL )' ); // Recuperamos datos: $r1 = $q1->fetch_assoc(); // Liberamos recursos: $q1->close(); // Hasta aquí todo correcto. // Preparamos otra consulta: $q2 = $this->cnx->query( 'CALL cmp_node_list( \'pedro\', NULL )' ); // ¡Se produce un error! printf( "ERROR { %s }\n", $this->cnx->error ); /* ERROR { Commands out of sync; you can't run this command now } */
Aparentemente se han hecho las cosas tal y como pone en el manual de PHP, buscamos y rebuscamos y no encontramos nada. ¿Que ocurre?, ¿porqué no se liberan los recursos de la sentencia anterior?.
No hay ningún problema sin embargo, si en lugar de procedimientos almacenados usamos consultas directamente (como un SELECT).
Harto de mirar en PHP, nos vamos a la documentación de MySql y leemos con sorpresa que el problema efectivamente está en MySql (aunque la API de PHP mysqli ya que lo sabe y que es específica de MySql podría haberlo dejado resuelto...).
En el punto 21.2.6. CALL Statement Syntax de la documentación de MySql leemos:
If you write C programs that use the CALL SQL statement to execute stored procedures that produce result sets, you must set the CLIENT_MULTI_RESULTS flag, either explicitly, or implicitly by setting CLIENT_MULTI_STATEMENTS when you call mysql_real_connect(). This is because each such stored procedure produces multiple results: the result sets returned by statements executed within the procedure, as well as a result to indicate the call status. To process the result of a CALL statement, use a loop that calls mysql_next_result() to determine whether there are more results. For an example, see Section 26.2.9, "C API Handling of Multiple Statement Execution".
Es decir, aunque no queramos leer los resultados, aunque no los pidamos (si, sin hacer fetch_assoc también falla) estamos obligados a recorrer una colección de resultados. Sí, no es mucho, pero estamos obligados a realizar un par de llamadas más.
Bien, ahora que lo sabemos, llamaremos directamente a la función multi_query y haremos siempre tantas llamadas a store_result como nos indique next_result.
En determinadas situaciones es preciso optimizar las llamadas a la base de datos, bien porque el conjunto de resultados es muy grande (y queremos procesarlo lo más rápida y directamente posible) o bien porque el número de llamadas es muy elevada. En el primer caso utilizaremos directamente el canal de datos que venga del motor de bases de datos y en el segundo precompilaremos las sentencias (sí, aun cuando la sentencia sea un procedimiento almacenado) parametrizándola (en mysqli mediante statements usando stmt_init y otros).
Sin embargo, en la mayoría de los casos, nuestras llamadas a procedimientos almacenados no son frecuentes (como para precompilar las sentencias cada vez) y no devuelven un conjunto grande de resultados (muchas veces ninguno o sólo un registro). Por tanto, parece que lo sensato es trivializar la programación y utilizar los métodos anteriores sólo cuando sea preciso.
Una sencilla función en PHP nos permitirá obtener todos los conjuntos de resultados de forma cómoda. Tiene el inconveniente de hacer un copiado intermedio de los datos (¡y duplicar para cada registro los nombres de los campos!), pero en la mayoría de los casos el coste adicional será despreciable consiguiendo una productividad mucho mayor, tanto en el desarrollo como en el mantenimiento del código.
/************************************************************************************************************** * Devuelve un array con array's asociativos conteniendo todos los datos. * ResultSet { * DataSet1 { * Row1 { * Columna1 => Dato1, * Columna2 => Dato2, * ... * } * Row2 { * Columna1 => Dato1, * Columna2 => Dato2, * ... * } * ... * } * DataSet2 { * ... * } * ... * } */ private function rs_query( $query ) { // nuestro ResultSet $RS = array(); // ¿Se ejecutó correctamente? if( $this->cnx->multi_query( $query ) ) { do { // ¿Hay datos? if( $result = $this->cnx->store_result() ) { // Un DataSet $DS = array(); // Copiamos datos: while( $row = $result->fetch_assoc() ) { $DS[] = $row; } // Añadimos el DataSet al ResultSet $RS[] = $DS; // Liberamos resultado intermedio: $result->close(); } // ¿Quedan datos? } while( $this->cnx->next_result() ); } // Devolvemos el ResultSet con los DataSet return $RS; }