Вы находитесь на странице: 1из 13

Boozox 20 Consejos coge la informacin para Mejorar tu MySQL que quizs no conocas

23 de agosto del 2010 Escrito por Alex Barros

y corre

Las operaciones sobre bases de datos suelen ser los principales cuellos de botella en las aplicaciones web. Por tanto es tarea de los programadores estructurar apropiadamente, escribir peticiones apropiadas, y programar mejor cdigo. A continuacin mostramos algunas tcnicas de optimizacin MySQL.

1. Optimiza tus peticiones para la cach.


La mayora de servidores MySQL tienen habilitado el sistema de cach. Es uno de los mtodos ms efectivos para mejorar el rendimiento, que vienen de la mano del motor de base de datos. Cuando la misma peticin se ejecuta varias veces, el resultado se obtiene de la cach, que resulta mucho ms rpida. El problema es que, es tan sencillo y transparente para el programador, que la mayora de nosotros tendemos a ignorarlo. Algunas cosas que hacemos de hecho pueden evitar que la cach haga su trabajo.
. //La cache NO funciona . $r = mysql_query("SELECT nombre FROM usuarios WHERE registro >= CURDATE()"); . . // La cach s funciona . $hoy = date("Y-m-d"); . $r = mysql_query("SELECT nombre FROM usuarios WHERE registro >= $hoy");

la razn por la que no funciona en el primer caso es por el uso de CURDATE(). Puede aplicarse a todas las funciones no deterministas, como NOW() y RAND(). Dado que el resultado retornado por la funcin puede cambiar, MySQL decide deshabitar la cach en esa consulta.

2. Usa EXPLAIN en tus consultas SELECT


Utilizar la palabra clave EXPLAIN te dar muchos detalles internos de lo que hace MySQL para ejecutar tu consulta. Esto te puede ayudar a detectar los cuellos de botella y otros problemas con tu query o la estructura de la tabla.

El resultado de una query EXPLAIN te mostrar los ndices que se estn utilizando, cmo se est explorando la tabla, cmo se est ordenando, etc Coge una consulta SELECT (preferiblemente una compleja, con uniones), y aade la palabra EXPLAIN al principio del todo. Puedes utilizar por ejemplo PhpMyAdmin para esto. Te devolver los resultados en una sencilla tabla. Por ejemplo, pongamos que me he olvidado de poner un ndice a una columna, con la que estoy ejecutando

Despus de aadir el ndice al campo group_id:

Ahora en lugar de escanear 7883 filas, slo escanear 9 y 16 filas de las dos tablas.

3. Usa LIMIT 1 Cuando slo quieras una nica fila.


A veces, cuando ests realizando consultas a tus tablas, ya sabes que slo necesitas una nica fila. En estos casos debes solicitar a la base de datos un nico resultado, o de lo contrario comprobar todos y cada uno de las coincidencias de la clusula WHERE. En estos casos, aadir LIMIT 1 a tu query puede mejorar significativamente la velocidad. De esta forma la base de datos dejar de escanear resultados en el momento que encuentre uno, en lugar de recorrer toda la tabla o un ndice.
. // Tengo usuarios de Valencia?

. . // lo que NO hay que hacer: . $r = mysql_query("SELECT * FROM user WHERE ciudad = Valencia"); . if (mysql_num_rows($r) > 0) { . . } . . // mucho mejor: . $r = mysql_query("SELECT 1 FROM user WHERE ciudad = Valencia LIMIT 1"); . if (mysql_num_rows($r) > 0) { . . } // //

4. Indexa los campos de Bsqueda


Los ndices no son slo para las claves primarias o las claves nicas. Si en tu tabla hay columnas sobre las que vas a realizar bsquedas, deberas indexarlas casi siempre.

Como puedes ver, esta regla se aplica tambin a las bsquedas parciales como apellido LIKE a%. Cuando se busca desde el comienzo de la cadena, MySQL es capaz de utilizar el ndice de esta columna. Deberas tambin comprender en qu tipos de bsqueda no pueden utilizarse ndices normales. Por ejemplo, cuando buscas una palabra dentro de un texto (p.e. WHERE contenido LIKE %manzana%), no observars ningn beneficio con un ndice normal. En este caso sera mejor utilizar una bsqueda FULLTEXT o construir tu propia solucin de indexacin.

