13 abr 2010

Top (casi) 20 Mejores Practicas de MySQL

Aqui les traigo parte de la traducción de un artículo que me gustó mucho del blog Nettuts+ sobre MySQL. Lo traduje yo mismo (cero google) y pueden leer su versión original aquí.

Las operaciones con bases de datos a menudo tienden a ser los principales embotellamientos para la mayoría de mas aplicaciones web hoy en día.No es solo el DBA (administrador de base de datos) que debe preocuparse por estos asuntos de desempeño. Nosotros como programadores necesitamos hacer nuestra parte en estructurar las tablas apropiadamente, escribiendo consultas optimizadas y mejores códigos. Aqui están algunas técnicas de MySQL para programadores.

1. Optimiza tus consultas para la caché de consultas

La mayoría de los servidores MySQL tienen habilitada la caché de consultas. Es uno de los métodos más efectivos de mejorar el rendimiento. Ya que es manejado silenciosamente por la máquina de base de datos. Cuando la misma consulta es ejecutada varias veces, el resultado es recogido de la caché, lo cual es bastante rápido.El problema principal es que, es tan fácil y oculto para el programador, que la mayoría de nosotros tiende a ignorarlo. De hecho, algunas cosas que hacemos pueden evitar que la caché de consulta ejecute su tarea.
// la cache de consulta NO funciona
$r = mysql_query("SELECT username FROM user WHERE signup_date >= CURDATE()");
// la cache de consulta FUNCIONA!
$hoy = date("Y-m-d");
$r = mysql_query("SELECT username FROM user WHERE signup_date >= '$hoy'");
La razon por la que la caché de consulta no funciona en la primera línea es debido al uso de la función CURDATE(). Esto se aplica a todas las funciones no-determinísticas como NOW() y RAND(), etc. Ya que el valor de retorno de la función puede variar, MySQL decide deshabilitar la caché de consultas para esa consulta. Sólo necesitamos añadir una línea extra de PHP antes de la consulta para evitar que esto suceda.

2. Usa LIMIT 1 Cuando busques una sola fila

A veces cuando estás consultando tus tablas, ya sabes que estas buscando sólo una fila. Tal vez estas recogiendo un solo registro, o tal vez sólo estas verificando la existencia de cualquier número de registro que satisfagan tu cláusula WHERE. En esos casos, añadir LIMIT 1 a tu consulta puede incrementar el desempeño. De esta manera el motor de la base de datos dejará de buscar registros luego de encontrar 1, en vez de ir a través de toda la tabla o índice.
// ¿Tengo algun usuario de Barquisimeto?
// Mala Idea:
$r = mysql_query("SELECT * FROM usuario WHERE ciudad = 'Barquisimeto'");
if (mysql_num_rows($r) > 0) {
// ...
}

// Buena Idea =D :
$r = mysql_query("SELECT 1 FROM usuario WHERE ciudad = 'Barquisimeto' LIMIT 1");
if (mysql_num_rows($r) > 0) {
// ...
}

3. Indexa los campos de búsqueda

Los indices no son solo paralas claves primarias y las claves de unicidad. Si hay alguna columna en tu tabla por la cual realizarás una búsqueda, casi siempre deberías indexarlas.
Como pueden ver, esta regla también aplica a las busquedas de cadenas parciales como "apellido LIKE 'a%'". Cuando se busca por el proncipio de una cadena, MySQL puede utilizar el índice de esa columna.

