MySQL nunca ha creado un perfil de consulta. Ahora que MySQL está siendo protegido por Oracle, sé que este seguirá siendo el caso.
Sin embargo, no se pierde toda esperanza.
Desde 2007, Percona ha creado algunas herramientas absolutamente maravillosas para todo lo que un desarrollador y un DBA desearían, incluido el perfil de consulta.
El primer conjunto de herramientas de Percona, conocido como MAATKIT , creó un reino para el usuario serio de MySQL. Cuenta con muchas cosas , como:
- Perfil de consulta
- Ritmo cardíaco de replicación
- Gestión de esclavos de replicación
- Tabla de suma de verificación y sincronización
Percona ha bifurcado recientemente MAATKIT en un conjunto de herramientas más actualizado, conocido hoy como Percona Toolkit . Estas herramientas continuaron donde MAATKIT lo dejó al expandir el ámbito de actividad para el usuario serio de MySQL para incluir cosas como:
- Comprobación de errores de clave externa
- Cambio de esquema en línea
- Planes de explicación visual
- y más ...
Volviendo a la pregunta original, las herramientas disponibles para el perfil de consultas son
Aquí hay un ejemplo del tipo de información enriquecida que puede derivarse del uso de una de estas herramientas:
Ayudé a un cliente a implementar mk-query-digest para informar las 20 consultas con peor rendimiento cada 20 minutos. Tengo la idea de este video de YouTube . El cliente movería la salida de cualquier consulta incorrecta a memcached, reduciendo así la incidencia de que la consulta afecte la base de datos.
Aquí está el script que hice para llamar a mk-query-digest (examinando solo la lista de procesos)
#!/bin/sh
RUNFILE=/tmp/QueriesAreBeingDigested.txt
if [ -f ${RUNFILE} ] ; then exit ; fi
MKDQ=/usr/local/sbin/mk-query-digest
RUNTIME=${1}
COPIES_TO_KEEP=${2}
DBVIP=${3}
WHICH=/usr/bin/which
DATE=`${WHICH} date`
ECHO=`${WHICH} echo`
HEAD=`${WHICH} head`
TAIL=`${WHICH} tail`
AWK=`${WHICH} awk`
SED=`${WHICH} sed`
CAT=`${WHICH} cat`
WC=`${WHICH} wc`
RM=`${WHICH} rm | ${TAIL} -1 | ${AWK} '{print $1}'`
LS=`${WHICH} ls | ${TAIL} -1 | ${AWK} '{print $1}'`
HAS_THE_DBVIP=`/sbin/ip addr show | grep "scope global secondary" | grep -c "${DBVIP}"`
if [ ${HAS_THE_DBVIP} -eq 1 ] ; then exit ; fi
DT=`${DATE} +"%Y%m%d_%H%M%S"`
UNIQUETAG=`${ECHO} ${SSH_CLIENT}_${SSH_CONNECTION}_${DT} | ${SED} 's/\./ /g' | ${SED} 's/ //g'`
cd /root/QueryDigest
OUTFILE=QP_${DT}.txt
HOSTADDR=${DBVIP}
${MKDQ} --processlist h=${HOSTADDR},u=queryprofiler,p=queryprofiler --run-time=${RUNTIME} > ${OUTFILE}
#
# Rotate out Old Copies
#
QPFILES=QPFiles.txt
QPFILES2ZAP=QPFiles2Zap.txt
${LS} QP_[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]_[0-9][0-9][0-9][0-9][0-9][0-9].txt > ${QPFILES}
LINECOUNT=`${WC} -l < ${QPFILES}`
if [ ${LINECOUNT} -gt ${COPIES_TO_KEEP} ]
then
(( DIFF = LINECOUNT - COPIES_TO_KEEP ))
${HEAD} -${DIFF} < ${QPFILES} > ${QPFILES2ZAP}
for QPFILETOZAP in `${CAT} ${QPFILES2ZAP}`
do
${RM} ${QPFILETOZAP}
done
fi
rm -f ${QPFILES2ZAP}
rm -f ${QPFILES}
rm -f ${RUNFILE}
Aquí está el usuario que hice para conectarme a mysql usando mk-query-digest
GRANT PROCESS ON *.* TO 'queryprofiler'@'%' IDENTIFIED BY 'queryprofiler';
Aquí está el crontab que ejecuté cada 20 minutos (menos 10 segundos) manteniendo las últimas 144 copias (que son 48 horas de creación de perfiles)
*/20 * * * * /root/QueryDigest/ExecQueryDigest.sh 1190s 144 10.1.1.8
La parte increíble: la salida de mk-query-digest
Aquí hay un perfil que se ejecutó el 28/12/2011 11:20:00 durante 1190 segundos (20 minutos menos 10 segundos)
Las últimas 22 líneas.
# Rank Query ID Response time Calls R/Call Item
# ==== ================== ================ ======= ========== ====
# 1 0x5E994008E9543B29 40.3255 11.2% 101 0.399263 SELECT schedule_occurrence schedule_eventschedule schedule_event schedule_eventtype schedule_event schedule_eventtype schedule_occurrence.start
# 2 0x392F6DA628C7FEBD 33.9181 9.4% 17 1.995184 SELECT mt_entry mt_objecttag
# 3 0x6C6318E56E149036 26.4695 7.3% 102 0.259505 SELECT schedule_occurrence schedule_eventschedule schedule_event schedule_eventtype schedule_event schedule_eventtype schedule_occurrence.start
# 4 0x00F66961DAE6FFB2 25.5472 7.1% 55 0.464495 SELECT mt_entry mt_placement mt_category
# 5 0x99E13015BFF1E75E 22.3618 6.2% 199 0.112371 SELECT mt_entry mt_objecttag
# 6 0x84DD09F0FC444677 22.3516 6.2% 39 0.573118 SELECT mt_entry
# 7 0x440EBDBCEDB88725 21.1817 5.9% 36 0.588380 SELECT mt_entry
# 8 0x8D258C584B858811 17.2402 4.8% 37 0.465951 SELECT mt_entry mt_placement mt_category
# 9 0x4E2CB0F4CAFD1400 16.9768 4.7% 40 0.424419 SELECT mt_entry mt_placement mt_category
# 10 0x377E0D0898266FDD 16.6979 4.6% 150 0.111319 SELECT polls_pollquestion mt_category
# 11 0x3B9686D98BB8E054 16.2089 4.5% 32 0.506529 SELECT mt_entry mt_objecttag mt_tag
# 12 0x97F670B604A85608 15.6158 4.3% 34 0.459287 SELECT mt_entry mt_placement mt_category
# 13 0x3F5557DA231225EB 14.4309 4.0% 36 0.400859 SELECT mt_entry mt_placement mt_category
# 14 0x191D660A10738896 13.1220 3.6% 31 0.423290 SELECT mt_entry mt_placement mt_category
# 15 0xF88F7421DD88036D 12.1261 3.4% 61 0.198788 SELECT mt_entry mt_blog mt_objecttag mt_tag mt_author
# 16 0xA909BF76E7051792 10.3971 2.9% 53 0.196172 SELECT mt_entry mt_objecttag mt_tag
# 17 0x3D42D07A335ED983 9.1424 2.5% 20 0.457121 SELECT mt_entry mt_placement mt_category
# 18 0x59F43B57DD43F2BD 9.0533 2.5% 21 0.431111 SELECT mt_entry mt_placement mt_category
# 19 0x7961BD4C76277EB7 8.5564 2.4% 47 0.182052 INSERT UNION UPDATE UNION mt_session
# 20 0x173EB4903F3B6DAC 8.5394 2.4% 22 0.388153 SELECT mt_entry mt_placement mt_category
Tenga en cuenta que esta es la lista de las 20 consultas con peor rendimiento en función del tiempo de respuesta de la consulta dividido por el número de veces que se llamó a la consulta.
Mirando la ID de consulta # 1, que es 0x5E994008E9543B29
, ubicamos esa ID de consulta en el archivo de salida y aquí está el informe para esa consulta en particular:
# Query 1: 0.09 QPS, 0.03x concurrency, ID 0x5E994008E9543B29 at byte 0 __
# This item is included in the report because it matches --limit.
# pct total min max avg 95% stddev median
# Count 4 101
# Exec time 7 40s 303ms 1s 399ms 992ms 198ms 293ms
# Lock time 0 0 0 0 0 0 0 0
# Users 1 mt
# Hosts 101 10.64.95.73:33750 (1), 10.64.95.73:34452 (1), 10.64.95.73:38440 (1)... 97 more
# Databases 1 mt1
# Time range 1325089201 to 1325090385
# bytes 0 273.60k 2.71k 2.71k 2.71k 2.62k 0 2.62k
# id 4 765.11M 7.57M 7.58M 7.58M 7.29M 0.12 7.29M
# Query_time distribution
# 1us
# 10us
# 100us
# 1ms
# 10ms
# 100ms ################################################################
# 1s ######
# 10s+
# Tables
# SHOW TABLE STATUS FROM `mt1` LIKE 'schedule_occurrence'\G
# SHOW CREATE TABLE `mt1`.`schedule_occurrence`\G
# SHOW TABLE STATUS FROM `mt1` LIKE 'schedule_eventschedule'\G
# SHOW CREATE TABLE `mt1`.`schedule_eventschedule`\G
# SHOW TABLE STATUS FROM `mt1` LIKE 'schedule_event'\G
# SHOW CREATE TABLE `mt1`.`schedule_event`\G
# SHOW TABLE STATUS FROM `mt1` LIKE 'schedule_eventtype'\G
# SHOW CREATE TABLE `mt1`.`schedule_eventtype`\G
# SHOW TABLE STATUS FROM `schedule_occurrence` LIKE 'start'\G
# SHOW CREATE TABLE `schedule_occurrence`.`start`\G
# EXPLAIN
SELECT `schedule_occurrence`.`id`, `schedule_occurrence`.`schedule_id`, `schedule_occurrence`.`event_id`, `schedule_occurrence`.`start`, `schedule_occurrence`.`end`, `schedule_occurrence`.`cancelled`, `schedule_occurrence`.`original_start`, `schedule_occurrence`.`original_end`, `schedule_occurrence`.`all_day`, `schedule_occurrence`.`ongoing`, `schedule_occurrence`.`featured`, `schedule_eventschedule`.`id`, `schedule_eventschedule`.`event_id`, `schedule_eventschedule`.`start`, `schedule_eventschedule`.`end`, `schedule_eventschedule`.`all_day`, `schedule_eventschedule`.`ongoing`, `schedule_eventschedule`.`min_date_calculated`, `schedule_eventschedule`.`max_date_calculated`, `schedule_eventschedule`.`rule`, `schedule_eventschedule`.`end_recurring_period`, `schedule_eventschedule`.`textual_description`, `schedule_event`.`id`, `schedule_event`.`title`, `schedule_event`.`slug`, `schedule_event`.`description`, `schedule_event`.`host_id`, `schedule_event`.`cost`, `schedule_event`.`age_restrictions`, `schedule_event`.`more_info`, `schedule_event`.`photo_id`, `schedule_event`.`contact_email`, `schedule_event`.`event_type_id`, `schedule_event`.`featured`, `schedule_event`.`staff_pick`, `schedule_event`.`futuremost`, `schedule_event`.`creator_id`, `schedule_event`.`created_on`, `schedule_event`.`allow_comments`, `schedule_event`.`mt_entry`, `schedule_eventtype`.`id`, `schedule_eventtype`.`parent_id`, `schedule_eventtype`.`name`, `schedule_eventtype`.`slug`, `schedule_eventtype`.`lft`, `schedule_eventtype`.`rght`, `schedule_eventtype`.`tree_id`, `schedule_eventtype`.`level`, T5.`id`, T5.`title`, T5.`slug`, T5.`description`, T5.`host_id`, T5.`cost`, T5.`age_restrictions`, T5.`more_info`, T5.`photo_id`, T5.`contact_email`, T5.`event_type_id`, T5.`featured`, T5.`staff_pick`, T5.`futuremost`, T5.`creator_id`, T5.`created_on`, T5.`allow_comments`, T5.`mt_entry`, T6.`id`, T6.`parent_id`, T6.`name`, T6.`slug`, T6.`lft`, T6.`rght`, T6.`tree_id`, T6.`level` FROM `schedule_occurrence` INNER JOIN `schedule_eventschedule` ON (`schedule_occurrence`.`schedule_id` = `schedule_eventschedule`.`id`) INNER JOIN `schedule_event` ON (`schedule_eventschedule`.`event_id` = `schedule_event`.`id`) INNER JOIN `schedule_eventtype` ON (`schedule_event`.`event_type_id` = `schedule_eventtype`.`id`) INNER JOIN `schedule_event` T5 ON (`schedule_occurrence`.`event_id` = T5.`id`) INNER JOIN `schedule_eventtype` T6 ON (T5.`event_type_id` = T6.`id`) WHERE (EXTRACT(MONTH FROM `schedule_occurrence`.`start`) = 8 AND EXTRACT(DAY FROM `schedule_occurrence`.`start`) = 6 AND `schedule_occurrence`.`start` BETWEEN '2011-01-01 00:00:00' and '2011-12-31 23:59:59.99') ORDER BY `schedule_occurrence`.`ongoing` ASC, `schedule_occurrence`.`all_day` DESC, `schedule_occurrence`.`start` ASC\G
Aunque el histograma está basado en texto, proporciona una imagen precisa del rendimiento general de la consulta, a veces se ejecuta durante 1 segundo, y la mayoría de las veces entre 0,01 y 0,1 segundos. A partir de aquí, se puede proceder a ajustar el rendimiento refactorizando la consulta, colocando los resultados de la consulta en memcached, agregando índices faltantes o de cobertura, etc.
CONCLUSIÓN
En mi humilde opinión, si Percona alguna vez colocó las herramientas del generador de perfiles en una GUI de Windows, rivalizaría fácilmente con el Analizador de SQL Server de Microsoft.
Restos de defensa !!!