5. Indexa, y utiliza el mismo tipo de columna para los Join


Si tu aplicacin contiene muchas sentencias JOIN debes asegurarte de que las columnas que unes estn indexadas en ambas tablas. Esto afecta en cmo MySQL optimiza internamente las operaciones JOIN. Adems, las columnas que vas a unir deben ser del mismo tipo. Por ejemplo, si ests uniendo una columna de tipo DECIMAL con una columna de tipo INT de otra tabla, MySQL no ser capaz de usar al menos uno de los dos ndices. Incluso la codificacin de caracteres necesita ser del mismo tipo para las columnas de tipo String.
. // buscando compaias en mi ciudad

. $r = mysql_query("SELECT nombre_companyia FROM usuarios . . . . // ambas columnas ciudad deben estar indexadas . // y ambas deberan ser del mismo tipo y codificacin de caracteres . // o MySQL tendr que hacer un escaneo total de las tablas LEFT JOIN companyias ON (usuarios.ciudad = companyias.ciudad) WHERE usuarios.id = $user_id");

6. No uses ORDER BY RAND()


ste es uno de esos truquillos que suenan muy bien a primera vista, y donde muchos programadores novatos suelen caer. Puede que no hayas cado en la cuenta del increble cuello de botella que se puede provocar si utilizas esta tcnica en tus peticiones. Si en verdad necesitas tablas aleatorias para tu resultado, hay formas mucho mejores de hacerlo. Est claro que ocuparn ms cdigo, pero estars previniendo un posible embotellamiento que aumenta exponencialmente a medida que tu contenido crece. El problema es que MySQL tendr que ejecutar RAND() (que requiere de potencia de procesado) para cada una de las filas antes de ordenarlas y devolver una simple fila.
. // la forma de NO hacerlo: . $r = mysql_query("SELECT nombreusuario FROM usuarios ORDER BY RAND() LIMIT 1"); . . // mucho mejor: . . $r = mysql_query("SELECT count(*) FROM usuarios"); . $d = mysql_fetch_row($r); . $rand = mt_rand(0,$d[0] 1); . . $r = mysql_query("SELECT nombreusuario FROM usuarios LIMIT $rand, 1");

De forma que seleccionas un nmero aleatorio inferior a la cantidad de resultados y lo usas como el desplazamiento en la clusula LIMIT.

7. Evita SELECT *
Cuanta ms informacin se lee de las tablas, ms lenta se ejecutar la peticin SQL. Aumenta el tiempo que toma para las operaciones en disco. Adems cuando el servidor de bases de datos est separado del servidor web, tendrs mayores retrasos de red debido a que la informacin tiene que ser transferida entre ambos servidores. Es un buen hbito especificar siempre las columnas que necesitas cuando ests haciendo un SELECT.
. // preferible no hacer: . $r = mysql_query("SELECT * FROM usuarios WHERE id_usuario = 1"); . $d = mysql_fetch_assoc($r); . echo "Bienvenido {$d['nombreusuario']}"; . . // mejor: . $r = mysql_query("SELECT nombreusuario FROM usuarios WHERE id_usuario = 1"); . $d = mysql_fetch_assoc($r);

. echo "Bienvenido {$d['nombreusuario']}"; . . // las diferencias son mucho ms significativas cuanta ms informacin haya

8. Ten casi siempre un campo identificativo


Ten en cada tabla una columna id con las propiedades PRIMARY KEY, AUTO_INCREMENT y alguna de las variantes de INT. Adems es preferible que sea UNSIGNED (sin signo) ya que el valor nunca podr ser negativo. Incluso si tienes una tabla de usuarios cuyos nombres de usuario sean nicos, no los uses como clave primaria. Los campos VARCHAR como clave primaria son muy lentos. Y tendrs una mejor estructura en tu cdigo si referencias a todos tus usuarios por sus ids internamente. Tambin hay una serie de operaciones internas que realiza el motor de MySQL por s mismo, que usa la clave primaria, lo cual se hace incluso ms importante cuanto ms compleja sea la base de datos (clusters, particionados, etc). Una posible excepcin a la regla son las tablas de asociacin, utilizadas en las relaciones muchos a muchos entre dos tablas. Por ejemplo, una tabla etiquetas_articulos que contiene dos columnas: id_articulo, id_etiqueta, que es utilizada para las relaciones entre las tablas articulos y etiquetas. Estas tablas pueden tener una clave PRIMARY que contenga ambos campos.

