2012-01-04 20 views
6

Uso de prueba y error. He descubierto que al eliminar una combinación de la consulta a continuación, se ejecuta alrededor de 30 veces más rápido. ¿Puede alguien explicar por qué sería esto y si es posible optimizar la consulta para incluir la unión adicional sin el golpe de rendimiento?Optimización de la consulta MySQL con costoso INNER JOIN

Esta es una captura de pantalla de la explicación que muestra que el índice no se está utilizando para la tabla uesr_groups.

enter image description here

http://i.imgur.com/9VDuV.png

Ésta es la consulta original:

SELECT `comments`.`comment_id`, `comments`.`comment_html`, `comments`.`comment_time_added`, `comments`.`comment_has_attachments`, `users`.`user_name`, `users`.`user_id`, `users`.`user_comments_count`, `users`.`user_time_registered`, `users`.`user_time_last_active`, `user_profile`.`user_avatar`, `user_profile`.`user_signature_html`, `user_groups`.`user_group_icon`, `user_groups`.`user_group_name` 
FROM (`comments`) 
INNER JOIN `users` ON `comments`.`comment_user_id` = `users`.`user_id` 
INNER JOIN `user_profile` ON `users`.`user_id` = `user_profile`.`user_id` 
INNER JOIN `user_groups` ON `users`.`user_group_id` = `user_groups`.`user_group_id` 
WHERE `comments`.`comment_enabled` = 1 
AND `comments`.`comment_content_id` = 12 
ORDER BY `comments`.`comment_time_added` ASC 
LIMIT 20 

Si quito los "user_groups" unirse a continuación, la consulta se ejecuta 30 veces más rápido como se mencionó anteriormente.

SELECT `comments`.`comment_id`, `comments`.`comment_html`, `comments`.`comment_time_added`, `comments`.`comment_has_attachments`, `users`.`user_name`, `users`.`user_id`, `users`.`user_comments_count`, `users`.`user_time_registered`, `users`.`user_time_last_active`, `user_profile`.`user_avatar`, `user_profile`.`user_signature_html` 
FROM (`comments`) 
INNER JOIN `users` ON `comments`.`comment_user_id` = `users`.`user_id` 
INNER JOIN `user_profile` ON `users`.`user_id` = `user_profile`.`user_id` 
WHERE `comments`.`comment_enabled` = 1 
AND `comments`.`comment_content_id` = 12 
ORDER BY `comments`.`comment_time_added` ASC 
LIMIT 20 

Mis cuadros están por debajo, ¿alguien puede ofrecer alguna idea de cómo evitar un impacto en el rendimiento para la inclusión de la tabla user_groups?

-- 
-- Table structure for table `comments` 
-- 

