<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>dbnewz &#187; pratique</title>
	<atom:link href="http://www.dbnewz.com/tag/pratique/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.dbnewz.com</link>
	<description>le blog français sur les SGBD - MySQL, Oracle et plus...</description>
	<lastBuildDate>Tue, 31 Jan 2012 16:03:38 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>MySQL et ses messages d&#8217;erreur</title>
		<link>http://www.dbnewz.com/2011/09/13/mysql-et-ses-messages-derreur/</link>
		<comments>http://www.dbnewz.com/2011/09/13/mysql-et-ses-messages-derreur/#comments</comments>
		<pubDate>Tue, 13 Sep 2011 14:16:04 +0000</pubDate>
		<dc:creator>stephane</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[erreur]]></category>
		<category><![CDATA[pratique]]></category>

		<guid isPermaLink="false">http://www.dbnewz.com/?p=972</guid>
		<description><![CDATA[Je suis en généralement plutôt content de MySQL : c&#8217;est simple et stable, ça fonctionne bien. Mais il reste encore du travail pour que les messages d&#8217;erreur soient explicites. Petit résumé d&#8217;une frayeur causée par un message d&#8217;erreur approximatif.

Une de mes tables a une tendance marquée à la fragmentation, ce qui a pour conséquence de [...]]]></description>
			<content:encoded><![CDATA[<p>Je suis en généralement plutôt content de MySQL : c&#8217;est simple et stable, ça fonctionne bien. Mais il reste encore du travail pour que les messages d&#8217;erreur soient explicites. Petit résumé d&#8217;une frayeur causée par un message d&#8217;erreur approximatif.<br />
<span id="more-972"></span></p>
<p>Une de mes tables a une tendance marquée à la fragmentation, ce qui a pour conséquence de faire gonfler artificiellement sa taille et de faire baisser les performances de certaines requêtes. Je dois donc de temps à autre la défragmenter. Pas de problème : je commence par regarder la taille sur le disque du fichier .ibd (il s&#8217;agit d&#8217;une table InnoDB sur un serveur pour lequel innodb_file_per_table est activé). Quand la table est défragmentée, elle fait environ 8 Go :<br />
<code><br />
# cd /data/mysql/main_revshare<br />
# du -sh bris_statistic_video.ibd<br />
11G	bris_statistic_video.ibd<br />
</code><br />
Pas de doute, une séance de défragmentation s&#8217;impose. Je me connecte donc au serveur MySQL pour exécuter un OPTIMIZE TABLE :<br />
<code><br />
mysql&gt; OPTIMIZE TABLE bris_statistic_video\G<br />
******** 1. row ********<br />
   Table: dailymotion.bris_statistic_video<br />
      Op: optimize<br />
Msg_type: Error<br />
Msg_text: Table 'dailymotion.bris_statistic_video' doesn't exist<br />
******** 2. row ********<br />
   Table: dailymotion.bris_statistic_video<br />
      Op: optimize<br />
Msg_type: error<br />
Msg_text: Corrupt<br />
</code><br />
Et là, c&#8217;est le drame ! Quoi, ma table est corrompue ? Impossible, l&#8217;application fonctionne toujours et la table est utilisée partout !</p>
<p>Comment sortir de ce problème : redémarrer le serveur en espérant qu&#8217;InnoDB répare la table, récupérer un backup ? En fait non, il faut simplement rester calme et bien regarder le message : si à la 2ème ligne, MySQL nous annonce que la table est corrompue, à la 1ère MySQL nous annonce que la table n&#8217;existe pas &#8230; alors que nous avons vu que le fichier .ibd existe bien. Louche cette histoire !</p>
<p>Un tour dans le journal d&#8217;erreurs confirme bien qu&#8217;il n&#8217;existe aucune erreur : il doit donc y avoir une erreur avec la commande OPTIMIZE. Réfléchissons, réfléchissons &#8230; ça y est : j&#8217;ai exécuté la commande OPTIMIZE dans la base main alors que ma table se trouve dans la base main_revshare. Il suffit donc de se positionner dans main_revshare et de relancer l&#8217;OPTIMIZE, avec succès cette fois.</p>
<p>Moralité : Mieux vaut se méfier des messages d&#8217;erreurs qui ne vous sont pas familiers et bien réfléchir à la cause de l&#8217;erreur avant de se lancer dans la correction d&#8217;un problème qui n&#8217;existe peut-être pas !</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dbnewz.com/2011/09/13/mysql-et-ses-messages-derreur/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Méthodes de suppression des index inutiles</title>
		<link>http://www.dbnewz.com/2011/09/05/methodes-de-suppression-des-index-inutiles/</link>
		<comments>http://www.dbnewz.com/2011/09/05/methodes-de-suppression-des-index-inutiles/#comments</comments>
		<pubDate>Mon, 05 Sep 2011 15:59:32 +0000</pubDate>
		<dc:creator>stephane</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[index]]></category>
		<category><![CDATA[log]]></category>
		<category><![CDATA[maatkit]]></category>
		<category><![CDATA[pratique]]></category>

		<guid isPermaLink="false">http://www.dbnewz.com/?p=916</guid>
		<description><![CDATA[Les vacances étant terminées, nous allons boucler notre tour de vue des index inutiles en voyant quels outils vont nous aider à découvrir les index qui peuvent être supprimés. Le dernier article présentait en effet des indications qui fonctionnent généralement bien mais qui ont l&#8217;inconvénient de demander beaucoup de travail manuel et de laisser de [...]]]></description>
			<content:encoded><![CDATA[<p>Les vacances étant terminées, nous allons boucler notre tour de vue des index inutiles en voyant quels outils vont nous aider à découvrir les index qui peuvent être supprimés. Le dernier article présentait en effet des indications qui fonctionnent généralement bien mais qui ont l&#8217;inconvénient de demander beaucoup de travail manuel et de laisser de côté tout un pan d&#8217;index qui peuvent être inutiles : ceux qui ne sont pas en doublon ni redondants, qui n&#8217;ont pas une cardinalité faible mais qui ne sont tout simplement pas utilisés par l&#8217;application.<br />
<span id="more-916"></span></p>
<h3>Idée générale</h3>
<p>Si vous avez bien lu l&#8217;article précédent, vous avez probablement remarqué que la principale difficulté est qu&#8217;il n&#8217;existe quasiment jamais de règle absolue permettant de savoir à coup sûr qu&#8217;un index est inutile (exception notable : les index en doublon repérés par mk-duplicate-key-checker et qui peuvent être supprimés dans 99% des cas sans problème). Finalement, la seule méthode qui semble marcher est la suivante :<br />
- Récupérer les requêtes exécutées sur le serveur<br />
- Regarder le plan d&#8217;exécution de ces requêtes pour voir les index utilisés<br />
- Comparer avec le schéma des tables pour en déduire les index non utilisés<br />
- Supprimer les index repérés<br />
La difficulté se situe surtout dans la 2ème étape (construire la liste des index utilisés à partir du plan d&#8217;exécution). Heureusement il existe au moins 2 manières, en fonction de votre version de MySQL, pour réussir cette étape.</p>
<h3>Index_statistics</h3>
<p>Les heureux utilisateurs de MariaDB ou Percona Server ont la chance d&#8217;avoir userstats v2, un patch exceptionnel développé à l&#8217;origine par Google. Ce patch ajoute un nombre incalculable de statistiques sur les utilisateurs, les tables mais aussi, ce qui nous intéresse ici, les index. Pour activer la fonctionnalité, il suffit de changer une variable :<br />
<code>mysql&gt; SET GLOBAL userstat_running = 1;</code></p>
<p>A partir de ce moment, la table <code>INDEX_STATISTICS</code> de la base <code>INFORMATION_SCHEMA</code> va se remplir. Les colonnes sont très simples à comprendre : <code>TABLE_SCHEMA</code>, <code>TABLE_NAME</code> et <code>INDEX_NAME</code> localisent l&#8217;index et <code>ROWS_READ</code> donne le nombre de lignes lues dans l&#8217;index. Evidemment, pour obtenir des statistiques significatives et fiables, il faut attendre suffisamment longtemps avant de consulter le contenu de la table. Une bonne question est de savoir ce que signifie &laquo;&nbsp;suffisamment longtemps&nbsp;&raquo; : disons qu&#8217;il faut que chaque requête potentielle de l&#8217;application ait pu être exécutée, en pensant bien aux requêtes rares telles que les requêtes faites dans des cronjobs par exemple.</p>
<p>Un petit exemple sur des données réelles ?<br />
<code><br />
mysql&gt; SELECT * FROM INFORMATION_SCHEMA.INDEX_STATISTICS WHERE TABLE_NAME='main';<br />
+--------------+------------+------------+-----------+<br />
| TABLE_SCHEMA | TABLE_NAME | INDEX_NAME               | ROWS_READ  |<br />
+--------------+------------+------------+-----------+<br />
| biz&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;  | main&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;      | user_id&nbsp;&nbsp;&nbsp;        |  231751890 |<br />
| biz&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;  | main&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;      | created&nbsp;&nbsp;&nbsp;                  |    4456658&nbsp;&nbsp; |<br />
| biz&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| main&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| PRIMARY&nbsp;&nbsp;&nbsp;&nbsp;| 1748023896&nbsp;|<br />
| biz&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| main&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;      | modified_idx&nbsp;&nbsp;&nbsp; |  266636148 |<br />
| biz&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;  | main&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;      | mobile_idx&nbsp;&nbsp;&nbsp; |        550 |<br />
+--------------+------------+---------------+------------+<br />
</code><br />
et voyons en parallèle le schéma de la table :<br />
<code><br />
mysql&gt; SHOW CREATE TABLE main\G<br />
********* 1. row *********<br />
Table: main<br />
Create Table: CREATE TABLE main (<br />
&nbsp;&nbsp;...<br />
&nbsp;&nbsp;PRIMARY KEY (main_id),<br />
&nbsp;&nbsp;KEY user_id (user_id),<br />
&nbsp;&nbsp;KEY created (created),<br />
&nbsp;&nbsp;KEY language_idx (language),<br />
&nbsp;&nbsp;KEY mobile_idx (mobile,deleted,web,published),<br />
&nbsp;&nbsp;KEY country_mobile_idx (country,mobile),<br />
&nbsp;&nbsp;KEY modified_idx (modified)<br />
) ENGINE=InnoDB;<br />
</code><br />
Qu&#8217;en déduit-on ? En premier lieu, que les index language_idx et country_mobile_idx ne sont jamais utilisés, ils ne servent donc à rien et peuvent être supprimés. Ensuite, en comparant les chiffres, on voit que le serveur a lu 1,7 milliards de lignes dans la clé primaire, 550 dans l&#8217;index mobile_idx : aucun doute mobile_idx ne sert à rien lui non plus !</p>
<p>Et voilà, de manière très simple, nous avons trouvé des index qui peuvent être supprimés.<br />
On peut même automatiser la découverte des index non utilisée <a href="http://www.mysqlperformanceblog.com/2008/09/12/unused-indexes-by-single-query/">avec une petite requête qui va bien</a>.</p>
<p>Quelles sont les limitations de cette méthode ? Très simple, autant les index qui ne sont jamais utilisés sont visibles immédiatement, autant on peut se poser la question de la pertinence de certains index. Et malheureusement, les chiffres donnés par la table INDEX_STATISTICS ne sont pas suffisants. Exemple : l&#8217;index created affiche un nombre de lignes lues environ 500 fois plus faibles que la clé primaire : cela signifie-t-il qu&#8217;il n&#8217;est pas très utile ou qu&#8217;au contraire il est très utile mais seulement sur certaines requêtes ?</p>
<h3>mk-index-usage</h3>
<p>Quand on ne dispose de userstats, la seule solution consiste à collecter toutes les requêtes pendant une période suffisamment longue, par exemple en mettant <code>long_query_time</code> à 0 pour que toutes les requêtes aboutissent dans le slow query log, et à se tourner vers Maatkit en espérant y trouver notre bonheur. Ca tombe bien, <code>mk-index-usage</code> est justement un outil lisant un fichier de log et délivrant de nombreuses informations intéressantes (en déterminant en particulier le plan d&#8217;exécution des requêtes).</p>
<p>Par défaut, <code>mk-index-usage</code> prend en entrée un fichier de log au format slow query log et affiche une liste de requêtes SQL pour supprimer les index inutiles :<br />
<code><br />
$ mk-index-usage slow.log<br />
slow.log:   5% 08:28 remain<br />
slow.log:  11% 07:20 remain<br />
slow.log:  14% 08:36 remain<br />
...<br />
slow.log:  93% 00:37 remain<br />
slow.log:  97% 00:12 remain<br />
</code><br />
ALTER TABLE `main`.`adfit` DROP KEY `status`; &#8212; type:non-unique<br />
&#8230;<br />
ALTER TABLE `main`.`biz` DROP KEY `created`, DROP KEY `modified_idx`; &#8212; type:non-unique<br />
</code><br />
Surprise ! Le script a bien identifié modified_idx comme inutile, mais pas mobile_idx. Et il a décidé que created pouvait être supprimé alors que nous avons vu que ce n'était pas forcément évident.</p>
<p>Comme d'habitude avec Maatkit, si vous êtes curieux et que vous lisez la documentation, vous verrez qu'il existe une multitude d'options permettant d'obtenir bien plus d'informations sur l'usage des index sur votre plate-forme. De plus, cette méthode est compatible avec toutes les versions de MySQL</p>
<h3>Conclusion</h3>
<p>Les deux méthodes présentées ici peuvent vous faire gagner beaucoup de temps en vous aidant à identifier les index candidats à la suppression. Bien entendu, comme souvent, il ne serait pas très prudent de faire confiance aveuglément à un script pour gérer votre plate-forme : votre travail consistera à vérifier s'il faut effectivement enlever les index qui sont susceptibles d'être supprimés. Normalement, ce type d'index ne devrait représenter qu'une petite portion des index de votre application, le gain de temps sera donc appréciable.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dbnewz.com/2011/09/05/methodes-de-suppression-des-index-inutiles/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Index candidats à la suppression</title>
		<link>http://www.dbnewz.com/2011/07/06/index-candidats-a-la-suppression/</link>
		<comments>http://www.dbnewz.com/2011/07/06/index-candidats-a-la-suppression/#comments</comments>
		<pubDate>Wed, 06 Jul 2011 13:10:14 +0000</pubDate>
		<dc:creator>stephane</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[index]]></category>
		<category><![CDATA[maatkit]]></category>
		<category><![CDATA[pratique]]></category>

		<guid isPermaLink="false">http://www.dbnewz.com/?p=895</guid>
		<description><![CDATA[Après avoir constaté dans les articles précédents que les index inutiles causent des baisses de performances non négligeables, nous allons voir dans cet article qu&#8217;il n&#8217;est pas aussi simple qu&#8217;il y paraît de déterminer si un index est utile ou non, même si dans certains cas la réponse semble évidente.

A première vue, trois catégories d&#8217;index [...]]]></description>
			<content:encoded><![CDATA[<p>Après avoir constaté dans les articles précédents que les index inutiles causent des baisses de performances non négligeables, nous allons voir dans cet article qu&#8217;il n&#8217;est pas aussi simple qu&#8217;il y paraît de déterminer si un index est utile ou non, même si dans certains cas la réponse semble évidente.<br />
<span id="more-895"></span><br />
A première vue, trois catégories d&#8217;index sont bien placés pour être qualifiés d&#8217;inutiles : les index en doublon, les index redondants et les index à faible cardinalité. Regardons chaque catégorie en détail.</p>
<h3>Index en doublon</h3>
<p>Les index en doublon sont simplement ceux qui sont définis plusieurs fois. Un exemple simple pour commencer :<br />
<code>CREATE TABLE t (<br />
&nbsp;&nbsp;id int(11) DEFAULT NULL,<br />
&nbsp;&nbsp;KEY a (id),<br />
&nbsp;&nbsp;KEY b (id)<br />
);</code></p>
<p>Notons que MySQL n&#8217;empêche en aucun cas ce genre de définition erronée.</p>
<p>Un autre exemple, un peu plus compliqué :<br />
<code>CREATE TABLE u (<br />
&nbsp;&nbsp;id int(11) NOT NULL DEFAULT '0',<br />
&nbsp;&nbsp;UNIQUE KEY (id),<br />
&nbsp;&nbsp;KEY b (id)<br />
);</code></p>
<p>La logique était : je crée une contrainte d&#8217;unicité sur la colonne id, puis un index pour accélérer les requêtes. Sauf qu&#8217;avec MySQL, toutes les contraintes sont vérifiées avec des index, ce qui signifie que la création de la contrainte d&#8217;unicité crée implicitement un index (idem pour <code>PRIMARY KEY</code> et <code>FOREIGN KEY</code>).</p>
<p>Un dernier exemple :<br />
<code>CREATE TABLE v (<br />
&nbsp;&nbsp;id int(11) NOT NULL DEFAULT '0',<br />
&nbsp;&nbsp;col varchar(10) DEFAULT NULL,<br />
&nbsp;&nbsp;PRIMARY KEY (id),<br />
&nbsp;&nbsp;KEY a (col),<br />
&nbsp;&nbsp;FULLTEXT KEY b (col)<br />
) ENGINE=MyISAM;</code></p>
<p>Ici, nous avons bien deux index sur col, mais ce ne sont pas des index de même type, ils ne sont donc pas en doublon. Attention aux excès de zèle !</p>
<p>Les index en doublon n&#8217;apportent rien, il est donc toujours utile de pouvoir les repérer pour ensuite les supprimer. Un bon outil pour ce travail est <code>mk-duplicate-key-checker</code> de Maatkit. L&#8217;outil est très simple d&#8217;usage et la documentation complète.</p>
<p>Par exemple, pour vérifier si notre table u (dans la base test) contient des index en doublon, on peut utiliser la ligne suivante :<br />
<code>$ mk-duplicate-key-checker h=localhost,u=root,D=test -t u<br />
# ##################################<br />
# test.u<br />
# ##################################</code></p>
<p><code># b is a duplicate of PRIMARY<br />
# Key definitions:<br />
#   KEY `b` (`id`)<br />
#   PRIMARY KEY (`id`),<br />
# Column types:<br />
#	  `id` int(11) not null default '0'<br />
# To remove this duplicate index, execute:<br />
ALTER TABLE `test`.`u` DROP INDEX `b`;</code></p>
<p><code># ##################################<br />
# Summary of indexes<br />
# ##################################</code></p>
<p><code># Size Duplicate Indexes   0<br />
# Total Duplicate Indexes  1<br />
# Total Indexes            2</code></p>
<p>L&#8217;outil suggère avec raison que l&#8217;index b est en doublon de la clé primaire et propose sous la forme d&#8217;un <code>ALTER TABLE</code> une requête pour supprimer cet index.</p>
<p>La même ligne de commande, en version longue :<br />
<code>$ mk-duplicate-key-checker --host localhost --user root --databases test --tables t</code></p>
<p>Il est bien sûr possible d&#8217;examiner en une seule passe toutes les tables de la base test :<br />
<code>$ mk-duplicate-key-checker --host localhost --user root --databases test<br />
# ##################################<br />
# test.t<br />
# ##################################</code></p>
<p><code># a is a duplicate of b<br />
# Key definitions:<br />
#   KEY `a` (`id`),<br />
#   KEY `b` (`id`)<br />
# Column types:<br />
#	  `id` int(11) default null<br />
# To remove this duplicate index, execute:<br />
ALTER TABLE `test`.`t` DROP INDEX `a`;</code></p>
<p><code># ##################################<br />
# test.u<br />
# ##################################</code></p>
<p><code># b is a duplicate of id<br />
# Key definitions:<br />
#   KEY `b` (`id`)<br />
#   UNIQUE KEY `id` (`id`),<br />
# Column types:<br />
#	  `id` int(11) not null default '0'<br />
# To remove this duplicate index, execute:<br />
ALTER TABLE `test`.`u` DROP INDEX `b`;</code></p>
<p><code># ##################################<br />
# Summary of indexes<br />
# ##################################</code></p>
<p><code># Size Duplicate Indexes   0<br />
# Total Duplicate Indexes  2<br />
# Total Indexes            7</code></p>
<p>Parfait, <code>mk-duplicate-key-checker</code> a bien identifié les doublons et ne s&#8217;est pas fait piéger par l&#8217;index FULLTEXT de la table v.</p>
<h3>Index redondants</h3>
<p>Pour les index de type BTREE (c&#8217;est-à-dire tous les index sauf les index HASH de Memory et les index FULLTEXT de MyISAM), l&#8217;optimiseur de requêtes de MySQL est capable d&#8217;utiliser un préfixe gauche d&#8217;un index composite. Tous les index portant sur des préfixes gauche d&#8217;un index composite ne sont donc théoriquement pas utiles.</p>
<p>Pas clair ? Regardez la table suivante :<br />
<code>CREATE TABLE w (<br />
&nbsp;&nbsp;id int(11) NOT NULL DEFAULT '0',<br />
&nbsp;&nbsp;col varchar(10) DEFAULT NULL,<br />
&nbsp;&nbsp;col2 varchar(10) DEFAULT NULL,<br />
&nbsp;&nbsp;KEY id (id),<br />
&nbsp;&nbsp;KEY id_col (id,col)<br />
);</code></p>
<p>Et considérez la requête suivante :<br />
<code>SELECT * FROM w WHERE id = 1</code></p>
<p><code>EXPLAIN</code> nous indique que l&#8217;index sur id est utilisé :<br />
<code>mysql&gt; EXPLAIN SELECT * FROM w WHERE id = 1\G<br />
********* 1. row ********<br />
&nbsp;&nbsp;&nbsp;id: 1<br />
&nbsp;&nbsp;&nbsp;select_type: SIMPLE<br />
&nbsp;&nbsp;&nbsp;table: w<br />
&nbsp;&nbsp;&nbsp;type: ref<br />
&nbsp;&nbsp;&nbsp;possible_keys: id,id_col<br />
&nbsp;&nbsp;&nbsp;key: id<br />
&nbsp;&nbsp;&nbsp;key_len: 4<br />
&nbsp;&nbsp;&nbsp;ref: const<br />
&nbsp;&nbsp;&nbsp;rows: 1<br />
&nbsp;&nbsp;&nbsp;Extra:<br />
</code><br />
Mais si on demande à l&#8217;optimiseur de ne pas considérer l&#8217;index sur id, on voit que la requête va s&#8217;exécuter de manière aussi efficace en utilisant l&#8217;index sur (id,col) :</p>
<p><code>mysql&gt; EXPLAIN SELECT * FROM w IGNORE INDEX(id) WHERE id = 1\G<br />
******** 1. row ********<br />
&nbsp;&nbsp;&nbsp;id: 1<br />
&nbsp;&nbsp;&nbsp;select_type: SIMPLE<br />
&nbsp;&nbsp;&nbsp;table: w<br />
&nbsp;&nbsp;&nbsp;type: ref<br />
&nbsp;&nbsp;&nbsp;possible_keys: id_col<br />
&nbsp;&nbsp;&nbsp;key: id_col<br />
&nbsp;&nbsp;&nbsp;key_len: 4<br />
&nbsp;&nbsp;&nbsp;ref: const<br />
&nbsp;&nbsp;&nbsp;rows: 1<br />
&nbsp;&nbsp;&nbsp;Extra:<br />
</code><br />
On peut donc éliminer sans problème l&#8217;index sur id.</p>
<p>Faut-il donc systématiquement supprimer les index redondants ? Eh bien non, il convient d&#8217;apporter quelques nuances. Disons que la majeure partie du temps, un index redondant est inutile et peut être supprimé, mais qu&#8217;il existe des situations où il faudra conserver tous les index.</p>
<p>Par exemple, ce sera souvent le cas quand les colonnes indexées sont de grande taille (que ceux qui n&#8217;utilisent jamais de <code>VARCHAR(255)</code> lèvent la main <img src='http://www.dbnewz.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> ). Pour avoir un cas concret de ce type de situation, vous pouvez lire <a href="http://www.mysqlperformanceblog.com/2007/08/28/redundant-index-is-not-always-bad/">cet article</a>.</p>
<p>Comment savoir si un index redondant est utile ou non ? La seule solution utilisable est bien souvent de faire des tests.</p>
<p>Que dit <code>mk-duplicate-key-checker</code> sur les index redondants ?</p>
<p><code>$ mk-duplicate-key-checker h=localhost,u=root,D=test -t w<br />
# ##################################<br />
# test.w<br />
# ##################################</code></p>
<p><code># id is a left-prefix of id_col<br />
# Key definitions:<br />
#   KEY `id` (`id`),<br />
#   KEY `id_col` (`id`,`col`)<br />
# Column types:<br />
#	  `id` int(11) not null default '0'<br />
#	  `col` varchar(10) default null<br />
# To remove this duplicate index, execute:<br />
ALTER TABLE `test`.`w` DROP INDEX `id`;</code></p>
<p><code># ##################################<br />
# Summary of indexes<br />
# ##################################</code></p>
<p><code># Size Duplicate Indexes   16<br />
# Total Duplicate Indexes  1<br />
# Total Indexes            2</code></p>
<p>L&#8217;outil considère toujours les index redondants comme candidats à la suppression. A vous de voir s&#8217;il vaux mieux le conserver ou non.</p>
<h3>Index à faible cardinalité</h3>
<p>Arnaud a déjà parlé en détail de la notion de<a href="http://www.dbnewz.com/2008/09/05/cardinalite-selectivite-et-distributivite-dun-index-mysql-quel-impact-sur-le-plan-dexecution/"> cardinalité d&#8217;un index</a>. En résumé, la cardinalité est le nombre de valeurs uniques de l&#8217;index. Plus elle est élevée, plus l&#8217;index sera efficace dans son filtrage.</p>
<p>Si la cardinalité est faible, l&#8217;index perd de son intérêt. D&#8217;ailleurs, l&#8217;optimiseur de requêtes n&#8217;utilisera pas l&#8217;index s&#8217;il estime qu&#8217;il n&#8217;est pas utile. Voici donc de bons candidats à la suppression ! Cependant là encore, il convient de nuancer.</p>
<p>Imaginez un site de e-commerce avec une table contenant les commandes. Cette table va contenir un champ indiquant le statut des commandes, avec par exemple les valeurs &#8216;archivée&#8217; et &#8216;en cours&#8217;. Au bout de quelque temps, il est certain que presque toutes les lignes de la table auront le statut &#8216;archivée&#8217;. Cependant, si vous écrivez une requête qui porte sur les commandes &#8216;en cours&#8217;, un index sur la colonne statut sera sûrement utile, même si sa cardinalité sera ridicule (2 en l&#8217;occurence).</p>
<p>Ce qui joue ici n&#8217;est pas seulement la cardinalité de l&#8217;index, mais aussi la distribution des valeurs et surtout les requêtes que vous faites. Si avec la même table des commandes, votre voisin ne s&#8217;intéresse qu&#8217;aux commandes archivées, l&#8217;index n&#8217;a plus aucun intérêt. C&#8217;est d&#8217;ailleurs une situation que l&#8217;on rencontre fréquemment : les requêtes que l&#8217;on peut faire en mode transactionnel ou en mode décisionnel sont radicalement différentes, exigeant souvent d&#8217;avoir des installations dédiées avec une indexation différente.</p>
<h3>Conclusion</h3>
<p>Cet article a montré que le repérage des index inutiles est loin d&#8217;être évident. Mis à part les index en doublon qu&#8217;on peut repérer et éliminer facilement, il est nécessaire d&#8217;avoir une bonne connaissance des requêtes qui sont exécutées afin de déterminer si un index est inutile. Le prochain article va se concentrer sur des méthodes plus systématiques d&#8217;identification des index inutiles.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dbnewz.com/2011/07/06/index-candidats-a-la-suppression/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Le coût des index inutiles &#8211; 2nde partie</title>
		<link>http://www.dbnewz.com/2011/05/27/le-cout-des-index-inutiles-2nde-partie/</link>
		<comments>http://www.dbnewz.com/2011/05/27/le-cout-des-index-inutiles-2nde-partie/#comments</comments>
		<pubDate>Fri, 27 May 2011 15:50:34 +0000</pubDate>
		<dc:creator>stephane</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[index]]></category>
		<category><![CDATA[pratique]]></category>

		<guid isPermaLink="false">http://www.dbnewz.com/?p=885</guid>
		<description><![CDATA[Dans l&#8217;article précédent, nous nous étions demandés quelle était la dégradation des performances en écriture quand on ajoute des index. On peut élargir la réflexion en se penchant sur les conditions qui améliorent ou diminuent la vitesse d&#8217;écriture dans une table.

Avant de commencer de nouvelles expérimentations, rappelons les conditions du test. La table utilisée a [...]]]></description>
			<content:encoded><![CDATA[<p>Dans l&#8217;article précédent, nous nous étions demandés quelle était la dégradation des performances en écriture quand on ajoute des index. On peut élargir la réflexion en se penchant sur les conditions qui améliorent ou diminuent la vitesse d&#8217;écriture dans une table.<br />
<span id="more-885"></span><br />
Avant de commencer de nouvelles expérimentations, rappelons les conditions du test. La table utilisée a la structure suivante :<br />
<code><br />
CREATE TABLE (<br />
&nbsp;&nbsp;id int(11) NOT NULL AUTO_INCREMENT,<br />
&nbsp;&nbsp;col_a varchar(30) NOT NULL DEFAULT '',<br />
&nbsp;&nbsp;col_b varchar(30) NOT NULL DEFAULT '',<br />
&nbsp;&nbsp;col_c varchar(30) NOT NULL DEFAULT '',<br />
PRIMARY KEY (id)<br />
);<br />
</code><br />
et nous cherchons à insérer un millions de lignes à l&#8217;aide de la commande <code>LOAD DATA INFILE</code>.</p>
<p>Rappelons aussi que pour améliorer les performances en écriture dans l&#8217;absolu, il existe d&#8217;autres pistes que nous n&#8217;explorerons pas dans cet article :</p>
<ul>
<li>si la table utilise InnoDB ou XtraDB, modifier certaines variables de configuration (<code>innodb_log_file_size</code>,<code>innodb_flush_log_at_trx_commit</code>,<br />
<code>innodb_adaptive_flushing</code>, etc)</li>
<li>si la table utilise MyISAM, modifier d&#8217;autres variables de configuration (<code>myisam_sort_buffer_size</code>, <code>bulk_insert_buffer_size</code>)</li>
<li>faire des insertions multithreadées</li>
<li>désactiver les vérifications de clés étrangères ou de clés uniques</li>
<li>&#8230;</li>
</ul>
<p>Pour commencer nos test, examinons le comportement de MyISAM. Le paramètre clé est la taille du cache d&#8217;index (<code>key_buffer_size</code>), nous faisons donc un essai avec un cache d&#8217;index suffisamment grand pour pouvoir contenir tous les index et un essai avec un cache trop petit.</p>
<p>MyISAM, key_buffer_size grand :<br />
Seulement la clé primaire : 4s<br />
Clé primaire + index sur col_a : 6,6s<br />
Clé primaire + index sur col_b : 9,3s<br />
Clé primaire + index sur (col_a,col_b) : 6,8s</p>
<p>MyISAM, key_buffer_size petit :<br />
Seulement la clé primaire : 4s<br />
Clé primaire + index sur col_a : 6,8s<br />
Clé primaire + index sur col_b : 9,3s<br />
Clé primaire + index sur (col_a,col_b) : 6,8s</p>
<p>A comparer avec les résultats obtenus la dernière fois avec InnoDB :<br />
Seulement la clé primaire : 15s<br />
Clé primaire + index sur col_a : 28,5s<br />
Clé primaire + index sur col_a + index sur col_b : 44,5s<br />
Clé primaire + index sur (col_a,col_b) : 36,3s</p>
<p>Très intéressant ! MyISAM se révèle plus performant qu&#8217;InnoDB, et de loin.</p>
<p>Attention, ne faisons pas de généralités sur la vitesse de MyISAM ! Ce test se déroule sur un serveur hors production qui n&#8217;exécute que les <code>LOAD DATA INFILE</code>. Il est certain que l&#8217;écart entre InnoDB et MyISAM ne serait pas aussi important si les insertions étaient multithreadées (verrouillage de toute la table pour MyISAM) et si le serveur recevait également des requêtes en lecture (les écritures seraient peut-être rapides mais les lectures souffriraient sans doute).</p>
<p>De plus, on voit que la taille du cache d&#8217;index n&#8217;influence quasiment pas le temps de chargement, ce qui est plutôt inattendu. Une explication possible serait que les données et index ne soient pas réellement écrits sur le disque, mais restent dans le cache de l&#8217;OS. L&#8217;hypothèse semble raisonnable puisqu&#8217;un coup d&#8217;oeil sur iostat pendant les <code>LOAD DATA INFILE</code> montre que les écritures sur le disque sont nettement plus nombreuses quand la table est en InnoDB.</p>
<p>Faisons donc un autre test avec MyISAM, pour lequel le cache d&#8217;index est petit et la quantité de mémoire disponible pour l&#8217;OS est trop faible pour pouvoir contenir les données et les index :<br />
Seulement la clé primaire : 6,8s<br />
Clé primaire + index sur col_a : 14s<br />
Clé primaire + index sur col_b : 24s<br />
Clé primaire + index sur (col_a,col_b) : 18s</p>
<p>Les performances sont bien moins bonnes qu&#8217;avant, mais restent encore supérieures à celles d&#8217;InnoDB !</p>
<p>Concentrons nous maintenant sur InnoDB et voyons l&#8217;influence du type de clé primaire sur le temps de chargement. Pour simplifier, mettons-nous toujours dans le cas où le buffer_pool est suffisamment grand pour contenir données et index.</p>
<p>Dans un premier temps, optons pour une clé primaire composite (id,col_c) :<br />
Seulement la clé primaire : 17,8s<br />
Clé primaire + index sur col_a : 40s<br />
Clé primaire + index sur col_b : 72s<br />
Clé primaire + index sur (col_a,col_b) : 51,7s</p>
<p>On voit qu&#8217;avoir une clé primaire composite a un impact non négligeable lorsqu&#8217;il n&#8217;existe pas d&#8217;autres index (20% de perte de performance) et que cet impact négatif est encore plus important quand on ajoute des index. Ce résultat était attendu, puisqu&#8217;avec InnoDB, chaque index secondaire contient la clé primaire : plus la clé primaire est volumineuse, plus les index secondaires sont volumineux et longs à mettre à jour.</p>
<p>Dans un second temps, supprimons la clé primaire :<br />
Seulement la clé primaire : 14,6s<br />
Clé primaire + index sur col_a : 29,2s<br />
Clé primaire + index sur col_b : 45,5s<br />
Clé primaire + index sur (col_a,col_b) : 38,1s</p>
<p>Les temps obtenus sont proches du cas initial où id est la clé primaire. Comment expliquer ce résultat ? InnoDB a besoin d&#8217;une clé primaire pour toutes ses tables. Si vous ne la définissez pas vous-même et qu&#8217;il n&#8217;existe pas d&#8217;index unique n&#8217;ayant aucune valeur NULL, InnoDB créé une clé primaire cachée sous la forme d&#8217;un entier. On se retrouve donc avec une structure du type suivant :<br />
<code><br />
CREATE TABLE (<br />
&nbsp;&nbsp;hidden_id int NOT NULL AUTO_INCREMENT,<br />
&nbsp;&nbsp;id int(11) NOT NULL,<br />
&nbsp;&nbsp;col_a varchar(30) NOT NULL DEFAULT '',<br />
&nbsp;&nbsp;col_b varchar(30) NOT NULL DEFAULT '',<br />
&nbsp;&nbsp;col_c varchar(30) NOT NULL DEFAULT '',<br />
&nbsp;&nbsp;PRIMARY KEY (hidden_id)<br />
);<br />
</code><br />
On se retrouve avec la même structure qu&#8217;initialement, mais avec une colonne de plus, donc avec une volumétrie un peu plus importante, ce qui explique les résultats similaires mais légèrement inférieurs.</p>
<p>Que pouvons-nous conclure de tous ces test ? Tout d&#8217;abord, que le moteur de stockage a une grande influence sur les résultats. Si vous avez le choix du moteur, des tests s&#8217;imposent pour déterminer lequel est le meilleur, en ayant soin de prendre en compte la charge réelle de l&#8217;application (connexions concurrentes, lectures et écritures). Ensuite, que pour InnoDB, le choix de la clé primaire est critique : la clé primaire doit être la plus compacte possible, sous peine de performances très dégradées. Enfin, que pour MyISAM comme pour InnoDB, il vaut mieux limiter le nombre d&#8217;index qui ralentissent considérablement toutes les écritures.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dbnewz.com/2011/05/27/le-cout-des-index-inutiles-2nde-partie/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Instrumentation et performance</title>
		<link>http://www.dbnewz.com/2011/01/24/instrumentation-et-performance/</link>
		<comments>http://www.dbnewz.com/2011/01/24/instrumentation-et-performance/#comments</comments>
		<pubDate>Mon, 24 Jan 2011 16:52:21 +0000</pubDate>
		<dc:creator>stephane</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[index]]></category>
		<category><![CDATA[maatkit]]></category>
		<category><![CDATA[performance]]></category>
		<category><![CDATA[pratique]]></category>

		<guid isPermaLink="false">http://www.dbnewz.com/?p=828</guid>
		<description><![CDATA[Instrumenter son application correctement représente un pas important dans la recherche des performances optimales. De bons outils permettent également de gagner du temps, qui est toujours précis. Cet article va vous donner un exemple de la valeur ajoutée que peut procurer un bon outil : le simple fait d&#8217;obtenir un rapport précis sur un problème [...]]]></description>
			<content:encoded><![CDATA[<p>Instrumenter son application correctement représente un pas important dans la recherche des performances optimales. De bons outils permettent également de gagner du temps, qui est toujours précis. Cet article va vous donner un exemple de la valeur ajoutée que peut procurer un bon outil : le simple fait d&#8217;obtenir un rapport précis sur un problème rencontré permet de résoudre en 5 minutes un gros problème de performance qui ne trouvait pas de solution depuis des semaines.<span id="more-828"></span></p>
<p>L&#8217;application en question dispose d&#8217;une table user permettant (surprise !!) de stocker les informations sur les utilisateurs. Cette table contient environ 30 millions de lignes et a l&#8217;allure suivante :</p>
<p><code><br />
CREATE TABLE user (<br />
  &nbsp;&nbsp;user_id int(11) NOT NULL AUTO_INCREMENT,<br />
  &nbsp;&nbsp;login varchar(30) NOT NULL DEFAULT '',<br />
  &nbsp;&nbsp;name varchar(50) NOT NULL DEFAULT '',<br />
  &nbsp;&nbsp;col_a varchar(30) NOT NULL DEFAULT '',<br />
  &nbsp;&nbsp;col_b varchar(30) NOT NULL DEFAULT '',<br />
  &nbsp;&nbsp;col_c varchar(30) NOT NULL DEFAULT '',<br />
  &nbsp;&nbsp;PRIMARY KEY (user_id),<br />
  &nbsp;&nbsp;UNIQUE KEY login (login)<br />
) ENGINE=InnoDB;<br />
</code><br />
Notons que la base ne tient pas en mémoire.</p>
<p>Un rapport quotidien basé sur <code>mysqldumpslow</code> envoyait les requêtes lentes de l&#8217;application. Parmi ces requêtes, on en trouvait une très étrange, prenant plus de 30s :<br />
<code><br />
Count: 195  Time=33.05s (6445s)  Lock=0.00s (0s)  Rows=129903.9 (25331256), user[db]@81hosts<br />
  SELECT user_id FROM user WHERE login = N<br />
</code></p>
<p>Pourquoi est-il étrange que cette requête soit aussi lente ? Tout simplement parce qu&#8217;il existe un index sur la colonne login et que la table étant en InnoDB, on peut profiter de l&#8217;index cluster pour couvrir la requête avec l&#8217;index login. Si la dernière phrase ne vous a paru très claire, regardez donc <a href="http://www.dbnewz.com/2008/10/24/dessine-moi-mysql-structure-dun-index-myisam-et-innodb/">cet article</a> et <a href="http://www.dbnewz.com/2008/11/20/les-covering-index-de-la-theorie-a-la-pratique-avec-myisam-et-innodb/">celui-là</a> également.</p>
<p>On peut confirmer facilement la validité de notre hypothèse sur le plan d&#8217;exécution avec <code>EXPLAIN</code> en prenant un login au hasard :<br />
<code><br />
mysql&gt; EXPLAIN SELECT user_id FROM test.user WHERE login="aqkme123456"\G<br />
***** 1. row *****<br />
          &nbsp;&nbsp; id: 1<br />
 &nbsp;&nbsp; select_type: SIMPLE<br />
     &nbsp;&nbsp;   table: user<br />
     &nbsp;&nbsp;    type: const<br />
&nbsp;&nbsp;&nbsp;possible_keys: login<br />
   &nbsp;&nbsp;       key: login<br />
     &nbsp;&nbsp; key_len: 32<br />
    &nbsp;&nbsp;      ref: const<br />
    &nbsp;&nbsp;     rows: 1<br />
  &nbsp;&nbsp;      Extra: Using index<br />
</code></p>
<p>Alors certes, la base ne tenant pas en mémoire, on ne sait pas si l&#8217;index login est en mémoire ou pas, mais dans la mesure où on accède directement à la bonne ligne, il n&#8217;y a vraiment aucune raison pour que la requête prenne plus de 30 secondes. Et en effet, les différents essais que j&#8217;ai pu faire avec des vraies valeurs donnent toujours un résultat instantané.</p>
<p>Poussons donc la réflexion : le problème de <code>mysqldumpslow</code>, c&#8217;est que l&#8217;outil ne donne qu&#8217;un exemple avec des valeurs abstraites. Il nous faudrait ici un exemple avec une valeur qui a provoqué une requête de 30s. Un autre outil permettrait-il d&#8217;avoir un tel exemple ? Oui, avec <code>mk-query-digest</code> de Maatkit par exemple.</p>
<p>Installons donc <code><a href="http://www.dbnewz.com/2010/09/22/outils-danalyse-de-requetes-lentes-mk-query-digest/">mk-query-digest</a></code>, demandons lui de générer un rapport sur les logs de la veille, et regardons l&#8217;exemple réel donné par l&#8217;outil :</p>
<p><code><br />
SELECT user_id FROM user WHERE login = 123456<br />
</code></p>
<p>Et voilà, soudainement tout est devenu limpide !! Non ? Mais si, regardez bien, la condition <code>WHERE</code> porte sur la colonne login de type <code>VARCHAR</code> et on passe un entier ! Ce qui change tout car MySQL convertit login en entier, ce qui empêche l&#8217;utilisation de l&#8217;index (rappelez-vous, MySQL ne connaît pas les<a href="http://www.dbnewz.com/2009/04/01/le-trigger-au-secours-des-function-based-index-fbi/"> function-based index</a>) et conduit donc à un parcours complet de la table.</p>
<p>En réalité, cette analyse n&#8217;est pas tout à fait exacte car l&#8217;index sur login est couvrant pour la requête : il est donc possible de faire un parcours complet de la table non pas en parcourant la clé primaire mais en parcourant l&#8217;index sur login. Quelle stratégie préfère choisir MySQL ?<br />
<code><br />
mysql&gt; EXPLAIN SELECT user_id FROM test.user WHERE login=123456\G<br />
***** 1. row *****<br />
 &nbsp;&nbsp;          id: 1<br />
 &nbsp;&nbsp; select_type: SIMPLE<br />
 &nbsp;&nbsp;       table: user<br />
 &nbsp;&nbsp;        type: index<br />
&nbsp;&nbsp;&nbsp;possible_keys: login<br />
&nbsp;&nbsp;          key: login<br />
&nbsp;&nbsp;      key_len: 32<br />
     &nbsp;&nbsp;     ref: NULL<br />
    &nbsp;&nbsp;     rows: 33153173<br />
    &nbsp;&nbsp;    Extra: Using where; Using index<br />
</code></p>
<p>MySQL choisit d&#8217;utiliser l&#8217;index sur login. Etait-ce la meilleure idée, autrement dit, était-il plus rapide de scanner la table avec l&#8217;index sur login (ce qui conduit à de nombreuses lectures aléatoires) plutôt que de scanner la table séquentiellement ? Pour y répondre, le seul moyen est de faire un test avec les 2 méthodes, en utilisant le modificateur <code>IGNORE INDEX(login)</code> dans le second cas pour forcer le scan de la table. Et dans ce cas, les tests montrent que le scan avec l&#8217;index sur login est plus rapide.</p>
<p>Dernier point, qui montre à la fois que MySQL convertit bien le champ login en entier et que la requête est complètement erronée : si je joue la requête <code>SELECT user_id FROM user WHERE login = 123456</code> sur mes données de production, j&#8217;obtiens non pas un résultat mais plus de 2000 ! Pourquoi un tel résultat alors que le login est censé être unique ? Tout simplement parce qu&#8217;en convertissant le champ login en entier, on perd de l&#8217;information : toutes les chaînes de caractères commençant par &laquo;&nbsp;123456&#8243; seront converties vers l&#8217;entier 123456 :<br />
<code><br />
mysql&gt; SELECT CONVERT("123456toto",UNSIGNED);<br />
+--------------------------------+<br />
| CONVERT("123456toto",UNSIGNED) |<br />
+--------------------------------+<br />
|                         123456 |<br />
+--------------------------------+<br />
</code><br />
<code><br />
mysql&gt; SHOW WARNINGS;<br />
+---------+------+-------------------------------------------------+<br />
| Level   | Code | Message                                         |<br />
+---------+------+-------------------------------------------------+<br />
| Warning | 1292 | Truncated incorrect INTEGER value: '123456toto' |<br />
+---------+------+-------------------------------------------------+<br />
</code></p>
<p>La correction du problème a finalement été très simple : il a suffi au développeur de forcer l&#8217;ajout des quotes dans la requête.</p>
<p>Morale de l&#8217;histoire : utiliser de bons outils peut vous faire gagner un temps précieux ! Notez que si j&#8217;avais été très méticuleux, j&#8217;aurais pu arriver au même résultat avec <code>mysqldumpslow</code>, puisque la ligne qui m&#8217;était indiquée donnait <code>WHERE login = N</code>, où N représente la valeur abstraite d&#8217;un entier alors qu&#8217;il aurait fallu avoir <code>WHERE login = S</code>, S représentant une chaîne. Mais c&#8217;était plus facile avec <code>mk-query-digest</code>, non ?</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dbnewz.com/2011/01/24/instrumentation-et-performance/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>30 questions sur MySQL &#8211; Réponses de la partie 2</title>
		<link>http://www.dbnewz.com/2010/11/19/30-questions-sur-mysql-reponses-de-la-partie-2/</link>
		<comments>http://www.dbnewz.com/2010/11/19/30-questions-sur-mysql-reponses-de-la-partie-2/#comments</comments>
		<pubDate>Fri, 19 Nov 2010 14:52:53 +0000</pubDate>
		<dc:creator>stephane</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[pratique]]></category>

		<guid isPermaLink="false">http://www.dbnewz.com/?p=768</guid>
		<description><![CDATA[Voici les réponses de la 2è partie de notre quiz. Là encore, des commentaires expliquent rapidement les réponses.

16- a : Le nom d&#8217;hôte n&#8217;étant pas spécifié, ce sera donc localhost. Et pour MySQL, cela indique que le client cherche à se connecter à travers une socket : le numéro de port est ignoré. Pour que [...]]]></description>
			<content:encoded><![CDATA[<p>Voici les réponses de la 2è partie de notre quiz. Là encore, des commentaires expliquent rapidement les réponses.<br />
<span id="more-768"></span></p>
<p>16- a : Le nom d&#8217;hôte n&#8217;étant pas spécifié, ce sera donc localhost. Et pour MySQL, cela indique que le client cherche à se connecter à travers une socket : le numéro de port est ignoré. Pour que le port soit pris en compte, il faut ajouter soit -h=127.0.0.1 soit &#8211;protocol=tcp</p>
<p>17- b : Conséquence : quand MySQL a besoin de créer une table temporaire de manière interne (pour un tri par exemple) et que la table contient un champ TEXT ou dérivé, elle est automatiquement créée sur disque et risque de poser des problèmes de performance</p>
<p>18- b : Et malheureusement, la taille du tablespace ne diminue pas !</p>
<p>19- a : Seuls les index des tables MyISAM peuvent être mis en cache par le serveur, les données ne peuvent l&#8217;être que par l&#8217;OS, ce qui est beaucoup moins efficace</p>
<p>20- b : Toutes les requêtes ne peuvent pas utiliser de requêtes préparées (ex : SELECT avec un LIMIT dynamique)</p>
<p>21- a : Cette option démarre une transaction et fait le dump à l&#8217;intérieur de cette transaction</p>
<p>22- b : On trouve également cette possibilité dans le Percona Server (http://www.percona.com/docs/wiki/percona-server:features:innodb_split_buf_pool_mutex)</p>
<p>23- c : Avec InnoDB, la clé primaire se retrouve automatiquement dans tout index secondaire, d&#8217;où l&#8217;intérêt aussi d&#8217;avoir une clé primaire aussi compacte que possible</p>
<p>24- b : InnoDB stocke les données avec la clé primaire, scanner les données revient donc à scanner la clé primaire</p>
<p>25- c : En particulier, si le tri se fait dans une table temporaire, vous verrez alors également la mention Using temporary</p>
<p>26- a : </p>
<p>27- b : </p>
<p>28- c : Avec MySQL 5.5 et la fonctionnalité de réplication semi-synchrone, on peut faire en sorte qu&#8217;un ou plusieurs esclaves n&#8217;aient jamais de retard sur le maître</p>
<p>29- b : </p>
<p>30- b : Données et index peuvent être fragmentées</p>
<p>Comme dans tout bon quiz, c&#8217;est le moment de faire un petit bilan :</p>
<p>Si vous avez moins de 10 bonnes réponses sur 30 : apparemment, ce n&#8217;était pas un bon jour pour vous. Allez, soyez persévérant, tout le monde a des choses à apprendre !</p>
<p>Si vous avez entre 10 et 20 bonnes réponses : manifestement, vous connaissez bien les bases de MySQL, mais il vous reste encore à vous frotter aux sujets complexes pour acquérir une bonne maîtrise du sujet.</p>
<p>Si vous avez entre 20 et 25 bonnes réponses : votre expérience et vos connaissances vous permettent de répondre à la plupart des problèmes que vous pourrez rencontrer avec MySQL. Encore un peu de patience et de travail et vous deviendrez un véritable expert.</p>
<p>Si vous avez plus de 25 bonnes réponses : MySQL n&#8217;a plus beaucoup de secrets pour vous. Bravo !</p>
<p>Alors, quel est votre score ?</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dbnewz.com/2010/11/19/30-questions-sur-mysql-reponses-de-la-partie-2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>30 questions sur MySQL &#8211; Partie 2</title>
		<link>http://www.dbnewz.com/2010/11/05/30-questions-sur-mysql-partie-2/</link>
		<comments>http://www.dbnewz.com/2010/11/05/30-questions-sur-mysql-partie-2/#comments</comments>
		<pubDate>Fri, 05 Nov 2010 16:26:08 +0000</pubDate>
		<dc:creator>stephane</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[pratique]]></category>

		<guid isPermaLink="false">http://www.dbnewz.com/?p=756</guid>
		<description><![CDATA[Après notre petit échauffement avec les 15 premières questions du quiz, voici la tant attendue deuxième partie ! Bon courage et à bientôt pour la deuxième série de réponses !
16- Sur un serveur Linux, deux instances de MySQL sont installées, l&#8217;une sur le port 3306 et l&#8217;autre sur le port 3307. Avec la commande mysql [...]]]></description>
			<content:encoded><![CDATA[<p>Après notre petit échauffement avec les 15 premières questions du quiz, voici la tant attendue deuxième partie ! Bon courage et à bientôt pour la deuxième série de réponses !<span id="more-756"></span></p>
<p>16- Sur un serveur Linux, deux instances de MySQL sont installées, l&#8217;une sur le port 3306 et l&#8217;autre sur le port 3307. Avec la commande mysql -uroot -p -P3307, sur quelle instance se connecte-t-on ?</p>
<ul>
<ol>
a) L&#8217;instance écoutant sur le port 3306</ol>
<ol>
b) L&#8217;instance écoutant sur le port 3307</ol>
</ul>
<p>17- Pour une table MEMORY, quelle est l&#8217;affirmation suivante qui est vraie ?</p>
<ul>
<ol>
a) Les tables ne peuvent pas contenir plus d&#8217;un million de lignes</ol>
<ol>
b) Les colonnes de type TEXT et dérivées ne sont pas autorisées</ol>
<ol>
c) Les données ne peuvent pas être répliquées vers un serveur esclave</ol>
</ul>
<p>18- L&#8217;option innodb_file_per_table étant désactivée, quand on supprime une table InnoDB :</p>
<ul>
<ol>
a) L&#8217;espace disque occupé par la table est libéré et récupéré par le système d&#8217;exploitation</ol>
<ol>b) L&#8217;espace disque occupé par la table est libéré et récupéré par le tablespace principal InnoDB</ol>
<ol>
c) L&#8217;espace disque occupé par la table est conservé tant qu&#8217;un COMMIT n&#8217;a pas été exécuté</ol>
</ul>
<p>19- Quelle est la directive qui permet de spécifier la quantité de mémoire à allouer pour que les données des tables MyISAM soient mises en cache par le serveur MySQL ?</p>
<ul>
<ol>
a) Il n&#8217;existe pas de telle directive</ol>
<ol>b) key_buffer_size</ol>
<ol>c) innodb_buffer_pool</ol>
</ul>
<p>20- L&#8217;utilisation des requêtes préparées permet d&#8217;éliminer tout risque d&#8217;injection SQL.</p>
<ul>
<ol>
a) Vrai</ol>
<ol>
b) Faux</ol>
</ul>
<p>21- Quelle est la meilleure option de mysqldump à utiliser pour réaliser une sauvegarde à chaud d&#8217;une base InnoDB ?</p>
<ul>
<ol>
a) &#8211;single-transaction</ol>
<ol>b) &#8211;master-data</ol>
<ol>c) &#8211;lock-all-tables</ol>
</ul>
<p>22- MySQL 5.5 offre la possibilité d&#8217;avoir plusieurs buffer pools pour InnoDB. Quel est l&#8217;intérêt ?</p>
<ul>
<ol>
a) Repousser la limitation sur la taille du buffer pool présente en version 5.1</ol>
<ol>
b) Améliorer les performances en limitant les contentions sur la mutex protégeant le buffer pool</ol>
<ol>
c) Se mettre en conformité avec la norme SQL ANSI 2008</ol>
</ul>
<p>23- Une table a pour clé primaire une colonne id. Que penser d&#8217;un index sur les colonnes (name, id) ?</p>
<ul>
<ol>
a) L&#8217;index peut être simplifié en un index sur la colonne name.</ol>
<ol>
b) L&#8217;index peut être simplifié en un index sur la colonne name seulement si la table utilise MyISAM.</ol>
<ol>
c) L&#8217;index peut être simplifié en un index sur la colonne name seulement si la table utilise InnoDB.</ol>
</ul>
<p>24- Pour une table InnoDB, quelle est la différence entre les deux motifs d&#8217;accès suivants donnés par EXPLAIN :<br />
<code>type: index, key: PRIMARY<br />
type: ALL, key: NULL</code></p>
<ul>
<ol>
a) La clé primaire étant triée, l&#8217;accès type: index est plus efficace</ol>
<ol>
b) Les deux type d&#8217;accès sont identiques</ol>
<ol>
c) L&#8217;accès type:ALL accède directement aux données sans passer par l&#8217;index, il est donc plus efficace</ol>
</ul>
<p>25- Que signifie la mention Using filesort dans EXPLAIN ?</p>
<ul>
<ol>
a) Le tri demandé dans la requête se fait dans un fichier sur disque</ol>
<ol>
b) Le tri demandé dans la requête se fait dans une table temporaire sur disque</ol>
<ol>
c) Le tri demandé dans la requête n&#8217;utilise pas d&#8217;index</ol>
</ul>
<p>26- La clé de partitionnement doit faire partie de la clé primaire.</p>
<ul>
<ol>
a) Vrai</ol>
<ol>
b) Faux</ol>
</ul>
<p>27- Une configuration master-master actif-actif permet-elle d&#8217;augmenter le nombre d&#8217;écritures ?</p>
<ul>
<ol>
a) Oui : on peut écrire sur les deux serveurs en même temps</ol>
<ol>
b) Non : chaque serveur doit jouer 100% des écritures, la partie reçue en tant que slave est sérialisée</ol>
</ul>
<p>28- Comment garantir qu&#8217;un esclave ait au maximum n secondes de retard sur son maître ?</p>
<ul>
<ol>
a) Utiliser une réplication master-master, le master actif sera en effet automatiquement ralenti en recevant les binlogs du master passif</ol>
<ol>
b) Utiliser n esclaves</ol>
<ol>
c) Ce n&#8217;est pas possible nativement</ol>
</ul>
<p>29- Maatkit contient-il un outil repérant les index redondants ou en doublon ?</p>
<ul>
<ol>
a) Oui, mk-index-usage</ol>
<ol>
b) Oui, mk-duplicate-key-checker</ol>
<ol>
c) Oui, mais c&#8217;est inutile car la commande CHECK TABLE fait déjà le travail depuis MySQL 5.1</ol>
</ul>
<p>30- Une table MyISAM ayant l&#8217;option ROW_FORMAT=FIXED ne peut pas être fragmentée.</p>
<ul>
<ol>
a) Vrai</ol>
<ol>
b) Faux</ol>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.dbnewz.com/2010/11/05/30-questions-sur-mysql-partie-2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>30 questions sur MySQL &#8211; Réponses de la partie 1</title>
		<link>http://www.dbnewz.com/2010/10/25/30-questions-sur-mysql-reponses-de-la-partie-1/</link>
		<comments>http://www.dbnewz.com/2010/10/25/30-questions-sur-mysql-reponses-de-la-partie-1/#comments</comments>
		<pubDate>Mon, 25 Oct 2010 14:24:41 +0000</pubDate>
		<dc:creator>stephane</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[pratique]]></category>

		<guid isPermaLink="false">http://www.dbnewz.com/?p=747</guid>
		<description><![CDATA[Et voici comme promis les réponses de la 1ère partie du quiz. Dans la mesure du possible, j&#8217;ai ajouté quelques petits commentaires pour expliquer le pourquoi du comment.
1- b : Toutes les ressources doivent etre étanches entre les instances
2- a : MyISAM garde dans ses méta-données le nombre de lignes de la table
3- b : [...]]]></description>
			<content:encoded><![CDATA[<p>Et voici comme promis les réponses de la 1ère partie du quiz. Dans la mesure du possible, j&#8217;ai ajouté quelques petits commentaires pour expliquer le pourquoi du comment.<span id="more-747"></span></p>
<p>1- b : Toutes les ressources doivent etre étanches entre les instances</p>
<p>2- a : MyISAM garde dans ses méta-données le nombre de lignes de la table</p>
<p>3- b : InnoDB remplit d&#8217;abord le 1er fichier, puis le 2nd, on ne peut pas parler de distribution des écritures</p>
<p>4- c : <a href="http://dev.mysql.com/doc/refman/5.1/en/privilege-changes.html">Certains changements de droits</a> n&#8217;affectent pas les sessions déjà ouvertes</p>
<p>5- a : Le tablespace principal contient des informations indispensables au bon fonctionnnement d&#8217;InnoDB, meme avec innodb_file_per_table</p>
<p>6- c : La réplication ne constitue pas une sauvegarde</p>
<p>7- a : Depuis MySQL 5.1 et la fonctionnalité de plugins, les versions d&#8217;InnoDB peuvent sortir indépendamment des versions de MySQL</p>
<p>8- b : En général, MySQL n&#8217;utilise qu&#8217;un seul index par table et par requete, un index multi-colonnes est donc souvent le plus efficace</p>
<p>9- c</p>
<p>10- a : C&#8217;est au cas par cas qu&#8217;il faut regarder qui est le plus performant, jointure ou sous-requete</p>
<p>11- c</p>
<p>12- b : Là encore, il faut regarder au cas par cas</p>
<p>13- b : Les autres méthodes sont risquées car elles vont poser des problèmes dans certains cas</p>
<p>14- a : Si vous ne connaissez pas MySQL Sandbox, voici un <a href="http://www.dbnewz.com/2008/10/10/15-secondes-pour-installer-une-replication-mysql-avec-mysql-sandbox-pari-tenu/">bon article</a> pour débuter</p>
<p>15- a : En général, ALTER TABLE crée une table temporaire pour chaque modification, mieux vaut donc les regrouper</p>
<p>Faites-moi savoir si certaines réponses vous étonnent, ça pourra être l&#8217;occasion de revenir sur ces points dans de prochains posts. Et bien sûr, dans le prochain article vont suivre les 15 questions restantes. Attention, d&#8217;une manière générale, les questions qui vont venir sont plus difficiles !!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dbnewz.com/2010/10/25/30-questions-sur-mysql-reponses-de-la-partie-1/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>30 questions sur MySQL &#8211; Partie 1</title>
		<link>http://www.dbnewz.com/2010/10/13/30-questions-sur-mysql-partie-1/</link>
		<comments>http://www.dbnewz.com/2010/10/13/30-questions-sur-mysql-partie-1/#comments</comments>
		<pubDate>Wed, 13 Oct 2010 12:10:25 +0000</pubDate>
		<dc:creator>stephane</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[pratique]]></category>

		<guid isPermaLink="false">http://www.dbnewz.com/?p=736</guid>
		<description><![CDATA[La rentrée est passée depuis quelques semaines, et dbnewz vous propose un petit quiz pour faire le point sur vos connaissances en MySQL. Ce quiz en 2 parties contient un total de 30 questions qui abordent les principaux domaines de notre base de données favorite : réplication, sauvegarde, performance des requêtes, installation, moteurs de stockage, [...]]]></description>
			<content:encoded><![CDATA[<p>La rentrée est passée depuis quelques semaines, et dbnewz vous propose un petit quiz pour faire le point sur vos connaissances en MySQL. Ce quiz en 2 parties contient un total de 30 questions qui abordent les principaux domaines de notre base de données favorite : réplication, sauvegarde, performance des requêtes, installation, moteurs de stockage, outils&#8230;<br />
Tous les documents sont bien sûr autorisés !<br />
A vos marques, prêts ? Partez &#8230;<span id="more-736"></span></p>
<p>1- Quand on installe deux instances de MySQL sur la même machine hôte, les deux instances ne peuvent pas partager le même port, mais peuvent partager la même socket.</p>
<ul>
<ol>
a) Vrai</ol>
<ol>
b) Faux</ol>
</ul>
<p>2- Quelle est la requête dont l&#8217;exécution est immédiate, la table t utilisant le moteur MyISAM et id étant la clé primaire ?</p>
<ul>
<ol>
a) SELECT COUNT(*) FROM t</ol>
<ol>
	b) SELECT COUNT(*) FROM t WHERE id &gt; 5</ol>