9. Usa ENUM antes que VARCHAR


Las columnas de tipo ENUM son muy rpidas y compactas. Internamente se almacenan como TINYINT, aunque pueden contener y representar valores de cadenas. Esto las hace un perfecto candidato para algunos campos. Si tienes un campo que contendr slo unos pocos valores distintos, utiliza ENUM en lugar de VARCHAR. Por ejemplo, podra ser una columna llamada estado, y slo unos pocos valores como activo, inactivo, pendiente, caducado, etc De hecho hay una forma de obtener sugerencias del propio MySQL para reestructurar nuestra tabla. Cuando tienes un campo VARCHAR te puede sugerir que cambies sa columna al tipo ENUM. Esto se hace utilizando la llamada a PROCEDURE ANALYSE(). Lo cual nos lleva a:

10. Obtn sugerencias con PROCEDURE ANALYSE()


PROCEDURE ANALYSE() permitir a MySQL analizar la estructura de las columnas y los datos actuales que contienen para retornar ciertas sugerencias que sern de tu inters. Slo es til si hay informacin en las tablas, porque esto toma gran importancia en la toma de decisiones. Por ejemplo, si creaste un campo INT para tu clave primaria, pero no tienes muchas filas, podra sugerirte que uses MEDIUMINT en su lugar. O si estas usando un campo VARCHAR, podra sugerirte que lo conviertas en ENUM, si slo ests escribiendo unos pocos valores. Tambin puedes ejecutarlo pulsando en Propose table structure (proponer estructura de tabla) en la interfaz de PhpMyAdmin, en una de las vistas de tus tablas.

Ten presente que esto son slo sugerencias. Y si tu tabla va a crecer mucho, podran no ser buenas sugerencias a seguir. La decisin es tuya en ltima instancia.

11. Usa NOT NULL si puedes


A no ser que tengas una razn especfica para usar el valor NULL, deberas establecer siempre tus columnas como NOT NULL. En primer lugar, pregntate a t mismo si habra alguna diferencia entre tener una cadena vaca y un valor NULL (o para campos INT: 0 contra NULL). Si no hay problema entre los dos valores, no necesitas un campo NULL. (Sabas que Oracle considera a NULL y una cadena vaca como lo mismo?) Las columnas NULL necesitan espacio adicional y pueden aadir complejidad a tus sentencias de comparacin. Simplemente evtalas siempre que puedas. En cualquier caso, entiendo que en algunos casos muy especficos haya razn para usar columnas NULL, lo cual no es siempre algo malo. Extrado de la documentacin de MySQL: las columnas NULL requieren espacio adicional en la fila a grabar donde los valores son NULL. Para las tablas MyISAM, cada columna NULL toma un bit extra, redondeando hacia arriba al byte ms cercano.

12. Declaraciones preparadas


Existen mltiples beneficios al usar declaraciones preparadas, tanto a nivel de productividad como de seguridad. Las declaraciones preparadas filtran las variables que le pasas por defecto, lo que es perfecto para proteger tu aplicacin contra ataques de inyeccin SQL. Claro que puedes filtrar tus variables manualmente, pero estos mtodos son propensos al error humano y al despiste del programador. Este problema no es tan acentuado cuando se utiliza algn tipo de Framework u ORM. Ya que queramos centrarnos en la productividad, deberamos mencionar los beneficios que ofrece este area. Estos beneficios son ms significativos cuando la misma consulta va a utilizarse varias veces en tu aplicacin. Puedes asignar diferentes valores a una misma declaracin, y MySQL slo tendr que analizarla una vez. Adems, las ltimas versiones de MySQL transmiten declaraciones preparadas de forma binaria nativamente, ms eficientes y que ayudan a reducir los retrasos de red.