CREATE TABLE IF NOT EXISTS `comments` (
    `comment_id` int(10) unsigned NOT NULL AUTO_INCREMENT, 
    `comment_content_id` int(10) unsigned NOT NULL, 
    `comment_user_id` mediumint(6) unsigned NOT NULL, 
    `comment_original` text NOT NULL, 
    `comment_html` text NOT NULL, 
    `comment_time_added` int(10) unsigned NOT NULL, 
    `comment_time_updated` int(10) unsigned NOT NULL, 
    `comment_enabled` tinyint(1) NOT NULL DEFAULT '0', 
    `comment_is_spam` tinyint(1) NOT NULL DEFAULT '0', 
    `comment_has_attachments` tinyint(1) unsigned NOT NULL, 
    `comment_has_edits` tinyint(1) NOT NULL, 
    PRIMARY KEY (`comment_id`), 
    KEY `comment_user_id` (`comment_user_id`), 
    KEY `comment_content_id` (`comment_content_id`), 
    KEY `comment_is_spam` (`comment_is_spam`), 
    KEY `comment_enabled` (`comment_enabled`), 
    KEY `comment_time_updated` (`comment_time_updated`), 
    KEY `comment_time_added` (`comment_time_added`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=352 ; 

-- -------------------------------------------------------- 

-- 
-- Table structure for table `users` 
-- 

CREATE TABLE IF NOT EXISTS `users` (
    `user_id` mediumint(6) unsigned NOT NULL AUTO_INCREMENT, 
    `user_ipb_id` int(10) unsigned DEFAULT NULL, 
    `user_activated` tinyint(1) NOT NULL DEFAULT '0', 
    `user_name` varchar(64) CHARACTER SET latin1 NOT NULL, 
    `user_email` varchar(255) NOT NULL, 
    `user_password` varchar(40) NOT NULL, 
    `user_content_count` int(10) unsigned NOT NULL DEFAULT '0', 
    `user_comments_count` int(10) unsigned NOT NULL DEFAULT '0', 
    `user_salt` varchar(8) NOT NULL, 
    `user_api_key` varchar(32) NOT NULL, 
    `user_auth_key` varchar(32) DEFAULT NULL, 
    `user_paypal_key` varchar(32) DEFAULT NULL, 
    `user_timezone_id` smallint(3) unsigned NOT NULL, 
    `user_group_id` tinyint(3) unsigned NOT NULL, 
    `user_custom_permission_mask_id` tinyint(3) unsigned DEFAULT NULL, 
    `user_lang_id` tinyint(2) unsigned NOT NULL, 
    `user_time_registered` int(10) unsigned NOT NULL, 
    `user_time_last_active` int(10) unsigned NOT NULL 
    PRIMARY KEY (`user_id`), 
    UNIQUE KEY `user_email` (`user_email`), 
    KEY `user_group_id` (`user_group_id`), 
    KEY `user_auth_key` (`user_auth_key`), 
    KEY `user_api_key` (`user_api_key`), 
    KEY `user_custom_permission_mask_id` (`user_custom_permission_mask_id`), 
    KEY `user_time_last_active` (`user_time_last_active`), 
    KEY `user_paypal_key` (`user_paypal_key`), 
    KEY `user_name` (`user_name`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=33 ; 

-- -------------------------------------------------------- 

-- 
-- Table structure for table `user_groups` 
-- 

CREATE TABLE IF NOT EXISTS `user_groups` (
    `user_group_id` tinyint(3) unsigned NOT NULL AUTO_INCREMENT, 
    `user_group_name` varchar(32) NOT NULL, 
    `user_group_permission_mask_id` tinyint(3) unsigned NOT NULL, 
    `user_group_icon` varchar(32) DEFAULT NULL, 
    PRIMARY KEY (`user_group_id`), 
    KEY `user_group_permission_mask_id` (`user_group_permission_mask_id`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=8 ; 

-- -------------------------------------------------------- 

-- 
-- Table structure for table `user_profile` 
-- 

CREATE TABLE IF NOT EXISTS `user_profile` (
    `user_id` mediumint(8) unsigned NOT NULL, 
    `user_signature_original` text, 
    `user_signature_html` text, 
    `user_avatar` varchar(64) DEFAULT NULL, 
    `user_steam_id` varchar(64) DEFAULT NULL, 
    `user_ps_id` varchar(16) DEFAULT NULL, 
    `user_xbox_id` varchar(64) DEFAULT NULL, 
    `user_wii_id` varchar(64) DEFAULT NULL, 
    PRIMARY KEY (`user_id`), 
    KEY `user_steam_id` (`user_steam_id`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8; 
+2

Vale la pena ejecutar "optimizar" (http://dev.mysql.com/doc/refman/5.0/en/optimize-table.html), especialmente si sus tablas han crecido rápidamente. –

+0

Esto todavía está en un entorno de desarrollo, por lo que las tablas no son enormes. Sin embargo, lo intenté y parece haber hecho una gran diferencia, y ahora estoy usando el índice y estoy corriendo mucho más rápido. – robjbrain

+0

¿Le gustaría ofrecer más información sobre el uso y la practicidad del optimizador y ejecutarlo regularmente en un entorno en vivo? Si agrega una respuesta completa en lugar de solo un comentario, podré aceptar su respuesta :) – robjbrain

Respuesta

6

mayoría de los motores de bases de datos calculan su plan de consulta en base a estadísticas sobre las tablas - por ejemplo, si una tabla tiene un pequeño número de filas, es más rápido ir a la mesa que el índice. Esas estadísticas se mantienen durante el funcionamiento "normal", p. inserta, actualiza y elimina, pero puede perder su sincronización cuando se modifican las definiciones de tabla o cuando se hacen inserciones masivas.

Si observa un comportamiento inesperado en el plan de consulta, puede forzar a la base de datos a actualizar sus estadísticas; en MySQL puede usar Optimize Table, que hace todo, incluso reordenar la tabla, o Analyze Table, que solo actualiza los índices.

Esto es difícil de hacer en entornos de producción, ya que ambas operaciones bloquean las tablas; Si puede negociar una ventana de mantenimiento, esa es la forma más sencilla de resolver el problema.

Vale la pena medir el rendimiento de la "tabla optimizada": en hardware bien especificado, debería tomar solo un par de segundos para tablas de tamaño "normal" (hasta millones de registros, con solo algunos índices). Eso podría significar que puede tener una ventana de mantenimiento "informal": no saca la aplicación fuera de línea, solo acepta que algunos usuarios tendrán un rendimiento reducido mientras ejecuta los scripts.

2

MySQL tiene una característica EXPLAIN que le ayudará a entender la consulta:

$ mysql 
> EXPLAIN SELECT `comments`.`comment_id`, `comments`.`comment_html`,`comments`.`comment_time_added`, `comments`.`comment_has_attachments`, `users`.`user_name`, `users`.`user_id`, `users`.`user_comments_count`, `users`.`user_time_registered`, `users`.`user_time_last_active`, `user_profile`.`user_avatar`, `user_profile`.`user_signature_html` 
    FROM (`comments`) 
    INNER JOIN `users` ON `comments`.`comment_user_id` = `users`.`user_id` 
    INNER JOIN `user_profile` ON `users`.`user_id` = `user_profile`.`user_id` 
    WHERE `comments`.`comment_enabled` = 1 
    AND `comments`.`comment_content_id` = 12 
    ORDER BY `comments`.`comment_time_added` ASC 
    LIMIT 20 

MySQL puede ser simplemente falta, o saltarse un índice.

Usted puede aprender más acerca de la comprensión de la salida de EXPLAIN aquí from the documentation (a little hard-core), o mejor aún de un simpler explanation here, (ignore the fact that it's on a Java site.)

Lo más probable es la cantidad de datos, o un índice desactualizado o incompleto es el sentido que MySQL está haciendo falsamente una mesa escanear. Cuando vea escaneos de tabla, o secuencial serdas, a menudo puede ver fácilmente en qué campo falta un índice o un índice que no se puede utilizar.

+0

Ah sí, este es el resultado de la explicación: http://i.imgur.com/9VDuV.png – robjbrain

+0

Como puede ver, le falta el índice en los grupos de usuarios, posiblemente se deba a que los datos son demasiado pequeños (solo 7 filas) pero es probable que nunca llegue a ser más grande que eso. – robjbrain

+0

¿Es esa la explicación de, o sin el 'JOIN'? Buscaría maneras de evitar la tabla temporal, o sintonizar el motor MySQL para hacerlo más eficiente, [una publicación relacionada de stackexchange podría ayudarlo] (http://dba.stackexchange.com/questions/2161/avoiding- temporary-tables-while-sorting-by-a-column-in-a-different-table) –

1

Podría intentar esto (puede eliminar join con user_group). Puede ser más rápido en caso de que si la consulta recuperar conjunto pequeño de datos de comments tabla:

SELECT 
    comments.comment_id, comments.comment_html, comments.comment_time_added, comments.comment_has_attachments, users.user_name, users.user_id, users.user_comments_count, users.user_time_registered, users.user_time_last_active, user_profile.user_avatar, user_profile.user_signature_html, user_groups.user_group_icon, user_groups.user_group_name 
FROM 
    (select * from comments where comment_content_id = 12 and active = 1) comments 
     INNER JOIN users u ON c.comment_user_id = users.user_id 
     INNER JOIN user_profile ON users.user_id = user_profile.user_id 
     INNER JOIN user_groups ON users.user_group_id = user_groups.user_group_id 
ORDER BY comments.comment_time_added ASC 
LIMIT 20 
0

Intenta usar uniones izquierdas en las relaciones no nulas.

Parece que, dado que las uniones internas son siempre simétricas, mysql reorganizará las uniones para usar primero la tabla que mejor se vea (normalmente la más pequeña).

Dado que las uniones a la izquierda no siempre son simétricas, mysql no las reordenará y, por lo tanto, puede usarlas para forzar el orden de las tablas. Sin embargo, con un campo no nulo, el izquierdo y el interno son equivalentes, por lo que sus resultados no cambiarán.

El orden de la tabla determinará qué índices se utilizan y pueden tener un gran impacto en el rendimiento.