Lors du développement sur MySQL, je suis vraiment incapable de lancer un profileur. Je trouve que SQLyog est un assez bon remplacement pour Query Analyzer mais je n'ai pas trouvé d'outil qui fonctionne comme SQL profiler.
Pour les gens de MySQL qui n'ont pas vu Microsoft SQL Profiler , voici une capture d'écran
Lors de mon travail précédent, nous avions un outil qui contrefait SQL Profiler et nous a même donné des traces de pile
Quelqu'un connaît-il des outils comme ceux que j'ai mentionnés qui fonctionnent avec MySQL?.
(Pour info, je peux faire fonctionner Altiris Profiler avec MySQL mais cela impliquera d'exécuter Windows en plus ce n'est pas vraiment un sku Symantec donc la licence est vraiment délicate)
Non, il n'y a pas un tel outil.
MySQL n'a jamais proposé de profilage de requête. Maintenant que MySQL bénéficie de droits acquis par Oracle, je sais que cela continuera d'être le cas.
Pourtant, tout espoir n'est pas perdu.
Depuis 2007, Percona a mis au point des outils absolument merveilleux pour tout ce qu'un développeur et un administrateur de base de données souhaiteraient, y compris le profilage de requête.
Le premier ensemble d'outils de Percona, connu sous le nom de [~ # ~] maatkit [~ # ~] , a créé un domaine pour le utilisateur sérieux de MySQL. Il comporte beaucoup de choses , comme:
Percona a récemment intégré MAATKIT à un ensemble d'outils plus à jour, connu aujourd'hui sous le nom de Percona Toolkit . Ces outils ont repris là où MAATKIT s'est arrêté en élargissant le domaine d'activité pour l'utilisateur sérieux de MySQL pour inclure des choses telles que:
Pour revenir à la question d'origine, les outils disponibles pour le profilage des requêtes sont
Voici un exemple du type d'informations riches pouvant provenir de l'utilisation de l'un de ces outils:
J'ai aidé un client à implémenter mk-query-digest pour signaler les 20 requêtes les moins performantes toutes les 20 minutes. J'ai eu l'idée de cette vidéo YouTube . Le client déplacerait la sortie de toute requête erronée vers memcached, réduisant ainsi l'incidence de la requête prenant un péage sur la base de données.
Voici le script que j'ai fait pour appeler mk-query-digest (en examinant la liste de processus uniquement)
#!/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}
Voici l'utilisateur que j'ai fait pour me connecter à mysql en utilisant mk-query-digest
GRANT PROCESS ON *.* TO 'queryprofiler'@'%' IDENTIFIED BY 'queryprofiler';
Voici le crontab que j'ai exécuté toutes les 20 minutes (moins 10 secondes) en gardant les 144 dernières copies (soit 48 heures de profilage)
*/20 * * * * /root/QueryDigest/ExecQueryDigest.sh 1190s 144 10.1.1.8
Voici un profil qui s'est déroulé le 2011-12-28 11:20:00 pendant 1190 s (20 min moins 10 s)
Les 22 dernières lignes
# 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
Notez que c'est la liste des 20 requêtes les moins performantes en fonction du temps de réponse aux requêtes divisé par le nombre d'appels de la requête.
En regardant l'ID de requête n ° 1, qui est 0x5E994008E9543B29
, nous localisons cet ID de requête dans le fichier de sortie et voici le rapport pour cette requête particulière:
# 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
Bien que l'histogramme soit basé sur du texte, il donne une image précise des performances globales de la requête, s'exécutant parfois sur 1 seconde et la plupart du temps entre 0,01 et 0,1 seconde. À partir d'ici, on peut procéder à l'optimisation des performances en refactorisant la requête, en plaçant les résultats de la requête dans memcached, en ajoutant des index manquants ou couvrant, etc.
À mon humble avis, si Percona plaçait un jour les outils de profilage dans une interface graphique Windows, elle rivaliserait facilement avec SQL Server Profiler de Microsoft.
La défense repose !!!
Voir aussi cette réponse à propos de Jet Profiler for MySQL
Si vous avez besoin de profiler une seule application, et pas toutes les bases de données présentes sur MySQL, vous trouverez Neor Profile SQL utile.
MySQL Query Profiler combiné avec GUI MySQL tools est probablement aussi proche que possible de l'outil SQL Server Profiler
Les meilleures solutions prêtes à l'emploi que j'ai trouvées sont d'utiliser une combinaison du journal des requêtes lentes (qui craint par rapport à Profiler) et d'exécuter simplement Wireshark sur le port 3306 (qui vraiment suce par rapport à Profiler, et ne fonctionnera pas si vous chiffrez des connexions).
Il y a aussi SHOW FULL PROCESSLIST, qui est comme une combinaison réduite de sys.dm_exec_sessions et sys.dm_exec_requests (avec un peu de sys.dm_exec_sql_text ajouté).
Nous avons 6 grands serveurs exécutant diverses versions de MySQL de 4.1.22 à 5.1. Jet profiler bon outil qui nous permet de voir graphiquement l'état de tous les serveurs en un coup d'œil. Visual profiler http://tinyurl.com/profiler-png
Je dirais que la chose la plus proche de cela est Optimizer Trace (nouveau dans 5.6).
Un autre exemple pourrait être SHOW PROFILES
(5.1+), ou performance_schema
, qui a une analyse au niveau des instructions de MySQL 5.6+.
Voir ceci réponse à propos du profileur MySql LogMonitor