También debes entender qué tipo de búsquedas no pueden usar índices regulares. Por ejemplo, si buscar en una palabra (Ej.: "WHERE contenido LIKE '%manzana%' ", no verás un beneficio de un índice normal. Estarás mejor usando la búsqueda de texto completo de MySQL o construyendo tu propia solución de índice.

4. Evita el SELECT *

Mientras más datos se leen de las tablas, más lenta será la consulta. El tiempo que tardan las operaciones en el disco duro aumenta. Tambien, cuando el servidor de la base de datos esta separado del servidor web, tendrás mas retrasos de red debido a que los datos tienen que ser transferidos entre los servidores.

Es un buen hábito esperificar siempre las columnas que necesitas cuando haces tus SELECT.
// evitar
$r = mysql_query("SELECT * FROM user WHERE user_id = 1");
$d = mysql_fetch_assoc($r);
echo "Bienvenido {$d['username']}";
// mejor asi
$r = mysql_query("SELECT username FROM user WHERE user_id = 1");
$d = mysql_fetch_assoc($r);
echo "Bienvenido {$d['username']}";
// la diferencia es mucho mayor con resultados mas grandes

5. Casi siempre ten un campo ID

En cada tabla ten un campo ID que sea clave primaria con auto incremento y entero. Preferiblemente que sea sin signo, ya que su valor no puede ser negativo. Incluso si tienes una tabla usuarios que tiene un campo nombre_de_usuario con clave de unicidad, no la hagas tu clave primaria. Los campos VARCHAR como claves primarias son mas lentos. Tendras una mejor estructura en tu código refiriendote a todos los usuarios por sus ID's internamente. También hay operaciones "detras de las camaras" ejecutadas por el motor de MySQL, que usan la clave primaria internamente. Esto se vuelve mas importante a medida que la base de datos sea mas complicada (clusters, particiones, etc).
Una posible excepción para la regla son las tablas asociadas, usadas para las relaciones muchos-a-muchos entre 2 tablas. Por ejemplo una tabla "etiquetas_post" que contiene 2 columnas: id_post, id_etiqueta, que se una para las relaciones entre dos tablas llamadas "post" y "etiquetas". Esta tablas pueden contener una clave primaria que contenga ambos campos.

6. Usa NOT NULL siempre que puedas

A menos que tengas una razón especifica para usar un valor NULL, siempre debes designar tus campos como NOT NULL. Primero que todo, pregúntate si existe alguna diferencia entre tener una cadena vacía vs. un valor nulo (para campos enteros: 0 vs. nulo). Si no hay razon para tenerlos, no necesitas un campo NULL (¿Sabias que Oracle considera el valor nulo y el vacío como lo mismo?. Los campos NULL requieren un espacio adicional y pueden añadir complejidad a la comparación de sentencias. Solo evitalos cuando puedas. Sin embargo, entiendo que algunas personas puedan tener razones específicas para usarlo, lo cual no siempre es malo. De la documentación de MySQL:
"Las columnas NULL requieren espacio adicional en el registro sin importar si su valor es NULL o no. Para las tablas MyISAm, cada columna NULL toma un bit extra, redondeado por encima al byte más cercano.”

7. Las tablas de longitud fija (estáticas) son más rapidas

Cuando cada columna de una tabla tiene una "longitud fija", la tabla se considera estática o de longitud fija. Algunos ejemplos de tipos de campos que no tienen longitud fija son: VARCHAR, TEXT, BLOB. Si tu incluyes al menos 1 de esos tipos de datos, la tabla dejará de ser de longitud fija y será manejada de manera distinta por el motor de MySQL. Las tablas de longitud fija pueden mejorar el desempeño porque es más rápido para el motor MySQL buscar a través de los registros. Cuando quiere leer una fila específica en la tabla, puede calcular rapidamente su posición. Si el tamaño de la fila no es fijo, cada vez que debe hacer una búsqueda, consultará el índice de la clave primaria. También son más faciles de manejar en caché y de reconstruir después de un imprevisto. Pero tambien pueden tomar mas espacio. Por ejemplo, si conviertes un campo de VARCHAR(20) a CHAR(20), siempre tomara 20 bytes de espacio sin importar su valor. Utilizando la técnica del "Particionamiento Vertical" puedes separar las columnas de longitud fija a una tabla aparte. Lo cual nos lleva a:

8. Particionamiento Vertical

Esto consiste en dividir la estructura de tu tabla de una forma vertical por razones de optimización.
  
Ejemplo 1: Tal vez tengas una tabla de usuarios que contenga la dirección del hogar, eso no se lee a menudo. Puedes escoger dividir tu tabla y almacenar la información de la dirección en una tabla aparte. De esta manera tu tabla maestro de usuarios será encogida. Como ya sabes, las tablas mas pequeñas se desenvuelven mas rápido.  

Ejemplo 2: Tienes un campo "ultima_visita" en tu tabla. Se actualiza cada vez que un usuario se loguea en el sitio web. Pero cada actualización en una tabla hace que la caché de consultas de esa tabla sea desechada. Puedes poner ese campo en otra tabla para mantener las actualizaciones de tus usuarios al minimo. Pero también necesitaras asegurarte de que no necesites constantemente unir esas 2 tablas después del particionamiento ó tal vez tengas que sufrir una decadencia de desempeño.

9. Divide las grandes consultas con DELETE ó INSERT

Si necesitas ejecutar una consulta grande con DELETE ó INSERT en un sitio web en vivo, necesitas ser cuidadoso de no perturbar el tráfico web. Cuando una gran consulta como esa es ejecutada, puede bloquear tus tablas y llevar a tu aplicación web a que se detenga. Apache ejecuta muchos procesos/hilos paralelos. Por lo tanto funciona de manera eficiente cuando los scripts terminan de ejecutarse lo más pronto posible, así los servidores no experimentan muchas conexiones abiertas y procesos a la vez que consumen recursos, especialmente memoria. Si terminas bloqueando tus tablas por un período de tiempo extendido (algo como 30seg. o más), en un sitio web de alto tráfico, harás que un proceso o consulta se apile, lo cual podría tardar mucho tiempo en desapilarse o incluso podría derrumbar tu servidor web.

Si tienes algún tipo de script de mantenimiento que necesite eliminar un gran número de registros, solo una la cláusula LIMIT para hacerlo por pequeños lotes para evitar esta congestión.
while (1) {
   mysql_query("DELETE FROM sesiones WHERE fec_sesion <= '2010-10-01' LIMIT 10000");    
   if (mysql_affected_rows() == 0) {           
       // termino de eliminar           
       break;          
       }     
  // puedes incluso pausarlo un poco  
  usleep(50000);    
} 

10. Escoge el motor de almacenamiento correcto

Los dos motores de almacenamiento principales de MySQL con MyISAM y InnoDB. Cada uno tienes sus propios pros y contras.

MyISAM es bueno para aplicaciones pesadas para leer, pero no es muy amplio cuando hay muchas escrituras. Incluso si estas actualizando un campo en una fila, toda la tabla se bloquea, y ningún otro proceso puede incluso leerla hasta que la consulta haya finalizado. MyISAM es muy rapido calculando consultas de tipo SELECT COUNT(*).

InnoDB tiende a ser un motor de almacenamiento más complicando y puede ser mas lento que MyISAM para las aplicaciones más pequeñas. Pero soporta el bloqueo basado en filas, el cual escala mejor. También soporta algunas características mas avanzadas como las transacciones.

- Motor de almacenamiento MyISAM

- Motor de almacenamiento InnoDB

11- Usa un mapeador de objeto relacional

Usando un ORM (del inglés: Object Relational Mapper), puedes obtener ciertos beneficios de desempeño. Todo lo que un ORM hace, puede ser codificado manualmente también. Pero esto puede significar mucho trabajo extra y requiere de un alto nivel de experiencia. Los ORM pueden ser grandiosos para el "cargado perezoso". Significa que puedes recoger valores, solo como los necesiten.

Pero debes tener cuidado con ellos o puedes terminar creando muchas mini-consultas que pueden reducir el desempeño. Los ORM tambien pueden lotear tus consultas en transacciones, las cuales operan mucho mas rápido que enviar consultas individuales a la base de datos. Actualmente, mi ORM favorito para PHP es Doctrine. Escribí un articulo sobre cómo instalar Doctrine con CodeIgniter.

12. Sé cuidadoso con las conexiones persistentes

Las conexiones persistentes tienen como fin reducir el trabajo de recrear las conexiones a MySQL. Cuando se crea una conexión persistente, se mantendrá abierta incluso después de que el script se termina de ejecutar. Ya que Apache se reutiliza sus procesos hijos, la próxima vez que el proceso corra para un nuevo script, utilizará la misma conexión a MySQL.

- mysql_pconnect() de PHP.

Suena muy bien en teoría. Pero desde mi experiencia personal (y la de muchos otros), esta característica parece no valer la pena. Puedes tener serios problemas con los límites de conexión, problemas de memoria y así sucesivamente.Apache corre extramamente paralelo, y crea muchos procesos hijos. Esta es la principal razon por la que las conexiones persistentes no funcionan muy bien en este entorno. Antes de considerar usar la función mysql_pconnect(), consulta al administrador de tu sistema.

0 comentarios:

Publicar un comentario

Soluciones Informáticas

Sigueme en Twitter