Hubo un tiempo en que muchos programadores solan evitar las declaraciones preparadas a propsito, por una nica razn: no estaban siendo cacheadas por la cach de consultas de MySQL. Pero aproximadamente en la versin 5.1, el cacheo de consultas tambin ha sido soportado. Para utilizar declaraciones preparadas en PHP puedes echar un ojo a la extensin mysqli o utilizar una capa de abstraccin de base de datos como PDO.
. // creamos la declaracin preparada . if ($stmt = $mysqli->prepare("SELECT nombre FROM usuarios WHERE ciudad=?")) { . . . . . . . . . . . . . . . . . } $stmt->close(); printf("%s es de %s\n", $nombre, $ciudad); // obtenemos el resultado $stmt->fetch(); // pasamos la variable de resultado $stmt->bind_result($nombre); // ejecutamos $stmt->execute(); // pasamos los parmetros $stmt->bind_param("s", $ciudad);

13. Consultas fuera de buffer


Normalmente cuando ests ejecutando una consulta en un script, ste se esperar a que acabe la ejecucin de esta consulta antes de que pueda continuar. Pero puedes cambiar este comportamiento sacando la consulta fuera del bffer. Puedes echar un ojo a la genial explicacin que hacen en la documentacin de PHP para la funcin the mysql_unbuffered_query(): mysql_unbuffered_query() enva la query SQL a MySQL, sin recuperar ni colocar en bfer las filas de resultado automticamente, como mysql_query() lo hace. Por una parte, esto ahorra una considerable cantidad de memoria con las consultas SQL que producen conjuntos grandes de resultados y se puede empezar a trabajar con el conjunto de resultado inmediatamente despus de que la primera fila ha sido recuperada: no necesita esperar hasta que la consulta SQL completa haya sido ejecutada. Para usar mysql_unbuffered_query() cuando se usan mltiples conexiones con la BD, se necesita indicar el parmetro opcional link_identifier para identificar que conexin se desea utilizar. Sin embargo los beneficios de mysql_unbuffered_query() tienen un precio: no poder usar mysql_num_rows() ni mysql_data_seek() en un conjunto de resultados devuelto por mysql_unbuffered_query(). Tambin tendrs que recuperar todas las filas de resultado de una consulta SQL sin bfer antes de poder enviar una nueva consulta SQL a MySQL.

14. Almacena las direcciones IP como UNSIGNED INT

Muchos programadores crearan un campo VARCHAR(15) sin darse cuenta de que pueden almacenar las direcciones IP como nmeros enteros. Cuando usas un INT slo haces uso de 4 bytes en la memoria, y cuenta adems con un tamao fijo en la tabla. Pero hay que asegurarse de que la columna sea UNSIGNED INT (entero sin signo) porque las direcciones IP hacen uso de todo el rango de 32 bits sin signo. En tus consultas puedes utilizar la funcin INET_ATON() para convertir una direccin IP en entero, e INET_NTOA() para hacer lo contrario. Tambin existen funciones parecidas en PHP llamadas ip2long() y long2ip().
. $r = "UPDATE users SET ip = INET_ATON({$_SERVER['REMOTE_ADDR']}) WHERE user_id = $user_id";

15. Las tablas de tamao fijo (Estticas) son ms rpidas


Cuando cada una de las columnas en una tabla es de tamaa fijo (fixed-length), la tabla entera se considera esttica o de tamao fijo. Algunos ejemplos de tipos de columna que NO son de tamao fijo son: VARCHAR, TEXT, BLOB. Si incluyes slo uno de estos tipos de columna, la tabla dejar de ser de tamao fijo y tendr que ser tratada de forma distinta por el motor de MySQL. Las tablas de tamao fijo pueden incrementar la productividad porque para el motor de MySQL es ms rpido buscar entre sus registros. Cuando quiere leer una fila en concreto de la tabla, puede calcular rpidamente la posicin que ocupa. Si el tamao de fila no es fijo, cada vez que tiene que buscar, ha de consultar primero el ndice de la clave primaria. Tambin resultan ms sencillas de cachear, y de reconstruir despus de un accidente. Pero por otra parte tambin podran ocupar ms espacio. Por ejemplo, si conviertes un campo VARCHAR(20) en CHAR(20), siempre ocupar 20 bytes en la memoria independientemente de lo que contenga. Usando tcnicas de Particionado Vertical, puedes separar las columnas de tamao variable en una tabla aparte. Lo cual nos lleva a:

16. Particionado Vertical


