lunes, julio 27, 2009

Jugando con PreparedStatement

Hace tiempo que he oido que los PreparedStatement de java son más eficientes que los Statement, así que me he puesto a jugar con el tema y verlo por mi mismo.

Mi primera prueba un pequeño fracaso. Cogí una base de datos MySQL e hice 1000 inserciones seguidas con Statement, componiendo la SQL y luego otras 1000 con PreparedStatement. Por igualdad de condiciones, en ambos casos partía de la base de datos vacía. Pues bien, no solo no había diferencia apreciable, sino que además en ocasiones las Statement tardaban menos.

Me puse a investigar y descubro que hay dos requisitos indispensables para que los PreparedStatement sean más efectivos:

  1. El servidor de base de datos debe soportar los Prepared Statement, que realmente no son cosa de java, sino del servidor de base de datos.
  2. El conector/driver que se use también debe soportarlos.

Así que a ver si mi versión de driver y de servidor MySQL lo soportan … pues sí, lo soportan. Sigo investigando y descubro que al conector de MySQL hay que decirle en la configuración que use PreparedStatement del lado del servidor, cosa que por defecto no hace. Esto se consigue poniendo en la cadena de conexión algo como

conexion = DriverManager.getConnection(
"jdbc:mysql://localhost/basedatos?useServerPrepStmts=true", "usuario", "password");

Pues nada, pruebo otra vez y esta vez sí que es ligeramente más rápido el PreparedStatement, pero no tanto. El tiempo que tarda cada vez el programa en correr es distinto y aunque el PreparedStatement suele tardar algo menos que el Statement, a veces no ocurre así. Estamos hablando de entre 15 y 18 segundos, en el que el PreparedStatement apenas le saca un segundo al Statement y no siempre.

No puede ser que eso sea así. Algo debo estar haciendo mal. Sigo leyendo y encuentro un sitio (no recuerdo cual) que dice que algunas bases de datos no mantienen los PreparedStatement después del commit. No veo nada en MySQL que indique esto, pero me decido a hacer la prueba. Hago que la conexión no sea auto-commit y pongo el commit fuera del bucle de inserción. Las mil inserciones en cada caso quedan "en el aire" hasta que se hace un commit al finalizar cada bucle.

Esta vez el PreparedStatement sigue sacando su aproximadamente un segundo sobre el Statement … pero el tiempo total se reduce drásticamente, tanto para Statement como para PreparedStatement, a unos 3 ó 4 segundos. El hecho de no hacer commit hasta el final, independientemente del tipo de Statement usado, hace que el tiempo total baje de 15 segundos a 3. Y el segundo a favor de PreparedStatement se convierte ahora sí, en algo apreciable. Seguramente, todo lo que estaba midiendo antes eran los accesos reales a disco duro para realizar realmente las inserciones de una en una con el auto-commit. En el segundo caso, la medida ya sí debe ser mejor comparativa enrtre PreparedStatement y Statement.

Pero … ¿y en una aplicación real?. ¿Podemos permitirnos el lujo de no hacer commit hasta que hayamos hecho un conjunto más o menos grandes de inserciones?. Yo creo que normalmente no, así que ¿realmente merece la pena el uso de PreparedStatement por temas de eficiencia?. Pues supongo que depende entonces de los casos.

Por ejemplo, se me ocurre que si una sola inserción es más compleja que la mia e involucra varias tablas, podemos dejar el commit para cuando se haya hecho la inserción en todas las tablas implicadas. Esto tiene cierta lógica, la inserción se completa totalmente o no se completa en absoluto y el commit confirma varias inserciones, una en cada tabla. La diferencia de velocidad entre PreparedStatement y Statement será mayor cuantas más tablas haya implicadas.

También se me ocurre, aunque sólo sea una tabla, que si tenemos garantía de que van a ir insertándose muchos registros con mucha frecuencia, como para comprometer la velocidad con la que somos capaces de insertarlos en base de datos, podemos usar PreparedStatement y un pequeño Timer que haga commit cada cierto tiempo. Una idea rebuscada, pero sí se de casos donde podemos aplicarla. A veces tenemos equipos hardware "escupiendo" información a toda velocidad, información que necesitamos almacenar en base de datos.

En resumen, PreparedStatement posiblemente sí es más eficiente, pero para sacarle realmente rendimiento, no basta con usarlo sin más. Hay que tener en cuenta más cosas, como si tenemos el auto-commit a true o con qué frecuencia necesitamos hacer los commit. Si se investiga un poco, también ser verá que hay otras variables a tener en cuenta, como temas de caché en la configuración del servidor de base de datos o del conector, pero soy demasiado vago para investigar eso sin necesidad real.

Aparte de todo esto, a favor de los PreparedStatement, también está el tema de seguridad. Con PreparedStatement evitamos tener que chequear y "escapar" los caracteres conflictivos en las cadenas de texto que vamos a insertar en base de datos. Si vamos a insertar un nombre de usuario que pedimos al usuario y a este se le ocurre poner una comilla simple, por ejemplo "O’Donnell", podemos meterla tal cual en un PreparedStatement, pero necesitamos "escapar" la comilla antes de usarla en un Statement.

Fuente

3 comentarios:

  1. como puedo saber si mi driver de bd y mi gestor de bd soportan las PreparedStatement?

    ResponderBorrar
  2. Realmente no lo sé del todo, pero lo que sí te puedo decir es que con Java he trabajado con MySQL5+, SQLServer 2005+, PostgreSQL8+ y los drivers respectivos para cada DBMS en cada versión mencionada funcionan con sentencias preparadas

    ResponderBorrar
  3. Realmente no lo sé del todo, pero lo que sí te puedo decir es que con Java he trabajado con MySQL5+, SQLServer 2005+, PostgreSQL8+ y los drivers respectivos para cada DBMS en cada versión mencionada funcionan con sentencias preparadas

    ResponderBorrar