<ol>
c) SELECT id FROM t</ol>
</ul>
<p>3- Quand on n&#8217;utilise pas l&#8217;option innodb_file_per_table, spécifier plusieurs tablespaces avec l&#8217;option innodb_data_file_path permet de distribuer les écritures InnoDB sur plusieurs fichiers.</p>
<ul>
<ol>
a) Vrai</ol>
<ol>
b) Faux</ol>
</ul>
<p>4- Les droits d&#8217;un utilisateur ont été changés avec la commande GRANT. Que faut-il faire pour être certain que les nouveaux droits soient pris en compte ?</p>
<ul>
<ol>
a) Rien</ol>
<ol>
b) Lancer FLUSH PRIVILEGES</ol>
<ol>
c) Tuer les sessions de l&#8217;utilisateur si elles existent</ol>
</ul>
<p>5- Quand l&#8217;option innodb_file_per_table est activée, sauvegarder les fichiers .ibd n&#8217;est pas suffisant.</p>
<ul>
<ol>
a) Vrai</ol>
<ol>
b) Faux</ol>
</ul>
<p>6- Quand on met en place une réplication maitre-esclave, il n&#8217;est plus nécessaire de faire des sauvegardes, l&#8217;esclave possédant une copie des données.</p>
<ul>
<ol>
	a) Vrai</ol>