El particionado vertical es el acto de separar la estructura de tu tabla de forma vertical por razones de optimizacin. Ejemplo 1: Seguramente tendrs una tabla de usuarios que contiene una direccin postal, la cual no se utiliza muy a menudo. Aqu podras dividir la tabla y almacenar las direcciones en una tabla separada. De esta forma tu tabla de usuarios principal tendra un tamao ms ajustado. Como sabes, cuanto ms pequeas ms rpidas son las tablas. Ejemplo 2: Tienes un campo de ultimo_acceso en tu tabla. Se actualiza cada vez que un usuario accede a tu pgina. Pero cada acceso hace que la cache de consultas de esa tabla se libere. Lo que puedes hacer es colocar este campo en otra tabla para que las modificaciones en tu tabla de usuarios se mantenga al mnimo. Pero tambin tienes que asegurarte de que no necesitas juntar las dos tablas constantemente despus del particionado o sufrirs una cada en el rendimiento, justo lo contrario a lo que buscbamos.

17. Divide las consultas DELETE o INSERT grandes

Si necesitas ejecutar una consulta DELETE o INSERT que sea grande en una pgina web activa, tienes que tener cuidado de no alterar el trfico web. Cuando una consulta grande como esas se ejecuta, puede bloquear tus tablas y paralizar tu aplicacin web momentaneamente. Apache ejecuta muchos procesos/hilos paralelamente. De ah que funcione mucho ms eficientemente cuando los scripts dejan de ejecutarse tan pronto como es posible, para que los servidores no experimenten muchas conexiones abiertas y procesos de una que consumen recursos, especialmente memoria primaria. Si en algn momento bloqueas tus tablas en un periodo largo (como 30 segundos o ms), en una web con mucho trfico, causars un apilamiento de procesos y consultas, que llevar mucho tiempo de concluir o que incluso podra estropear tu servidor web. Si tienes algn script de mantenimiento que tiene que borrar una gran cantidad de filas, simplemente utiliza la clusula LIMIT para hacerlo en porciones ms pequeas y as evitar la congestin.
. while (1) { . . . . . . . . } } // incluso viene bien parar un poco usleep(50000); mysql_query("DELETE FROM logs WHERE log_date <= 2009-10-01 LIMIT 10000"); if (mysql_affected_rows() == 0) { // finalizado el borrado break;

18. Las columnas pequeas son ms rpidas


En los motores de bases de datos, la memoria en disco probablemente sea el cuello de botella ms significativo. En trminos de productividad, mantener las cosas reducidas y ms compactas suele ayudar a reducir la cantidad de transferencia desde disco. La documentacin de MySQL tiene una lista de Requerimientos de Almacenamiento para todos los tipos de dato. Si est previsto que una tabla tenga muy pocos registros, no hay razn para usar un INT para la clave primaria, en lugar de un MEDIUMINT, SMALLINT o incluso en algunos casos TINYINT. Y si no necesitas el componente del tiempo, puedes utilizar DATE en lugar de DATETIME. Simplemente, debes asegurarte de que dejas espacio razonable para poder crecer, o podras acabar como Slashdot.

19. Escoge el motor de almacenamiento adecuado


Los dos principales motores en MySQL son MyISAM y InnoDB, Cada uno tiene sus pros y sus contras. MyISAM is adecuado para aplicaciones con mucha lectura, pero no escala cuando hay muchas escrituras. Incluso si ests editando un campo de una fila, la tabla completa se bloquea, y ningn otro proceso puede siquiera leer hasta que la consulta ha finalizado. MyISAM es muy rpido calculando consultas de tipo SELECT COUNT(*). Inno DB tiende a ser un motor ms complicado y puede ser ms lento que MyISAM para la mayora de aplicaciones pequeas. Pero soporta bloqueo basado en fila, lo cual escala mejor. Tambin soporta algunas caractersticas ms

avanzadas como las transacciones. Motor MyISAM Motor InnoDB

20. Usa un Mapeador de objetos relacionales


Al usar un ORM (Object Relational Mapper), puedes conseguir algunas mejoras en la productividad. Cualquier cosa que puede hacer un ORM, puedes programarlo a mano tambin. Pero podra significar demasiado trabajo extra y requerir de un alto nivel de experiencia. Los ORM son perfectos para la carga perezosa. Significa que se puede obtener valores slo cuando se necesitan. Pero hay que tener cuidado porque podra acabar creando demasiadas mini peticiones que perjudicaran al rendimiento. Los ORM tambin pueden agrupar tus consultas en transacciones, que operan mucho ms rpido que enviar consultas individuales a la base de datos. Actualmente un ORM recomendable para PHP es Doctrine. Puedes leer cmo instalarlo en este artculo (ingls).

