08 septiembre 2017

Java + SQL: ¿Por qué son importantes las declaraciones preparadas (prepare statement)?

Las instrucciones de procesamiento pueden ser una operación costosa, pero las bases de datos se escriben ahora de tal manera que se minimiza esta sobrecarga. Sin embargo, estas optimizaciones necesitan ayuda de los desarrolladores de aplicaciones si queremos aprovecharlas. En este artículo se muestra cómo el uso correcto de las instrucciones preparadas puede ayudar significativamente a una base de datos realizar estas optimizaciones.
Las bases de datos tienen un trabajo duro. Ellos aceptan consultas SQL de muchos clientes simultáneamente y ejecutan las consultas de la manera más eficiente posible contra los datos. Las instrucciones de procesamiento pueden ser una operación costosa, pero las bases de datos se escriben ahora de tal manera que se minimiza esta sobrecarga. Sin embargo, estas optimizaciones necesitan ayuda de los desarrolladores de aplicaciones si queremos aprovecharlas. En este artículo se muestra cómo el uso correcto de PreparedStatements puede ayudar significativamente a una base de datos realizar estas optimizaciones.

¿Cómo ejecuta una base de datos una sentencia?

Obviamente, no esperes mucho detalle aquí; sólo examinaremos los aspectos importantes para este artículo. Cuando una base de datos recibe una sentencia, el motor de base de datos analiza primero la sentencia y busca errores de sintaxis. Una vez analizada la sentencia, la base de datos debe encontrar la forma más eficiente de ejecutar la sentencia. Esto puede ser computacionalmente bastante caro. La base de datos comprueba qué índices, si los hay, pueden ayudar o si debe realizar una lectura completa de todas las filas de una tabla. Las bases de datos utilizan estadísticas sobre los datos para averiguar cuál es la mejor manera. Una vez creado el plan de consulta, el motor de base de datos puede ejecutarlo.

Toma la energía de la CPU para hacer la generación del plan de acceso. Idealmente, si enviamos la misma declaración a la base de datos dos veces, entonces nos gustaría que la base de datos reutilizar el plan de acceso para la primera declaración. Esto utiliza menos CPU que si regeneró el plan una segunda vez.

Cachés de sentencias

Las bases de datos se sintonizan para hacer cachés de sentencias. Por lo general incluyen algún tipo de caché de instrucciones. Esta caché utiliza la instrucción en sí como una clave y el plan de acceso se almacena en la caché con la instrucción correspondiente. Esto permite al motor de base de datos reutilizar los planes para las sentencias que se han ejecutado anteriormente. Por ejemplo, si enviamos a la base de datos una sentencia como "select a, b from t where c = 2", entonces el plan de acceso calculado se almacena en caché. Si enviamos la misma sentencia más adelante, la base de datos puede volver a utilizar el plan de acceso anterior, ahorrándonos energía de la CPU.
Tenga en cuenta, sin embargo, que toda la sentencia es la clave. Por ejemplo, si luego enviamos la sentencia "seleccione a, b de t donde c = 3", no encontraría un plan de acceso. Esto se debe a que el "c = 3" es diferente del plan en caché "c = 2". Así por ejemplo:

For(int I = 0; I < 1000; ++I)
{
        PreparedStatement ps = conn.prepareStatement("select a,b from t where c = " + I);
        ResultSet rs = Ps.executeQuery();
        Rs.close();
        Ps.close();
}

Aquí no se utilizará el caché. Cada iteración del bucle envía una instrucción SQL diferente a la base de datos. Se calcula un nuevo plan de acceso para cada iteración y básicamente estamos lanzando ciclos de CPU usando este enfoque. Sin embargo, mira el siguiente fragmento:
PreparedStatement ps = conn.prepareStatement("select a,b from t where c = ?");
PreparedStatement ps = conn.prepareStatement("select a,b from t where c = ?");
For(int I = 0; I < 1000; ++I)
{
        ps.setInt(1, I);
        ResultSet rs = ps.executeQuery();
        Rs.close();
}
ps.close();

Aquí será mucho más eficiente. La sentencia enviada a la base de datos se parametriza usando el signo '?' marcador en el sql. Esto significa que cada iteración está enviando la misma sentencia a la base de datos con diferentes parámetros para el "c =?" parte. Esto permite que la base de datos reutilice los planes de acceso para la sentencia y haga que el programa se ejecute más eficientemente dentro de la base de datos. Esto básicamente permite que su aplicación se ejecute más rápido o haga más CPU disponible para los usuarios de la base de datos.

PreparedStatements y servidores J2EE