<ol>
b) Vrai seulement s&#8217;il existe au moins deux esclaves, afin que le système soit redondant</ol>
<ol>
	c) Faux</ol>
</ul>
<p>7- Qu&#8217;est le plugin InnoDB ?</p>
<ul>
<ol>
a) Une version d&#8217;InnoDB installable sous forme de plugin pour MySQL 5.1 avec des fonctionnalités supplémentaires par rapport à la version d&#8217;InnoDB fournie en standard avec MySQL 5.1</ol>
<ol>
b) Un add-on qui facilite la migration de MySQL vers Oracle</ol>
<ol>
c) Un script permettant d&#8217;effectuer des backups à chaud des tables InnoDB</ol>
</ul>
<p>8- Pour répondre à une clause de type WHERE type = &laquo;&nbsp;XXX&nbsp;&raquo; AND customer_id = 123, quelle est a priori la meilleure indexation ?</p>
<ul>
<ol>
a) Aucun index supplémentaire n&#8217;est nécessaire, la clé primaire suffit pour ce type de filtrage</ol>
<ol>
b) Un index sur les 2 colonnes type et customer_id</ol>
<ol>
c) Un index sur type et un index sur customer_id</ol>
</ul>
<p>9- Dans une jointure à deux tables, est-il nécessaire d&#8217;indexer chaque colonne de la condition de jointure ?</p>
<ul>
<ol>
a) Oui</ol>
<ol>
b) Non</ol>
<ol>
c) Seul l&#8217;index de la table jointe est utilisé, mais l&#8217;optimiseur peut inverser l&#8217;ordre de jointure, mieux vaut donc en général indexer les deux colonnes</ol>
</ul>
<p>10- Une sous-requête est parfois plus performant qu&#8217;une jointure équivalente.</p>
<ul>
<ol>
a) Vrai</ol>
<ol>
b) Faux</ol>
</ul>
<p>11- Quelle est la commande qui permet de savoir quelles partitions sont examinées par une requête ?</p>
<ul>
<ol>
a) EXPLAIN</ol>
<ol>
b) EXPLAIN EXTENDED</ol>
<ol>
c) EXPLAIN PARTITIONS</ol>
</ul>
<p>12- Les procédures et fonctions stockées améliorent nettement les performances par rapport à un code similaire côté applicatif.</p>
<ul>
<ol>
a) Vrai</ol>
<ol>
b) Ca dépend</ol>
<ol>
c) Faux</ol>
</ul>
<p>13- Pour changer les coordonnées de réplication d&#8217;un esclave, quelle est la meilleure méthode ?</p>
<ul>
<ol>
a) Editer le fichier master.info</ol>
<ol>
b) Lancer une commande CHANGE MASTER TO</ol>
<ol>
c) Editer le fichier my.cnf</ol>
</ul>
<p>14- Pour mener des tests sur une configuration de réplication circulaire à 3 serveurs, quel outil peut être utilisé pour monter rapidement les serveurs ?</p>
<ul>
<ol>
a) MySQL Sandbox</ol>
<ol>
b) Maatkit</ol>
<ol>
c) Aucun outil n&#8217;est capable de réaliser cette opération</ol>
</ul>
<p>15- Si on souhaite, sur une même table, modifier le type d&#8217;une colonne et en ajouter une autre, quelle est la meilleure stratégie ?</p>
<ul>
<ol>
a) Lancer une seule commande ALTER TABLE pour modifier et ajouter en même temps</ol>
<ol>
b) Lancer une commande ALTER TABLE pour modifier, puis une commande ALTER TABLE pour ajouter</ol>
<ol>
c) Lancer une commande ALTER TABLE pour ajouter, puis une commande ALTER TABLE pour modifier</ol>
</ul>
<p>Alors, facile ? Rendez-vous très prochainement pour la deuxième partie du quiz et pour les réponses !</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dbnewz.com/2010/10/13/30-questions-sur-mysql-partie-1/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Outils d’analyse de requêtes lentes – mysqlsla</title>
		<link>http://www.dbnewz.com/2010/07/28/outils-d%e2%80%99analyse-de-requetes-lentes-%e2%80%93-mysqlsla/</link>
		<comments>http://www.dbnewz.com/2010/07/28/outils-d%e2%80%99analyse-de-requetes-lentes-%e2%80%93-mysqlsla/#comments</comments>
		<pubDate>Wed, 28 Jul 2010 14:01:15 +0000</pubDate>
		<dc:creator>stephane</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[log]]></category>
		<category><![CDATA[outils]]></category>
		<category><![CDATA[pratique]]></category>

		<guid isPermaLink="false">http://www.dbnewz.com/?p=622</guid>
		<description><![CDATA[Pour ce second volet de notre série consacrée aux outils d&#8217;analyse de requêtes lentes, nous allons nous pencher aujourd&#8217;hui sur mysqlsla, qui est un script Perl disposant de nombreuses options d&#8217;agrégation et de filtrage.
Commençons par l&#8217;installation du script. Rien de plus simple, il vous suffit pour commencer de télécharger et de décompresser une archive de [...]]]></description>
			<content:encoded><![CDATA[<p>Pour ce second volet de notre série consacrée aux outils d&#8217;analyse de requêtes lentes, nous allons nous pencher aujourd&#8217;hui sur <code>mysqlsla</code>, qui est un script Perl disposant de nombreuses options d&#8217;agrégation et de filtrage.<span id="more-622"></span></p>
<p>Commençons par l&#8217;installation du script. Rien de plus simple, il vous suffit pour commencer de télécharger et de décompresser une archive de l&#8217;outil, disponible <a href="http://hackmysql.com/scripts/mysqlsla-2.03.tar.gz">ici</a>. Ensuite, des classiques<br />
<code><br />
$ perl Makefile.PL<br />
$ make<br />
# make install<br />
</code><br />
vous permettent d&#8217;installer le script. Notez, point agréable, qu&#8217;une page de <code>man</code> est intégrée. Si vous cherchez la syntaxe d&#8217;une option, un <code>man mysqlsla</code> vous dispensera donc bien souvent d&#8217;aller faire un tour sur le site du projet.</p>
<p><code>mysqlsla</code> est plus généraliste que <code>mysqldumpslow</code> dans le sens où il est capable de traiter tout type de journal (requêtes lentes, mais aussi journal binaire ou journal général, ou encore journal défini par l&#8217;utilisateur). Les principes que nous allons voir dans cet article pour les requêtes lentes sont aussi valables pour les autres types de journaux.</p>
<p>L&#8217;idée de base de <code>mysqlsla</code> est de pouvoir analyser des fichiers journaux en leur appliquant éventuellement des filtres afin de ne garder que certains événements et d&#8217;émettre un rapport personnalisable présentant les résultats de cette analyse. Dans cet article nous allons seulement regarder les capacités de filtrage de <code>mysqlsla</code> et pas la manière de produire un rapport personnalisé, puisque le rapport par défaut nous convient très bien.</p>
<p>Pour un premier essai, lançons <code>mysqlsla</code> sans paramètre particulier à part l&#8217;option <code>lt</code> (comme log type) qui indique au script que le fichier passé est un journal de requêtes lentes. Cette option n&#8217;est en réalité pas indispensable car <code>mysqlsla</code> sait détecter automatiquement le type de journal :<br />
<code><br />
$ mysqlsla -lt slow msandbox-slow.log<br />
Report for slow logs: msandbox-slow.log<br />
4 queries total, 2 unique<br />
Sorted by 't_sum'<br />
Grand Totals: Time 1 s, Lock 0 s, Rows sent 1.80k, Rows Examined 64.18k<br />
</code><code><br />
________________________________________________ 001 ___<br />
Count         : 1  (25.00%)<br />
Time          : 718.344 ms total, 718.344 ms avg, 718.344 ms to 718.344 ms max  (79.40%)<br />
Lock Time (s) : 259 s total, 259 s avg, 259 s to 259 s max  (52.11%)<br />
Rows sent     : 0 avg, 0 to 0 max  (0.00%)<br />
Rows examined : 16.04k avg, 16.04k to 16.04k max  (25.00%)<br />
Database      : sakila<br />
Users         :<br />
	msandbox@localhost  : 100.00% (1) of query, 100.00% (4) of all users<br />
</code><code><br />
Query abstract:<br />
SET timestamp=N; INSERT INTO rental2 SELECT * FROM rental;<br />
</code><code><br />
Query sample:<br />
SET timestamp=1278504762;<br />
INSERT INTO rental2 SELECT * FROM rental;<br />
</code><code><br />
________________________________________________ 002 ___<br />
Count         : 3  (75.00%)<br />
Time          : 186.368 ms total, 62.123 ms avg, 52.575 ms to 74.232 ms max  (20.60%)<br />
Lock Time (s) : 238 s total, 79 s avg, 60 s to 117 s max  (47.89%)<br />
Rows sent     : 599 avg, 599 to 599 max  (100.00%)<br />
Rows examined : 16.04k avg, 16.04k to 16.04k max  (75.00%)<br />
Database      : sakila<br />
Users         :<br />
	msandbox@localhost  : 100.00% (3) of query, 100.00% (4) of all users<br />
</code><code><br />
Query abstract:<br />
SET timestamp=N; SELECT customer_id,COUNT(*) FROM rental WHERE return_date&gt;'S' GROUP BY customer_id;<br />
</code><code><br />
Query sample:<br />
SET timestamp=1278504711;<br />
SELECT customer_id,COUNT(*) FROM rental WHERE return_date&gt;'2005-01-01' GROUP BY customer_id;<br />
</code><br />
A première vue, ce rapport ressemble à celui de <code>mysqldumpslow</code> avec un regroupement des requêtes similaires. Le rapport est cependant plus complet car pour chaque groupe, nous avons les valeurs minimales, moyennes et maximales ainsi qu&#8217;une information très intéressante sur le poids relatif de chaque groupe, ce qui permet de cibler facilement les requêtes qui ont le plus contribué au temps de réponse ou qui ont examiné le plus de lignes.</p>
<p>Autre point à noter : pour chaque groupe de requêtes, <code>mysqlsla</code> nous présente une vue abstraite de la requête, c&#8217;est-à-dire une requête générique où les paramètres qui varient entre les requêtes du groupe sont remplacés par N pour un nombre ou S pour une chaîne de caractères, mais aussi un exemple de requête avec des vrais paramètres. C&#8217;est un bon point par rapport à <code>mysqldumpslow</code> puisqu&#8217;il est facile avec l&#8217;exemple de regarder le plan d&#8217;exécution donné par la commande <code>EXPLAIN</code>.</p>
<p>Intéressons-nous aux 4 questions que nous nous étions posées lors de l&#8217;<a href="http://www.dbnewz.com/2010/07/08/outils-danalyse-de-requetes-lentes-mysqldumpslow">article précédent</a>, qui vont nous permettre de voir comment utiliser les méta-données des requêtes pour trier ou filtrer.</p>
<p>Chaque requête dans le journal possède un certain nombre de méta-données, comme le temps d&#8217;exécution ou le nombre de lignes examinées. Il existe également des méta-données dérivées, comme la moyenne des temps d&#8217;exécution pour un groupe de requêtes. Les méta-données disponibles dépendent du type de journal considéré, et tout est détaillé dans la <a href="http://hackmysql.com/mysqlsla_filters">page de la documentation</a> consacrée aux filtres.</p>
<p><code>mysqlsla</code> nous permet de trier les résultats du rapport selon n&#8217;importe quelle méta-donnée avec l&#8217;option <code>--sort</code>.</p>
<p>Ceci va nous permettre de répondre aux 2 premières questions :</p>
<p>Quel est le groupe de requêtes ayant le plus long temps de réponse ?<br />
<code><br />
$ mysqlsla -lt slow --sort t_sum msandbox-slow.log<br />
</code><br />
Ici l&#8217;affichage est exactement celui que nous avions sans l&#8217;option <code>--sort</code>. C&#8217;est normal car pour les journaux de requêtes lentes, <code>mysqlsla</code> applique par défaut l&#8217;option <code>--sort t_sum</code> !</p>
<p>Quel est le groupe de requêtes ayant le plus grand nombre d&#8217;occurrences ?<br />
<code><br />
$ mysqlsla -lt slow --sort c_sum msandbox-slow.log<br />
Report for slow logs: msandbox-slow.log<br />
4 queries total, 2 unique<br />
Sorted by 'c_sum'<br />
Grand Totals: Time 1 s, Lock 0 s, Rows sent 1.80k, Rows Examined 64.18k<br />
</code><code><br />
_______________________________________________ 001 ___<br />
Count         : 3  (75.00%)<br />
Time          : 186.368 ms total, 62.123 ms avg, 52.575 ms to 74.232 ms max  (20.60%)<br />
Lock Time (s) : 238 s total, 79 s avg, 60 s to 117 s max  (47.89%)<br />
Rows sent     : 599 avg, 599 to 599 max  (100.00%)<br />
Rows examined : 16.04k avg, 16.04k to 16.04k max  (75.00%)<br />
Database      : sakila<br />
Users         :<br />
	msandbox@localhost  : 100.00% (3) of query, 100.00% (4) of all users<br />
</code><code><br />
Query abstract:<br />
SET timestamp=N; SELECT customer_id,COUNT(*) FROM rental WHERE return_date&gt;'S' GROUP BY customer_id;<br />
</code><code><br />
Query sample:<br />
SET timestamp=1278504711;<br />
SELECT customer_id,COUNT(*) FROM rental WHERE return_date&gt;'2005-01-01' GROUP BY customer_id;<br />
</code><code><br />
________________________________________________ 002 ___<br />
Count         : 1  (25.00%)<br />
Time          : 718.344 ms total, 718.344 ms avg, 718.344 ms to 718.344 ms max  (79.40%)<br />
Lock Time (s) : 259 s total, 259 s avg, 259 s to 259 s max  (52.11%)<br />
Rows sent     : 0 avg, 0 to 0 max  (0.00%)<br />
Rows examined : 16.04k avg, 16.04k to 16.04k max  (25.00%)<br />
Database      :<br />
Users         :<br />
	msandbox@localhost  : 100.00% (1) of query, 100.00% (4) of all users<br />
</code><code><br />
Query abstract:<br />
SET timestamp=N; INSERT INTO rental2 SELECT * FROM rental;<br />
</code><code><br />
Query sample:<br />
SET timestamp=1278504762;<br />
INSERT INTO rental2 SELECT * FROM rental;<br />
</code><br />
L&#8217;utilisation des filtres va nous permettre de répondre aux 2 dernières questions. Il faut savoir que <code>mysqlsla</code> dispose de deux types de filtres : le premier type permet de filtrer sur les méta-données des entrées du journal alors que le second permettre de sélectionner ou d&#8217;exclure un ou plusieurs types de requêtes. Ces deux types de filtres peuvent bien sûr être combinés afin de répondre à des questions complexes.</p>
<p>Les filtres sur les méta-données s&#8217;écrivent avec l&#8217;option <code>--meta-filter</code> (ou <code>-mf</code> en abrégé), comme par exemple <code>-mf "db=sakila"</code> pour ne conserver que les requêtes sur la base sakila ou <code>-mf "db=sakila,c_sum&gt;5"</code> pour ne conserver que les requêtes sur la base sakila qui apparaissent au moins 6 fois.</p>
<p>Il est facile avec cette option de répondre à la 3è question :<br />
Quelles sont les requêtes qui prennent plus de x secondes ?<br />
En positionnant x à 0.1s, on obtient le résultat suivant :<br />
<code><br />
$ mysqlsla -lt slow -mf "t&gt;0.1" msandbox-slow.log<br />
Report for slow logs: msandbox-slow.log<br />
1 queries total, 1 unique<br />
Sorted by 't_sum'<br />
Grand Totals: Time 1 s, Lock 0 s, Rows sent 0, Rows Examined 16.04k<br />
</code><code><br />
________________________________________________ 001 ___<br />
Count         : 1  (100.00%)<br />
Time          : 718.344 ms total, 718.344 ms avg, 718.344 ms to 718.344 ms max  (100.00%)<br />
Lock Time (s) : 259 s total, 259 s avg, 259 s to 259 s max  (100.00%)<br />
Rows sent     : 0 avg, 0 to 0 max  (0.00%)<br />
Rows examined : 16.04k avg, 16.04k to 16.04k max  (100.00%)<br />
Database      :<br />
Users         :<br />
	msandbox@localhost  : 100.00% (1) of query, 100.00% (1) of all users<br />
</code><code><br />
Query abstract:<br />
SET timestamp=N; INSERT INTO rental2 SELECT * FROM rental;<br />
</code><code><br />
Query sample:<br />
SET timestamp=1278504762;<br />
INSERT INTO rental2 SELECT * FROM rental;<br />
</code></p>
<p>Enfin nous allons nous servir des filtres sur les requêtes (option <code>--statement-filter</code> ou <code>-sf</code>) pour répondre à la 4è question :<br />
Comment ne conserver que les requêtes <code>SELECT</code> ?<br />
<code><br />
$ mysqlsla -lt slow -sf "+SELECT" msandbox-slow.log<br />
Report for slow logs: msandbox-slow.log<br />
3 queries total, 1 unique<br />
Sorted by 't_sum'<br />
Grand Totals: Time 0 s, Lock 0 s, Rows sent 1.80k, Rows Examined 48.13k<br />
</code><code><br />
________________________________________________ 001 ___<br />
Count         : 3  (100.00%)<br />
Time          : 186.368 ms total, 62.123 ms avg, 52.575 ms to 74.232 ms max  (100.00%)<br />
Lock Time (s) : 238 s total, 79 s avg, 60 s to 117 s max  (100.00%)<br />
Rows sent     : 599 avg, 599 to 599 max  (100.00%)<br />
Rows examined : 16.04k avg, 16.04k to 16.04k max  (100.00%)<br />
Database      : sakila<br />
Users         :<br />
	msandbox@localhost  : 100.00% (3) of query, 100.00% (3) of all users<br />
</code><code><br />
Query abstract:<br />
SELECT customer_id,COUNT(*) FROM rental WHERE return_date&gt;'S' GROUP BY customer_id;<br />
</code><code><br />
Query sample:<br />
SELECT customer_id,COUNT(*) FROM rental WHERE return_date&gt;'2005-01-01' GROUP BY customer_id;<br />
</code><br />
Et voilà, notre (courte) exploration de <code>mysqlsla</code> est terminée ! Ces quelques exemples ont montré que <code>mysqlsla</code> est extrêmement flexible comparé à <code>mysqldumpslow</code>, ce qui en fait un très bon choix en tant outil d&#8217;analyse de journaux MySQL. Quels sont les inconvénients de <code>mysqlsla</code> ? En fait, je n&#8217;en vois qu&#8217;un seul : Daniel, le développeur de <code>mysqlsla</code>, a annoncé au printemps que l&#8217;outil ne serait plus maintenu. La raison est simple : Daniel fait maitenant partie de l&#8217;équipe de développement de Maatkit, qui propose un outil remplaçant <code>mysqlsla</code>. Mais assez dit, ce sera l&#8217;objet du prochain article de cette série.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dbnewz.com/2010/07/28/outils-d%e2%80%99analyse-de-requetes-lentes-%e2%80%93-mysqlsla/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