21. Ten cuidado con las conexiones persistentes


El objetivo de las Conexiones Persistentes es reducir el esfuerzo de reabrir conexiones con MySQL. Cuando se crea una conexin persistente, queda abierta incluso despus de que el script haya acabado de ejecutarse. Dado que Apache reutiliza sus procesos hijos, el siguiente script reutilizara la misma conexin MySQL. mysql_pconnect en PHP En la teora suena muy bien. Pero desde mi experiencia personal (y la de muchos otros), esta caracterstica acaba por no merecer la pena. Puedes tener serios problemas con los lmites de conexin, problemas de memoria y mucho ms. Apache ejecuta de forma extremadamente paralela, y crea muchos procesos hijo. Esta es la principal razn por la que las conexiones persistentes no funcionan muy bien en este entorno. Antes de que consideres usar la funcin mysql_pconnect(), consulta a tu administrador de sistemas. Extrado y traducido de nettuts por Alex Barros. Fecha del original: 25-11-09. Categorias: MySQL | En la ca-lle-lle veinticua-tro-tro, han escri-to-to un comenta-rio-rio

Hay 7 comentarios, pero nos falta el tuyo


24 agosto 1:21 miquel camps Ha dicho: LIMIT 1 tambien se aplica a las queries DELETE y UPDATE? un saludo!

24 agosto 12:17 Alex Barros Ha dicho: A las queries DELETE y UPDATE se aplica el punto 17: 17. Divide las consultas DELETE o INSERT grandes No es necesario usar LIMIT 1, pero s segmentarlo cuando son muy grandes. 26 agosto 21:27 alejo Ha dicho: Muy buen articulo, siempre es bueno aprender algo mas. Saludos! 10 septiembre 17:52 Claudio Ha dicho: Agregara algo al punto 7. - No es recomendable select * por que obligas al motor a leer la estructura de la tabla para conocer los nombres de los campos. 10 enero 1:22 Rodrigo Ha dicho: Excelente! Muchas gracias por tu tiempo, me ha servido mucho. 9 febrero 18:02 Angel Aparicio Ha dicho: Muy interesante, gracias. Algunas son tan de cajn, como la de poner LIMIT 1 cuando solo quieres un elemento, que es darte de collejas por no haberla estado usando antes. Muy interesantes tambin el puntos del particionamiento vertical. Hace un par de semanas le un artculo sobre como haban optimizado Reddit para poder soportar el trfico que tienen y una de las claves era esa, tablas muy bsicas con la informacin mnima de cada elemento y tablas auxiliares con los detalles de cada cosa. O algo as, cito de memoria (y no encuentro el enlace) 3 marzo 15:10 Pedro Ha dicho: Particionado vertical

Eso se llama 3era forma normal Nombre E-mail Web

Enviar

Notify me of followup comments via e-mail

Search:
Buscar

Bicivalencia Localiza las estaciones de Valenbisi, servicio pblico de bicicletas en Valencia, Espaa. Ver ms Gpsia Descubre y comparte rutas por todo el mundo, tomadas con GPS. Ver ms Imaset Edita tus imgenes de Wordpress con este sencillo plugin. Ver ms

Mi msica es tuya!
Experimentos BSO TMEC Como la vida - Hanna Crash! - Propellerheads Broken Dreams (acstica) - Basement Jaxx

Digo yo que...
sgueme!

Mis fotos de Flickr

w w w .flickr .com

Entradas recientes
20 Consejos para Mejorar tu MySQL que quizs no conocas API de Valenbisi (Servicio de JCDeacaux) Defensa a ultranza del Software Libre Mltiples join y solucin al encadenar LEFT JOINS 5 mximas Por qu cambio el cdigo por la cmara de vdeo? Proyectos on progress Ir a la Valencia Pillow Fight Script PHP para explorar archivos y directorios recursivamente Retomando Gpsia con fuerza

Meta:
Acceder RSS RSS de los comentarios

Respeta el copyleft 2013 Boozox Wordpress theme by Arcsin ,

Вам также может понравиться