Las cosas pueden ser más complicadas cuando usamos un servidor J2EE. Normalmente, una sentencia preparada se asocia con una conexión de base de datos única. Cuando se cierra la conexión, se descarta el estado de preparación. Normalmente, una aplicación de cliente de grasa obtendría una conexión de base de datos y luego la mantendría durante toda su vida útil. También crearía todas las declaraciones preparadas con avidez o pereza. Ansiosamente significa que todos ellos se crean al mismo tiempo cuando se inicia la aplicación. Lazily significa que se crean como se utilizan. Un enfoque ansioso da un retraso cuando la aplicación se inicia, pero una vez que se inicia, entonces se realiza de manera óptima. Un enfoque lento proporciona un inicio rápido, pero a medida que se ejecuta la aplicación, las sentencias preparadas se crean cuando son utilizadas por primera vez por la aplicación. Esto da un rendimiento desigual hasta que se preparan todas las sentencias, pero la aplicación finalmente se instala y se ejecuta tan rápido como la aplicación ansiosa. Cuál es el mejor depende de si usted necesita un comienzo rápido o aún funcionamiento.
El problema con una aplicación J2EE es que no puede funcionar así. Sólo mantiene una conexión durante la duración de la solicitud. Esto significa que debe crear las instrucciones preparadas cada vez que se ejecuta la solicitud. Esto no es tan eficiente como el enfoque de cliente de grasa donde las sentencias preparadas se crean una vez, en lugar de en cada solicitud. Los proveedores de J2EE han notado esto y han diseñado la agrupación de conexiones para evitar esta desventaja de rendimiento.
Cuando el servidor J2EE da a su aplicación una conexión, no le está dando la conexión real; usted está consiguiendo un envoltorio. Puede verificar esto mirando el nombre de la clase para la conexión que se le da. No será una conexión de base de datos JDBC, será una clase creada por su servidor de aplicaciones. Normalmente, si llama cerca de una conexión, entonces el controlador jdbc cierra la conexión. Queremos que la conexión sea devuelta a la agrupación cuando close sea llamada por una aplicación J2EE. Hacemos esto haciendo una clase de conexión de proxy jdbc que parece una conexión real. Tiene una referencia a la conexión real. Cuando invocamos cualquier método en la conexión entonces el proxy reenvía la llamada a la conexión real. Pero, cuando llamamos a métodos como cerrar en lugar de llamar cerca de la conexión real, simplemente devuelve la conexión al grupo de conexiones y luego marca la conexión proxy como no válida, de modo que si la utiliza de nuevo, obtendremos una excepción.

El empaquetado es muy útil, ya que también ayuda a los implementadores de servidores de aplicaciones J2EE a agregar soporte para sentencias preparadas de una manera sensible. Cuando una aplicación llama Connection.prepareStatement, el controlador devuelve un objeto PreparedStatement. La aplicación, a continuación, mantiene el identificador mientras tiene la conexión y lo cierra antes de que cierra la conexión cuando finaliza la solicitud. Sin embargo, después de que se devuelva la conexión a la agrupación y más tarde reutilizada por la misma u otra aplicación, idealmente, queremos que se devuelva el mismo PreparedStatement a la aplicación.

J2EE PreparedStatement Cache

J2EE PreparedStatement Cache se implementa utilizando un caché dentro del gestor de la agrupación de conexiones del servidor J2EE. El servidor J2EE mantiene una lista de instrucciones preparadas para cada conexión de base de datos en el grupo. Cuando una aplicación llama a prepareStatement en una conexión, el servidor de aplicaciones comprueba si dicha sentencia fue preparada previamente. Si lo fuera, el objeto PreparedStatement estará en el caché y éste será devuelto a la aplicación. Si no, la llamada se pasa al controlador jdbc y se agrega el objeto query / preparedstatement en esa caché de conexiones.

Necesitamos una caché por conexión, porque así funcionan los controladores jdbc. Cualquier estado de preparación devuelto es específico de esa conexión.
Si queremos aprovechar esta caché, las mismas reglas se aplican como antes. Necesitamos usar consultas parametrizadas para que coincidan con las ya preparadas en la caché. La mayoría de los servidores de aplicaciones le permitirán ajustar el tamaño de este caché de instrucciones preparado.

En resumen

En conclusión, debemos utilizar consultas parametrizadas con declaraciones preparadas. Esto reduce la carga de la base de datos permitiéndole reutilizar planes de acceso ya preparados. Esta caché es de toda la base de datos, por lo que si puede organizar todas sus aplicaciones para utilizar SQL parametrizado similar, mejorará la eficiencia de este esquema de almacenamiento en caché como una aplicación puede aprovechar las sentencias preparadas utilizadas por otra aplicación. Esta es una ventaja de un servidor de aplicaciones porque la lógica que accede a la base de datos debe centralizarse en una capa de acceso a datos (ya sea un mapeador OR, un beans de entidad o un JDBC recto).

Por último, el uso correcto de sentencias preparadas también le permite aprovechar la caché de instrucciones preparada en el servidor de aplicaciones. Esto mejora el rendimiento de su aplicación, ya que la aplicación puede reducir el número de llamadas al controlador JDBC mediante la reutilización de una llamada de sentencia previamente preparada. Esto hace que sea competitivo con los clientes de grasa en función de la eficiencia y elimina la desventaja de no poder mantener una conexión dedicada.
Si utiliza sentencias preparadas parametrizadas, mejora la eficiencia de la base de datos y del código alojado de su servidor de aplicaciones. Ambas mejoras permitirán que su aplicación mejore su rendimiento.

No hay comentarios:

Publicar un comentario

Por favor deja tu comentario, es valioso.