<?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; 5.1</title>
	<atom:link href="http://www.dbnewz.com/category/mysql/51/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>Wed, 28 Jul 2010 14:01:15 +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>Outils d&#8217;analyse de requêtes lentes &#8211; mysqldumpslow</title>
		<link>http://www.dbnewz.com/2010/07/08/outils-danalyse-de-requetes-lentes-mysqldumpslow/</link>
		<comments>http://www.dbnewz.com/2010/07/08/outils-danalyse-de-requetes-lentes-mysqldumpslow/#comments</comments>
		<pubDate>Thu, 08 Jul 2010 15:15:22 +0000</pubDate>
		<dc:creator>stephane</dc:creator>
				<category><![CDATA[5.1]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[log]]></category>
		<category><![CDATA[outils]]></category>

		<guid isPermaLink="false">http://www.dbnewz.com/?p=601</guid>
		<description><![CDATA[Nous avons vu dans un précédent article comment tracer les requêtes lentes avec MySQL et quelles sont les possibilités selon la version du serveur. Si vous avez activé le journal des requêtes lentes, vous avez sans doute recueilli un certain nombre de requêtes qu&#8217;il faut maintenant analyser afin de pouvoir les optimiser ou afin de [...]]]></description>
			<content:encoded><![CDATA[<p>Nous avons vu dans un précédent article comment tracer les requêtes lentes avec MySQL et quelles sont les possibilités selon la version du serveur. Si vous avez activé le journal des requêtes lentes, vous avez sans doute recueilli un certain nombre de requêtes qu&#8217;il faut maintenant analyser afin de pouvoir les optimiser ou afin de revoir le paramétrage du serveur. Cet article est le premier d&#8217;une série de trois, qui va vous montrer quelques outils qui vont vous aider dans cette analyse.<span id="more-601"></span></p>
<p>Avant toute chose, montons une petite configuration qui va nous être utile pour illustrer le fonctionnement et les limitations de chaque outil.</p>
<p>Prenons un serveur MySQL en version 5.1 avec la table exemple sakila et choisissons une valeur de <code>long_query_time</code> de 0.05s. Pourquoi une valeur aussi faible ? Tout simplement parce qu&#8217;avec une telle valeur, il n&#8217;est vraiment pas difficile des requêtes qui seront considérées comme lentes, ce qui nous permettra d&#8217;obtenir facilement un journal de requêtes lentes.</p>
<p>Effectuons les requêtes suivantes :</p>
<p>(1) <code>mysql&gt; SELECT customer_id,COUNT(*) FROM rental WHERE return_date &gt; '2005-01-01' GROUP BY customer_id;</code><br />
(2) <code>mysql&gt; SELECT customer_id,COUNT(*) FROM rental WHERE return_date &gt; '2005-01-02' GROUP BY customer_id;</code><br />
(3) <code>mysql&gt; SELECT customer_id,COUNT(*) FROM rental WHERE return_date &gt; '2005-01-03' GROUP BY customer_id;</code><br />
(4) <code>mysql&gt; CREATE TABLE rental2 LIKE rental;</code><br />
(5) <code>mysql&gt; INSERT INTO rental2 SELECT * FROM rental;</code></p>
<p>Toutes les requêtes, sauf (4), sont lentes (si ce n&#8217;est pas le cas pour vous, il vous suffit de baisser la valeur de <code>long_query_time</code>).</p>
<p>Le journal des requêtes lentes a maintenant l&#8217;allure suivante :<br />
<code><br />
# Time: 100707 14:11:51<br />
# User@Host: msandbox[msandbox] @ localhost []<br />
# Query_time: 0.074232  Lock_time: 0.000061 Rows_sent: 599  Rows_examined: 16044<br />
use sakila;<br />
SET timestamp=1278504711;<br />
SELECT customer_id,COUNT(*) FROM rental WHERE return_date&gt;'2005-01-01' GROUP BY customer_id;<br />
# Time: 100707 14:12:10<br />
# User@Host: msandbox[msandbox] @ localhost []<br />
# Query_time: 0.052575  Lock_time: 0.000117 Rows_sent: 599  Rows_examined: 16044<br />
SET timestamp=1278504730;<br />
SELECT customer_id,COUNT(*) FROM rental WHERE return_date&gt;'2005-01-02' GROUP BY customer_id;<br />
# Time: 100707 14:12:15<br />
# User@Host: msandbox[msandbox] @ localhost []<br />
# Query_time: 0.059561  Lock_time: 0.000060 Rows_sent: 599  Rows_examined: 16044<br />
SET timestamp=1278504735;<br />
SELECT customer_id,COUNT(*) FROM rental WHERE return_date&gt;'2005-01-03' GROUP BY customer_id;<br />
# Time: 100707 14:12:42<br />
# User@Host: msandbox[msandbox] @ localhost []<br />
# Query_time: 0.718344  Lock_time: 0.000259 Rows_sent: 0  Rows_examined: 16044<br />
SET timestamp=1278504762;<br />
INSERT INTO rental2 SELECT * FROM rental;<br />
</code></p>
<p>Plutôt que de montrer toutes les possibilités des outils que nous allons regarder, je vous propose de nous concentrer sur quelques questions clés que l&#8217;on se pose souvent et de voir si les outils examinés permettent de répondre à ces questions :</p>
<p>- Comment ne garder que les groupes de requêtes qui ont entraîné le plus long temps de réponse ?<br />
- Comment ne garder que les groupes de requêtes qui ont le plus d&#8217;occurences ?<br />
- Comment ne garder que les requêtes qui prennent plus de x secondes ?<br />
- Comment ne garder que les SELECT ?</p>
<p>La deux premières questions demandent quelques éclaircissements : qu&#8217;est-ce qu&#8217;un groupe de requêtes et pourquoi une même requête, pas trop lente mais s&#8217;exécutant plusieurs fois, serait-elle plus intéressante à analyser qu&#8217;une requête très lente ?</p>
<p>Premièrement, il suffit de regarder les 4 premières requêtes du listing ci-dessus pour s&#8217;apercevoir que les requêtes sont identiques à une constante près. Un appel à EXPLAIN donnera le même résultat pour toutes les requêtes, et l&#8217;amélioration de l&#8217;une d&#8217;elles les améliorera toutes.<br />
Et deuxièmement, il faut garder à l&#8217;esprit qu&#8217;une requête exécutée 1000 fois par seconde en 1 ms chargera plus le serveur qu&#8217;une requête exécuté 1 fois par seconde en 0.5s. La recherche des requêtes qui provoquent le plus de charge est un travail aussi important que la recherche des requêtes les plus lentes.</p>
<p>Nous allons regarder dans cet article <code>mysqldumpslow</code>, qui est un script Perl que vous trouverez dans toute distribution MySQL. Ce script est assez rustique (il a été écrit en 2000 !) mais comme c&#8217;est le seul outil d&#8217;analyse fourni en standard, nous allons y jeter un oeil.</p>
<p>L&#8217;utilisation est très simple : il suffit de passer le chemin du journal au script, avec éventuellement des options que nous allons examiner un peu plus loin.</p>
<p>Voici ce que nous obtenons dans notre cas :<br />
<code><br />
$ mysqldumpslow msandbox-slow.log </code><br />
<code><br />
Reading mysql slow query log from msandbox-slow.log<br />
Count: 1  Time=0.72s (0s)  Lock=0.00s (0s)  Rows=0.0 (0), msandbox[msandbox]@localhost<br />
  insert into rental2 select * from rental<br />
</code><code><br />
Count: 3  Time=0.06s (0s)  Lock=0.00s (0s)  Rows=599.0 (1797), msandbox[msandbox]@localhost<br />
  select customer_id,count(*) from rental where return_date&gt;'S' group by customer_id<br />
</code></p>
<p>Quelques commentaires sur le rapport obtenu :</p>
<p>- Les requêtes sont rassemblées au sein de groupes comme nous l&#8217;avons expliqué. Les constantes qui changent entre chaque requête d&#8217;un même groupe sont remplacées par un S, ce qui indique que les valeurs réelles sont des chaines de caractères ou par un N, ce qui indique que les valeurs réelles sont des nombres. Cette présentation concise est bien pratique car très souvent vous retrouverez le même type de requêtes exécuté des dizaines ou des centaines de fois, mais elle a pour inconvénient de ne pas fournir de requête directement exploitable : si vous voulez utiliser EXPLAIN, il vous faudra d&#8217;abord remplacer le S par une &#8216;vraie&#8217; valeur, ce qui peut s&#8217;avérer désagréable à l&#8217;usage. Il est possible de demander à mysqldumpslow de ne pas rendre abstraites les constantes, mais dans ce cas, chaque requête d&#8217;un groupe est considérée comme unique si bien que le regroupement est perdu.</p>
<p>- Les champs Time, Lock et Rows reprennent les informations du journal, en mentionnant la valeur moyenne et la valeur totale (entre parenthèses) de chaque champ pour le groupe. Notez que pour les valeurs totales de Time et Lock, l&#8217;unité est la seconde, ce qui était bien adapté en 2000 quand la résolution du journal était la seconde, mais ne l&#8217;est plus du tout dans notre cas. Ces valeurs ne sont donc pas toujours exploitables.</p>
<p>Pouvons-nous répondre aux questions que nous nous étions posées avec mysqldumpslow ?</p>
<p>Pour les 2 premières (groupe de requêtes ayant le plus d&#8217;occurences et ayant entrainé le plus long temps de réponse), la réponse est oui. mysqldumpslow dispose en effet d&#8217;une option pour trier les entrées du rapport selon différents critères. Ainsi :<br />
<code>mysqldumpslow -s c msandbox-slow.log</code><br />
trie les groupes de requêtes par nombre d&#8217;occurences. Et </p>
<p><code>mysqldumpslow -s t msandbox-slow.log</code><br />
trie les groupes de requêtes par temps d&#8217;exécution. Notez que le tri se fait sur le temps total, ie le chiffre entre parenthèses. Si vous préférez que le tri se fasse sur le temps moyen il faut utiliser l&#8217;option -s at.</p>
<p>Pour la troisième question, portant sur les requêtes prenant plus de x secondes, nous ne pouvons pas directement obtenir le résultat. Mais nous pouvons nous en approcher en triant par temps de réponse et en utilisant l&#8217;option -t qui n&#8217;affiche que les N premiers résultats :<br />
<code>mysqldumpslow -s t -t 1 msandbox-slow.log</code><br />
 donne le groupe de requêtes ayant le temps total d&#8217;exécution le plus long. Il suffit alors de trouver, par tâtonnements successifs par exemple, la bonne valeur de l&#8217;option -t qui va nous permettre d&#8217;obtenir le résultat voulu.</p>
<p>Quant à la dernière question, à savoir filtrer l&#8217;affichage sur les SELECT, il existe bien une option -g qui permet de fournir un motif de filtrage, mais il ne m&#8217;a pas semblé possible dans notre cas de trouver un motif qui filtre le INSERT &#8230; SELECT sans filtrer les SELECT.</p>
<p>Résumons donc : <code>mysqldumpslow</code> est un outil très simple, toujours disponible, qui permet de produire un rapport concis mais peu précis en regroupant les requêtes similaires. Les possibilités de filtrage sont réduites au minimum, ce qui n&#8217;en fait pas un outil très pratique quand le journal contient beaucoup d&#8217;entrées. On l&#8217;utilisera donc surtout quand aucun autre outil n&#8217;est disponible, ce qui devrait être finalement assez rare, puisque rien ne vous empêche de rapatrier le journal de requêtes lentes sur votre PC pour l&#8217;analyser avec un autre outil.</p>
<p>Rendez-vous très bientôt pour la seconde partie de notre découverte des outils d&#8217;analyse de journaux de requêtes lentes avec </code>mysqlsla</code>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dbnewz.com/2010/07/08/outils-danalyse-de-requetes-lentes-mysqldumpslow/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Encore un nouveau livre sur MySQL !</title>
		<link>http://www.dbnewz.com/2010/06/18/encore-un-nouveau-livre-sur-mysql/</link>
		<comments>http://www.dbnewz.com/2010/06/18/encore-un-nouveau-livre-sur-mysql/#comments</comments>
		<pubDate>Fri, 18 Jun 2010 14:18:57 +0000</pubDate>
		<dc:creator>stephane</dc:creator>
				<category><![CDATA[5.0]]></category>
		<category><![CDATA[5.1]]></category>
		<category><![CDATA[DBNewz]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[livres]]></category>

		<guid isPermaLink="false">http://www.dbnewz.com/?p=597</guid>
		<description><![CDATA[Après l&#8217;excellent &#171;&#160;MySQL5, Audit et optimisation&#160;&#187; sorti fin mars, voici un nouveau livre pour vous occuper sur la plage cet été : MySQL5, Administration et optimisation.
Pour vous mettre l&#8217;eau à la bouche, la TDM_MySQL5_Admin_Optim et un Extrait_MySQL5_Admin_Optim consacré aux verrous et transactions sont disponibles
Le livre est bien sûr disponible dans toutes les bonnes librairies informatiques [...]]]></description>
			<content:encoded><![CDATA[<p>Après l&#8217;excellent &laquo;&nbsp;MySQL5, Audit et optimisation&nbsp;&raquo; sorti fin mars, voici un nouveau livre pour vous occuper sur la plage cet été : <a href="http://www.editions-eni.fr/Livres/MySQL-5-administration-et-optimisation/.5_3a6222cf-b921-41f5-886c-c989f77ba994_cb487b7e-d258-456f-9051-6cc0e5e2b22f_817f0d89-4a9c-49f7-ad91-63e24f3c9941_1_0_d9bd8b5e-f324-473f-b1fc-b41b421c950f.html">MySQL5, Administration et optimisation</a>.<br />
Pour vous mettre l&#8217;eau à la bouche, la <a href='http://www.dbnewz.com/wp-content/uploads/2010/06/TDM_MySQL5_Admin_Optim.pdf'>TDM_MySQL5_Admin_Optim</a> et un <a href='http://www.dbnewz.com/wp-content/uploads/2010/06/Extrait_MySQL5_Admin_Optim.pdf'>Extrait_MySQL5_Admin_Optim</a> consacré aux verrous et transactions sont disponibles</p>
<p>Le livre est bien sûr disponible dans toutes les bonnes librairies informatiques et autres FNAC, Amazon &#8230;</p>
<p>Bonne lecture !</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dbnewz.com/2010/06/18/encore-un-nouveau-livre-sur-mysql/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Securich &#8211; Darren Cassar</title>
		<link>http://www.dbnewz.com/2009/08/23/securich-darren-cassar/</link>
		<comments>http://www.dbnewz.com/2009/08/23/securich-darren-cassar/#comments</comments>
		<pubDate>Sun, 23 Aug 2009 12:36:12 +0000</pubDate>
		<dc:creator>stephane</dc:creator>
				<category><![CDATA[5.1]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[OpenSQLCamp]]></category>
		<category><![CDATA[outils]]></category>

		<guid isPermaLink="false">http://www.dbnewz.com/?p=349</guid>
		<description><![CDATA[Darren s&#8217;est occupé récemment d&#8217;une migration de Sybase vers MySQL. Et il s&#8217;est aperçu à cette occasion que la gestion des utilisateurs sous MySQL n&#8217;est pas sans défaut. Par exemple, il n&#8217;est pas possible de créer des rôles, il n&#8217;est pas possible de donner à un utilisateur des droits sur toutes les tables sauf une, [...]]]></description>
			<content:encoded><![CDATA[<p>Darren s&#8217;est occupé récemment d&#8217;une migration de Sybase vers MySQL. Et il s&#8217;est aperçu à cette occasion que la gestion des utilisateurs sous MySQL n&#8217;est pas sans défaut. Par exemple, il n&#8217;est pas possible de créer des rôles, il n&#8217;est pas possible de donner à un utilisateur des droits sur toutes les tables sauf une, il n&#8217;est pas possible de connaître le degré de complexité d&#8217;un mot de passe&#8230;</p>
<p>Pour essayer de pallier à tous ces défauts, Darren a créé un outil : <a href="http://www.securich.com">Securich,</a> installable sur tout serveur MySQL 5.1. Cet outil permet, à l&#8217;aide d&#8217;appels à des procédures stockées, de manipuler les utilisateurs et leurs droits. Attention tout de même, le développement de Securich a commencé il y a peu de temps et le code est encore expérimental. Il reste pas mal de fonctionnalités que Darren voudrait implémenter et quelques bugs gênants : par exemple, si vous installez Securich sur un serveur contenant des utilisateurs, Securich va effacer tous les utilisateurs sans vous en avertir&#8230;</p>
<p>L&#8217;initiative est en tout cas intéressante car il reste effectivement pas mal de travail pour que MySQL présente autant de fonctionnalités sur les utilisateurs et les droits que d&#8217;autres produits.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dbnewz.com/2009/08/23/securich-darren-cassar/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>OpenSQLCamp : Sharding for the masses</title>
		<link>http://www.dbnewz.com/2009/08/22/opensqlcamp-sharding-for-the-masses/</link>
		<comments>http://www.dbnewz.com/2009/08/22/opensqlcamp-sharding-for-the-masses/#comments</comments>
		<pubDate>Sat, 22 Aug 2009 09:02:43 +0000</pubDate>
		<dc:creator>stephane</dc:creator>
				<category><![CDATA[5.1]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[OpenSQLCamp]]></category>

		<guid isPermaLink="false">http://www.dbnewz.com/?p=328</guid>
		<description><![CDATA[Ce week-end a lieu à côté de Bonn l&#8217;OpenSQLCamp, en parallèle de la FrOSCon. Giuseppe Maxia nous parle aujourd&#8217;hui d&#8217;une technique utilisée dans certaines applications à fort trafic : le sharding. Mais à quoi sert le sharding ?
Quand on commence une application, un seul serveur SQL suffit généralement à absorber la charge. Si le trafic [...]]]></description>
			<content:encoded><![CDATA[<p>Ce week-end a lieu à côté de Bonn l&#8217;OpenSQLCamp, en parallèle de la <a href="http://www.froscon.de/en/">FrOSCon</a>. <a href="http://datacharmer.blogspot.com/">Giuseppe Maxia</a> nous parle aujourd&#8217;hui d&#8217;une technique utilisée dans certaines applications à fort trafic : le sharding. Mais à quoi sert le sharding ?</p>
<p>Quand on commence une application, un seul serveur SQL suffit généralement à absorber la charge. Si le trafic augmente et que les performances du serveur commencent à s&#8217;écrouler, on peut utiliser la réplication. Mais la réplication ne permet que d&#8217;augmenter le nombre de lectures possibles (read scaling) et pas le nombre d&#8217;écritures (write scaling). Cette limitation provient du fonctionnement même de la réplication car toutes les écritures doivent arriver sur le serveur maître. On peut alors imaginer de séparer les données selon certaines règles (dépendantes de l&#8217;application) de manière à avoir plusieurs masters sur lesquels on peut utiliser la réplication. Et voilà, nous venons d&#8217;inventer le sharding.</p>
<p>Giuseppe nous explique que le sharding est simple à mettre en place : il suffit de créer des règles permettant de dispatcher les données entre les différents masters. Malheureusement, le sharding est fragile : si, par exemple, vous changez les règles de dispatch, vous risquez de casser vos shards ! Pour simplifier le sharding, il existe un moteur de stockage récent, installable en tant que plugin pour MySQL 5.1 : Spider. Spider est basé sur le partitionnement et permet de créer des tables sur un serveur dont les données seront stockées sur des hosts distants. En gros, on utilise une pincée de partitionnement et une pincée de Federated&#8230;</p>
<p>Les démonstrations faites par Giuseppe montrent quand même que si Spider peut nous simplifier la tâche du sharding, la mise en place de tables avec Spider n&#8217;est pas si simple et demande un peu de doigté et de réflexion&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dbnewz.com/2009/08/22/opensqlcamp-sharding-for-the-masses/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>le trigger au secours des function-based index (FBI)</title>
		<link>http://www.dbnewz.com/2009/04/01/le-trigger-au-secours-des-function-based-index-fbi/</link>
		<comments>http://www.dbnewz.com/2009/04/01/le-trigger-au-secours-des-function-based-index-fbi/#comments</comments>
		<pubDate>Wed, 01 Apr 2009 06:36:55 +0000</pubDate>
		<dc:creator>arnaud</dc:creator>
				<category><![CDATA[5.0]]></category>
		<category><![CDATA[5.1]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[index]]></category>
		<category><![CDATA[pratique]]></category>

		<guid isPermaLink="false">http://www.dbnewz.com/?p=228</guid>
		<description><![CDATA[Constat : les FBI (function-based index) ne sont pas disponibles en MySQL.
Comment faire dans ce cas pour obtenir par exemple l&#8217;équivalent de l&#8217;index suivant ?
CREATE INDEX idx_str_len ON my_table (length(str));
Une des solutions les plus simples consiste à utiliser un trigger qui assurera la mise à jour d&#8217;une colonne supplémentaire que l&#8217;on créera dans la table [...]]]></description>
			<content:encoded><![CDATA[<p>Constat : les FBI (function-based index) ne sont pas disponibles en MySQL.</p>
<p>Comment faire dans ce cas pour obtenir par exemple l&#8217;équivalent de l&#8217;index suivant ?</p>
<p><code>CREATE INDEX idx_str_len ON my_table (<strong>length</strong>(str));</code></p>
<p>Une des solutions les plus simples consiste à utiliser un <a href="http://dev.mysql.com/doc/refman/5.1/en/create-trigger.html" target="_blank">trigger</a> qui assurera la mise à jour d&#8217;une colonne supplémentaire que l&#8217;on créera dans la table cible.</p>
<p>Partons de la table suivante :</p>
<p><code>CREATE TABLE `t` (<br />
`id` mediumint(8) unsigned NOT NULL auto_increment,<br />
`date` timestamp NOT NULL,<br />
`str` varchar(100) NOT NULL default '0',<br />
PRIMARY KEY  (`id`)<br />
) ENGINE=MyISAM;</code></p>
<p>Le but est de pouvoir obtenir une réponse rapide à la requête suivante :</p>
<p>select sql_no_cache count(*) from t where length(str) between 20 and 70;</p>
<p>Afin de simuler un jeu d&#8217;essai similaire à ma table de production, j&#8217;alimente ma table de test grâce à <a href="http://www.dbnewz.com/2008/08/19/generer-un-jeu-de-donnees-shell-mysqlslap-et-procedure-stockee/" target="_blank">une procédure stockée</a> déjà évoquée sur dbnewz.</p>
<p>Je la modifie ici afin d&#8217;obtenir une longueur aléatoire pour ma chaîne de caractère &laquo;&nbsp;str&nbsp;&raquo; :</p>
<p><code>delimiter //<br />
CREATE PROCEDURE fill_table(nb_rows INT)<br />
BEGIN<br />
DECLARE i INT DEFAULT 0;<br />
REPEAT<br />
SET i = i + 1;<br />
INSERT INTO t (str) VALUES(repeat('a', round(rand()*100)));<br />
UNTIL i &gt;= nb_rows<br />
END REPEAT;<br />
END;<br />
//<br />
delimiter ;</code></p>
<p>Afin de valider deux scénarios pour ce billet, j&#8217;ai utilisé deux machines, une machine perso et un serveur pro. Deux configurations différentes : la perso n&#8217;est absolument pas &laquo;&nbsp;tunée&nbsp;&raquo;, en configuration strictement d&#8217;origine (5.1.32), la seconde a des processeurs et de la RAM à revendre.<br />
J&#8217;afficherai donc les temps d&#8217;exécution des deux machines à titre de comparaison.</p>
<p><span id="more-228"></span>Pour renseigner ma table de test à hauteur de 2 millions d&#8217;enregistrements, j&#8217;appelle ma procédure stockée :</p>
<p>Perso :<br />
mysql&gt; call fill_table(2000000);<br />
Query OK, 1 row affected (1 min 52.97 sec)</p>
<p>Pro :<br />
mysql&gt; call fill_table(2000000);<br />
Query OK, 1 row affected (42.23 sec)</p>
<p>Je souhaite tester ma requête sous InnoDB, je modifie le moteur de la table :</p>
<p>mysql&gt; alter table t engine=&#8217;innodb&#8217;;<br />
Query OK, 2000000 rows affected (43.57 sec)<br />
Records: 2000000  Duplicates: 0  Warnings: 0</p>
<p>mysql&gt; alter table t engine=&#8217;innodb&#8217;;<br />
Query OK, 2000000 rows affected (13.10 sec)<br />
Records: 2000000  Duplicates: 0  Warnings: 0</p>
<p>J&#8217;ai testé pour vous : il est <strong>bien plus rapide</strong> d&#8217;insérer en MyISAM et de faire l&#8217;ALTER en InnoDB que d&#8217;insérer directement en InnoDB :</p>
<p>Test de chargement avec <strong>table en InnoDB</strong> <strong>d&#8217;origine</strong> sur la machine <strong>perso</strong> :<br />
mysql&gt; call fill_table(<strong>100000</strong>);<br />
Query OK, 1 row affected (1 min 14.15 sec)</p>
<p>=&gt; Déjà 30 secondes de plus (70% plus lent) pour 20x moins de données !</p>
<p>La machine pro (en 5.0.56) est bien plus rapide mais valide elle aussi la stratégie de passer par MyISAM en premier lieu :<br />
Chargement d&#8217;une <strong>table en InnoDB</strong> sur serveur <strong>pro</strong> :<br />
mysql&gt; call fill_table(2000000);<br />
Query OK, 1 row affected (1 min 11.16 sec)</p>
<p>=&gt; A comparer avec les 42 sec (chargement MyISAM natif) + 13 sec (alter InnoDB) = 55 s. Au moins 30% plus rapide.</p>
<p>On obtient une table contenant des enregistrements du type :</p>
<p>mysql&gt; select * from t limit 5;<br />
+&#8212;-+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br />
| id | date                | str<br />
+&#8212;-+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br />
|  1 | 2009-03-30 22:18:02 | aaaaaaaa<br />
|  2 | 2009-03-30 22:18:02 | aaaaaaaaaaaaaaaa<br />
|  3 | 2009-03-30 22:18:02 | aaaaaaaaaaaaaaaaaaaaaaa<br />
|  4 | 2009-03-30 22:18:02 | aaaaaaaaaaa<br />
|  5 | 2009-03-30 22:18:02 | aaa<br />
+&#8212;-+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;</p>
<p>Nous voici donc avec un jeu d&#8217;essai raisonnable, prêt à en découdre avec cette requête&#8230;</p>
<p>mysql&gt; explain select sql_no_cache count(*) from t where length(str) between 20 and 70;</p>
<p>*************************** 1. row ***************************<br />
id: 1<br />
select_type: SIMPLE<br />
table: t<br />
type: ALL<br />
possible_keys: NULL<br />
key: NULL<br />
key_len: NULL<br />
ref: NULL<br />
rows: 2015427<br />
Extra: Using where<br />
1 row in set (0.00 sec)</p>
<p>&#8230; Au temps d&#8217;exécution pitoyable. Sur la machine perso, j&#8217;obtiens en moyenne :</p>
<p>mysql&gt; select sql_no_cache count(*) from t where length(str) between 20 and 70;<br />
+&#8212;&#8212;&#8212;-+<br />
| count(*) |<br />
+&#8212;&#8212;&#8212;-+<br />
|  1019970 |<br />
+&#8212;&#8212;&#8212;-+<br />
1 row in set (1.83 sec)</p>
<p>Sur le serveur pro :</p>
<p>mysql&gt; select sql_no_cache count(*) from t where length(str) between 20 and 70;<br />
+&#8212;&#8212;&#8212;-+<br />
| count(*) |<br />
+&#8212;&#8212;&#8212;-+<br />
|  1020008 |<br />
+&#8212;&#8212;&#8212;-+<br />
1 row in set (0.82 sec)</p>
<p>L&#8217;innodb_buffer_pool_size de la machine perso est complètement sous-dimensionné pour contenir toutes les données de ma table. L&#8217;intérêt ici est de tester ce qui se passe quand les données ne sont pas montées en RAM (comportement de MyISAM qui charge les datas dans le cache OS).</p>
<p>Constatons les &laquo;&nbsp;dégats&nbsp;&raquo; sur la machine perso :</p>
<p>mysql&gt; show variables like &#8216;innodb_buffer_pool_size&#8217;;<br />
+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-+&#8212;&#8212;&#8212;+<br />
| Variable_name           | Value   |<br />
+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-+&#8212;&#8212;&#8212;+<br />
| innodb_buffer_pool_size | 8 388 608<br />
+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-+&#8212;&#8212;&#8212;+</p>
<p>&#8230; le jour et la nuit avec :</p>
<p>mysql&gt; show variables like &#8216;innodb_buffer_pool_size&#8217;;<br />
+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;+<br />
| Variable_name           | Value      |<br />
+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;+<br />
| innodb_buffer_pool_size | 3 984 588 800<br />
+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;+</p>
<p>Voyons si un index peut changer la donne ? Plaçons-le sur la colonne &laquo;&nbsp;str&nbsp;&raquo; :</p>
<p>mysql&gt; create index idx_str on t (str);<br />
Query OK, 2000000 rows affected (1 min 1.95 sec)<br />
Records: 2000000  Duplicates: 0  Warnings: 0</p>
<p>mysql&gt; create index idx_str on t (str);<br />
Query OK, 2000000 rows affected (24.95 sec)<br />
Records: 2000000  Duplicates: 0  Warnings: 0</p>
<p>Le nouveau plan d&#8217;exécution est le suivant :</p>
<p>mysql&gt; explain select sql_no_cache count(*) from t where length(str) between 20 and 70;<br />
*************************** 1. row ***************************<br />
id: 1<br />
select_type: SIMPLE<br />
table: t<br />
type: index<br />
possible_keys: NULL<br />
key: idx_str<br />
key_len: 102<br />
ref: NULL<br />
rows: 1957767<br />
Extra: Using where; Using index<br />
1 row in set (0.00 sec)</p>
<p>Nous sommes dans le cas d&#8217;un <a href="http://www.dbnewz.com/2008/11/20/les-covering-index-de-la-theorie-a-la-pratique-avec-myisam-et-innodb/" target="_blank">covering index</a> mais celui-ci n&#8217;est pas hélas pas d&#8217;une grande utilité sur la machine perso :</p>
<p>mysql&gt; select sql_no_cache count(*) from t where length(str) between 20 and 70;<br />
+&#8212;&#8212;&#8212;-+<br />
| count(*) |<br />
+&#8212;&#8212;&#8212;-+<br />
|  1019970 |<br />
+&#8212;&#8212;&#8212;-+<br />
1 row in set (1.98 sec)</p>
<p>On frôle les 2 secondes.</p>
<p>Sur la machine de prod c&#8217;est en revanche légèrement plus rapide :</p>
<p>mysql&gt; select sql_no_cache count(*) from t where length(str) between 20 and 70;<br />
+&#8212;&#8212;&#8212;-+<br />
| count(*) |<br />
+&#8212;&#8212;&#8212;-+<br />
|  1020008 |<br />
+&#8212;&#8212;&#8212;-+<br />
1 row in set (0.73 sec)</p>
<p>Stratégie : rajouter une colonne &laquo;&nbsp;str_nb&nbsp;&raquo; à ma table t et mettre à jour cette colonne selon la longueur de &laquo;&nbsp;str&nbsp;&raquo;.</p>
<p><strong>Machine Perso :</strong><br />
mysql&gt; ALTER table t ADD str_nb tinyint unsigned, ADD index idx_str_nb(str_nb);<br />
Query OK, 2000000 rows affected (1 min 9.22 sec)<br />
Records: 2000000  Duplicates: 0  Warnings: 0</p>
<p>mysql&gt; UPDATE t SET str_nb = length(str);<br />
Query OK, 2000000 rows affected (2 min 3.97 sec)<br />
Rows matched: 2000000  Changed: 2000000  Warnings: 0</p>
<p><strong>Serveur Pro :</strong><br />
mysql&gt; ALTER table t ADD str_nb tinyint unsigned, ADD index idx_str_nb(str_nb);<br />
Query OK, 2000000 rows affected (30.91 sec)<br />
Records: 2000000  Duplicates: 0  Warnings: 0</p>
<p>mysql&gt; UPDATE t SET str_nb = length(str);</p>
<p>Query OK, 2000000 rows affected (1 min 22.54 sec)<br />
Rows matched: 2000000  Changed: 2000000  Warnings: 0</p>
<p>mysql&gt; explain select sql_no_cache count(*) from t where str_nb between 20 and 70\G</p>
<p>*************************** 1. row ***************************<br />
id: 1<br />
select_type: SIMPLE<br />
table: t<br />
type: range<br />
possible_keys: idx_str_nb<br />
key: idx_str_nb<br />
key_len: 2<br />
ref: NULL<br />
rows: 914077<br />
Extra: Using where; Using index<br />
1 row in set (0.00 sec)</p>
<p>Notre requête modifiée tire parti du nouvel index :</p>
<p>mysql&gt; select sql_no_cache count(*) from t where str_nb between 20 and 70;<br />
+&#8212;&#8212;&#8212;-+<br />
| count(*) |<br />
+&#8212;&#8212;&#8212;-+<br />
|  1019970 |<br />
+&#8212;&#8212;&#8212;-+<br />
1 row in set (0.99 sec)</p>
<p>Sur la machine de prod :</p>
<p>mysql&gt; select sql_no_cache count(*) from t where str_nb between 20 and 70;<br />
+&#8212;&#8212;&#8212;-+<br />
| count(*) |<br />
+&#8212;&#8212;&#8212;-+<br />
|  1020008 |<br />
+&#8212;&#8212;&#8212;-+<br />
1 row in set (0.37 sec)</p>
<p>Résultat de ces tests : tirer parti d&#8217;une colonne &laquo;&nbsp;précalculée&nbsp;&raquo; permet de <strong>diviser par deux</strong> le temps d&#8217;exécution sur les deux machines.</p>
<p>Comment maintenir à jour cette colonne ?</p>
<p>C&#8217;est là que les <strong>triggers</strong> rentrent en jeu, on en crée deux :</p>
<p>Le premier concerne les <strong>INSERT</strong>&#8230;</p>
<p><code>DELIMITER |<br />
CREATE TRIGGER maj_str_nb_ins BEFORE INSERT ON t<br />
FOR EACH ROW BEGIN<br />
SET NEW.str_nb = length(NEW.str);<br />
END;<br />
|<br />
DELIMITER ;</code></p>
<p>&#8230; Et le second les <strong>UPDATE</strong> :</p>
<p><code>DELIMITER |<br />
CREATE TRIGGER maj_str_nb_upd BEFORE UPDATE ON t<br />
FOR EACH ROW BEGIN<br />
SET NEW.str_nb = length(NEW.str);<br />
END;<br />
|<br />
DELIMITER ;</code></p>
<p>Le mot clé &laquo;&nbsp;NEW&nbsp;&raquo; permet de faire référence à la nouvelle donnée qui est insérée. Dans le cadre d&#8217;un UPDATE il existe également le mot clé &laquo;&nbsp;OLD&nbsp;&raquo; qui permet de manipuler l&#8217;ancienne donnée, celle qui va être mise à jour.</p>
<p>Une fois insérés, ces triggers s&#8217;occuperont pour nous de mettre à jour automatiquement la colonne &laquo;&nbsp;str_nb&nbsp;&raquo;. Quelques exemples :</p>
<p>mysql&gt; insert into t (str) values (&#8216;dbnewz&#8217;);<br />
Query OK, 1 row affected (0.45 sec)</p>
<p>mysql&gt; select * from t where id = 2000001;<br />
+&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8211;+&#8212;&#8212;&#8211;+<br />
| id      | date                | str    | <strong>str_nb</strong> |<br />
+&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8211;+&#8212;&#8212;&#8211;+<br />
| 2000001 | 2009-04-01 00:35:58 | dbnewz |      <strong>6</strong> |<br />
+&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8211;+&#8212;&#8212;&#8211;+</p>
<p>mysql&gt; update t set date = &#8216;2009-04-01 03:33:33&#8242; where id = 2000001;<br />
Query OK, 1 row affected (0.35 sec)<br />
Rows matched: 1  Changed: 1  Warnings: 0</p>
<p>mysql&gt; select * from t where id = 2000001;<br />
+&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8211;+&#8212;&#8212;&#8211;+<br />
| id      | date                | str    | str_nb |<br />
+&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8211;+&#8212;&#8212;&#8211;+<br />
| 2000001 | 2009-04-01 03:33:33 | dbnewz |      6 |<br />
+&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8211;+&#8212;&#8212;&#8211;+<br />
1 row in set (0.00 sec)</p>
<p>mysql&gt; update t set str = &#8216;MySQL&#8217; where id = 2000001;<br />
Query OK, 1 row affected (0.00 sec)<br />
Rows matched: 1  Changed: 1  Warnings: 0</p>
<p>mysql&gt; select * from t where id = 2000001;<br />
+&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;-+&#8212;&#8212;&#8211;+<br />
| id      | date                | str   | str_nb |<br />
+&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;-+&#8212;&#8212;&#8211;+<br />
| 2000001 | 2009-04-01 00:38:18 | MySQL |      5 |<br />
+&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;-+&#8212;&#8212;&#8211;+<br />
1 row in set (0.00 sec)</p>
<p>A vous de tester si ces triggers peuvent correspondre à un cas que vous cherchez à résoudre, dans ce cas précis, ils fonctionnent.</p>
<p>Merci à <a href="http://www.cybersite.com.au/blog" target="_blank">Jonathon Coombes</a> avec qui j&#8217;ai échangé autour de ce sujet.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dbnewz.com/2009/04/01/le-trigger-au-secours-des-function-based-index-fbi/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Qualité du Code de Mysql  &#8230; ou non qualité</title>
		<link>http://www.dbnewz.com/2008/12/02/qualite-du-code-de-mysql-ou-non-qualite/</link>
		<comments>http://www.dbnewz.com/2008/12/02/qualite-du-code-de-mysql-ou-non-qualite/#comments</comments>
		<pubDate>Tue, 02 Dec 2008 14:32:36 +0000</pubDate>
		<dc:creator>Laurent Tardif</dc:creator>
				<category><![CDATA[5.1]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Analyse Statique du Code]]></category>
		<category><![CDATA[Static Code Analysis]]></category>

		<guid isPermaLink="false">http://www.dbnewz.com/?p=159</guid>
		<description><![CDATA[Ma marotte actuelle sont les outils d&#8217;analyse statique de code (ASC). J&#8217;ai essayé d&#8217;expliquer a mon DBA préféré comment faire de l&#8217;intégration continue avec une DB, et comment intégrer des outils d&#8217;ASC pour valider son travail.
Nous sommes partis de la dernière version de Mysql (5.1.29-rc&#8230;) que j&#8217;ai téléchargé sous forme de tarball sans m&#8217;embêter a [...]]]></description>
			<content:encoded><![CDATA[<p>Ma marotte actuelle sont les outils d&#8217;analyse statique de code (ASC). J&#8217;ai essayé d&#8217;expliquer a mon DBA préféré comment faire de l&#8217;intégration continue avec une DB, et comment intégrer des outils d&#8217;ASC pour valider son travail.</p>
<p>Nous sommes partis de la dernière version de Mysql (5.1.29-rc&#8230;) que j&#8217;ai téléchargé sous forme de tarball sans m&#8217;embêter a lire la doc de bazaar :-p &#8230;..  J&#8217;ai intégré les quelques patches maison, les quelques modifications, et plugé ca dans <a href="https://hudson.dev.java.net/" target="_blank">Hudson </a>pour avoir un retour rapide sur la qualité de mon build.</p>
<p>(Profitant des nombreux tests fournis avec le source.)</p>
<p>Plutôt content de moi, j&#8217;ai décidé de brancher plusieurs outils d&#8217;ASC (spécialisés dans la recherche de bugs, ou de code &#8216;risqué&#8217;). Je n&#8217;ai pas était déçu du tout du voyage &#8230;&#8230;</p>
<p>Plus de 2000 problèmes potentiels !!!!!!!!!</p>
<p><span id="more-159"></span></p>
<p>Alors, triés par modules cela donne a peu prêt :</p>
<p>client                      134<br />
cmd-line-utils         80<br />
core                      1034<br />
libmysql                  211<br />
mysys                       61<br />
server-tools             34<br />
storage/archive        38<br />
storage/blackhole      1<br />
storage/csv                9<br />
storage/federated       5<br />
storage/heap              8<br />
storage/innobase   262<br />
storage/myisam     147<br />
storage/ndb           785</p>
<p>Juste pour illustrer les problèmes rencontrés regardons les (enfin, une petite partie des) problèmes liés a myisam:</p>
<ul>
<li> <strong>storage/myisam/mi_check.c</strong></li>
</ul>
<p><em>Return code not check</em> : everywhere the return code is checked, and an error is raised &#8230; my checker assume the return code is critical. So why at this line &#8230; no check ???</p>
<p>=&gt; ligne 1185 :i_pack_get_block_info(info, &amp;info-&gt;bit_buff, &amp;block_info, &amp;info-&gt;rec_buff, file, filepos)</p>
<ul>
<li> <strong>storage/myisam/mi_key.c</strong></li>
</ul>
<p><em>NPE :</em><br />
&#8211;&gt; ligne 252 : char_length= (!is_ft &amp;&amp; cs &amp;&amp; cs-&gt;mbmaxlen &gt; 1) ? length/cs-&gt;mbmaxlen : length;<br />
//so assuming cs is null<br />
&#8211;&gt; ligne 268<br />
FIX_LENGTH(cs, pos, length, char_length); //which dereference cs without any checks &#8230;.</p>
<ul>
<li> <strong>storage/myisam/mi_rkey.c</strong></li>
</ul>
<p><em>Lock error :</em><br />
&#8212;-&gt; ligne 78 : rw_rdlock(&amp;share-&gt;key_root_lock[inx]);  // take a lock<br />
if (!(nextflag &amp; (SEARCH_FIND | SEARCH_NO_FIND | SEARCH_LAST))) use_key_length=USE_WHOLE_KEY;<br />
&#8230;&#8230;<br />
if (rtree_find_first(info,inx,key_buff,use_key_length,nextflag) &lt; 0)<br />
//Allons donc dans le &laquo;&nbsp;then&nbsp;&raquo; &#8230; et bien la ressource n&#8217;est jamais libérée &#8230;.</p>
<ul>
<li> <strong>storage/myisam/ha_myisam.cc</strong></li>
</ul>
<p><em>Fuite Memoire : </em><br />
&#8212;&gt; ligne 146<br />
DBUG_ENTER(&laquo;&nbsp;table2myisam&nbsp;&raquo;);<br />
if (!(my_multi_malloc(MYF(MY_WME, &#8230;..) ///// the allocation is not stored &#8230; and never free<br />
DBUG_RETURN(HA_ERR_OUT_OF_MEM);</p>
<p>Alors, on peut bien sur, dire que certains problèmes n&#8217;arrivent que rarement, ou que c&#8217;est dans une partie du code peu appelée &#8230; Personnelement je pense qu&#8217;un BUG est un BUG &#8230;<br />
Et les lois de Murphy m&#8217;ont apprises qu&#8217;un BUG vous embêtera toujours un vendredi soir &#8230; ou un week end.</p>
<p>Alors, ce n&#8217;est pas parcequ&#8217;il y a 2000 bugs potentiels qu&#8217;on peut dire que la release est de mauvaise qualité (lisez aussi (<a href="http://monty-says.blogspot.com/2008/11/oops-we-did-it-again-mysql-51-released.html" target="_blank">cela</a>), &#8230; mais c&#8217;est un indice de plus, qu&#8217;a force de trop vouloir rajouter de nouvelles fonctionnalités, on oublie souvent l&#8217;essentiel, la stabilité et la robustesse du code.</p>
<p>(Vous pouvez trouver une autre version de l&#8217;article <a href="http://ouelcum.wordpress.com/2008/12/01/how-can-static-code-analysis-tools-may-help-you/" target="_blank">ici</a>)</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dbnewz.com/2008/12/02/qualite-du-code-de-mysql-ou-non-qualite/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>15 secondes pour installer une réplication MySQL avec MySQL Sandbox, pari tenu ?</title>
		<link>http://www.dbnewz.com/2008/10/10/15-secondes-pour-installer-une-replication-mysql-avec-mysql-sandbox-pari-tenu/</link>
		<comments>http://www.dbnewz.com/2008/10/10/15-secondes-pour-installer-une-replication-mysql-avec-mysql-sandbox-pari-tenu/#comments</comments>
		<pubDate>Fri, 10 Oct 2008 07:08:28 +0000</pubDate>
		<dc:creator>arnaud</dc:creator>
				<category><![CDATA[4.0]]></category>
		<category><![CDATA[4.1]]></category>
		<category><![CDATA[5.0]]></category>
		<category><![CDATA[5.1]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[réplication]]></category>
		<category><![CDATA[outils]]></category>

		<guid isPermaLink="false">http://www.dbnewz.com/?p=75</guid>
		<description><![CDATA[&#171;&#160;Installez-moi une configuration MySQL composée d&#8217;un master et deux slaves, vous avez 15 secondes. Top chrono&#160;&#187;&#8230;
Non, ça n&#8217;est pas la dernière énigme à la mode pour rentrer chez Google mais plutôt une question qui pourrait devenir presque banale pour un entretien d&#8217;embauche pour un DBA MySQL à l&#8217;avenir, qui sait ?
Face à un tel défi, [...]]]></description>
			<content:encoded><![CDATA[<p>&laquo;&nbsp;Installez-moi une configuration MySQL composée d&#8217;un master et deux slaves, vous avez 15 secondes. Top chrono&nbsp;&raquo;&#8230;</p>
<p>Non, ça n&#8217;est pas la dernière énigme à la mode pour rentrer chez Google mais plutôt une question qui pourrait devenir presque banale pour un entretien d&#8217;embauche pour un DBA MySQL à l&#8217;avenir, qui sait ?</p>
<p>Face à un tel défi, trois solutions :</p>
<p>- La fuite (mais faites une croix sur la &laquo;&nbsp;recommandation&nbsp;&raquo; Linkedin)<br />
- Le kernel panic<br />
- MySQL Sandbox !</p>
<p>Bien vu, MySQL Sandbox est la réponse la plus stratégique pour la poursuite de votre carrière.</p>
<p>Giuseppe Maxia (dont le blog figure dans notre blogroll, allez y jeter un oeil) est l&#8217;auteur de cet outil vraiment très pratique.  Que propose t-il ?</p>
<p>L&#8217;idée est d&#8217;automatiser l&#8217;installation de plusieurs serveurs MySQL sur une même machine. Rien que nous ne puissions faire manuellement c&#8217;est vrai, cependant la procédure habituelle consistant à ne pas mélanger les répertoires d&#8217;installation, choisir un port différent par serveur, appliquer <a href="http://www.dbnewz.com/2008/07/14/mysql_secure_installation-pratique-mais-non-parametrable/" target="_blank">mysql_secure_installation</a>&#8230; Tout cela gagnerait à être automatisé non ? De plus ces installations manuelles sont potentiellement sources d&#8217;erreurs.</p>
<p>MySQL Sandbox est compatible avec toutes les versions MySQL de la 3.23 à la 6.0. Les différentes installations effectuées sont indépendantes les unes des autres, on peut ainsi faire cohabiter sans risque de conflits plusieurs versions différentes de MySQL sur une même machine (ports, répertoires et sockets indépendants).</p>
<p>En plus d&#8217;automatiser ces processus d&#8217;installation (gain de temps), MySQL Sandbox ne s&#8217;arrête pas là et  propose également des commandes très simples pour gérer les serveurs une fois installés.</p>
<p>Concernant le gain de temps, son auteur promet (notamment en page 2 de cette <a href="http://datacharmer.org/presentations/mysqluniv_2008/sandbox_2_0.html" target="_blank">présentation</a>) l&#8217;installation d&#8217;une réplication MySQL en 15 secondes. Info ou intox ?</p>
<p><span id="more-75"></span></p>
<p><strong>Installation et pré-requis<br />
</strong></p>
<p>Pour fonctionner MySQL Sandbox n&#8217;a besoin que des binaries des versions que vous souhaitez installer.</p>
<p>L&#8217;installation est très rapide :</p>
<p>Après avoir téléchargé <a href="https://launchpad.net/mysql-sandbox" target="_blank">MySQL Sandbox</a>, il suffit de le décompresser dans le répertoire de votre choix :</p>
<p>debian:/opt# tar xzvf mysql_sandbox_2.0.11.tar.gz</p>
<p>On remarque alors que différentes commandes sont à notre disposition, plutôt explicites :</p>
<p><code>-rwxr-xr-x 1 501 staff  6154 2008-10-05 07:42 <strong>make_multiple_custom_sandbox</strong><br />
-rwxr-xr-x 1 501 staff  9062 2008-10-05 07:42 <strong>make_multiple_sandbox</strong><br />
-rwxr-xr-x 1 501 staff 12180 2008-10-05 07:42 <strong>make_replication_sandbox</strong><br />
-rwxr-xr-x 1 501 staff  5786 2008-10-05 07:42 <strong>make_sandbox</strong></code></p>
<p>make_sandbox : installe un serveur MySQL simple<br />
make_replication_sandbox : installe une réplication (par défaut un master, 2 slaves)<br />
make_multiple_sandbox : pour installer plusieurs serveurs identiques<br />
make_multiple_custom_sandbox : permet d&#8217;installer plusieurs serveurs de versions différentes</p>
<p><strong>Installer une réplication</strong></p>
<p>Une fois le <a href="http://dev.mysql.com/downloads/" target="_blank">binary récupéré</a>, nous utilisons la commande <strong>make_replication_sandbox</strong> pour obtenir un master et deux slaves. Elle prend en paramètre le tar.gz du binary (d&#8217;autres raccourcis sont possibles, je vous renvoie au fichier README de l&#8217;outil).</p>
<p><code>debian:/opt/mysql_sandbox_2.0.11# ./make_replication_sandbox /opt/mysql-5.0.67-linux-i686-glibc23.tar.gz</code></p>
<p>La sortie écran donne ceci :<br />
installing and starting master<br />
installing slave 1<br />
installing slave 2<br />
starting slave 1<br />
. sandbox server started<br />
starting slave 2<br />
.. sandbox server started<br />
initializing slave 1<br />
initializing slave 2<br />
replication directory installed on /root/sandboxes/rsandbox_5_0_67</p>
<p>C&#8217;est fait ! L&#8217;installation en elle-même a duré un peu plus de 15 secondes, disons que les lignes de commande à taper tiennent dans ce laps de temps&#8230;</p>
<p>Par défaut les &laquo;&nbsp;bacs à sable&nbsp;&raquo; installés par MySQL Sandbox se placent dans $HOME/sandboxes/. Dans le cadre de notre réplication, celle-ci a été installée ici : debian:~/sandboxes/rsandbox_5_0_67#</p>
<p>Notre réplication est d&#8217;ores et déjà fonctionnelle, on peut le vérifier en se connectant par exemple au slave numero 2 (faites un ps-ef | grep mysql pour afficher rapidement les noms/localisations des sockets par ex)</p>
<p>debian:/home/user# mysql &#8211;socket=/tmp/mysql_sandbox29473.sock -p</p>
<p>Le password par défaut est &laquo;&nbsp;msandbox&nbsp;&raquo;.</p>
<p>mysql&gt; show slave status\G<br />
Slave_IO_State: Waiting for master to send event<br />
Master_Host: 127.0.0.1<br />
Master_User: msandbox<br />
Master_Port: 29472<br />
Connect_Retry: 60<br />
Master_Log_File: mysql-bin.000001<br />
Read_Master_Log_Pos: 628<br />
Relay_Log_File: mysql_sandbox29473-relay-bin.000002<br />
Relay_Log_Pos: 765<br />
Relay_Master_Log_File: mysql-bin.000001<br />
<strong>Slave_IO_Running: Yes<br />
Slave_SQL_Running: Yes</strong></p>
<p>La réplication fonctionne, le pari est tenu !</p>
<p><strong>Les autres commandes disponibles</strong></p>
<p>Evoquées plus haut les commandes make_multiple_custom_sandbox, make_multiple_sandbox, make_sandbox et make_replication_sandbox vous permettent d&#8217;installer différentes topologies de serveurs MySQL, du master-master au groupe de serveurs aux versions identiques ou pas.</p>
<p>Exemple, pour installer la version MySQL 5.1.28 sur la même machine que notre réplication précédente :</p>
<p>debian:/opt/mysql_sandbox_2.0.11# ./make_sandbox /opt/mysql-5.1.28-rc-linux-i686-glibc23.tar.gz</p>
<p>Résultat :</p>
<p>&laquo;&nbsp;Your sandbox server was installed in /root/sandboxes/msb_5_1_28.&nbsp;&raquo;, simple non ?</p>
<p>D&#8217;autres commandes existent pour gérer votre installation :</p>
<p><strong>start, restart, stop, clear</strong>&#8230; un suffixe &laquo;&nbsp;_all&nbsp;&raquo; pour les réplications vient s&#8217;ajouter aux commandes précédentes, ex &laquo;&nbsp;<strong>stop_all</strong>&laquo;&nbsp;.</p>
<p>&laquo;&nbsp;m&nbsp;&raquo; est le raccourci de master, &laquo;&nbsp;s1&#8243;, &laquo;&nbsp;s2&#8243;, etc concernent les slaves.</p>
<p>Ainsi pour arrêter totalement notre configuration de réplication précédente, on effectue :</p>
<p>debian:~/sandboxes/rsandbox_5_0_67# ./stop_all</p>
<p>A l&#8217;écran :</p>
<p>executing &laquo;&nbsp;stop&nbsp;&raquo; on slave 1<br />
executing &laquo;&nbsp;stop&nbsp;&raquo; on slave 2<br />
executing &laquo;&nbsp;stop&nbsp;&raquo; on master</p>
<p>Comme vous le voyez, MySQL Sandbox est très simple d&#8217;emploi.</p>
<p>Pour aller plus loin je vous conseille vivement la lecture (c&#8217;est rapide) du <a href="http://forge.mysql.com/wiki/MySQL_Sandbox" target="_blank">wiki</a> de l&#8217;outil. Il reprend en fait le fichier README. Clair et concis vous y trouverez certainement votre bonheur : grâce à MySQL Sandbox, tester une nouvelle version de MySQL devient un jeu d&#8217;enfant.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dbnewz.com/2008/10/10/15-secondes-pour-installer-une-replication-mysql-avec-mysql-sandbox-pari-tenu/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Générer un jeu de données : shell, mysqlslap, et procédure stockée</title>
		<link>http://www.dbnewz.com/2008/08/19/generer-un-jeu-de-donnees-shell-mysqlslap-et-procedure-stockee/</link>
		<comments>http://www.dbnewz.com/2008/08/19/generer-un-jeu-de-donnees-shell-mysqlslap-et-procedure-stockee/#comments</comments>
		<pubDate>Mon, 18 Aug 2008 22:39:46 +0000</pubDate>
		<dc:creator>arnaud</dc:creator>
				<category><![CDATA[4.0]]></category>
		<category><![CDATA[4.1]]></category>
		<category><![CDATA[5.0]]></category>
		<category><![CDATA[5.1]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[index]]></category>
		<category><![CDATA[pratique]]></category>

		<guid isPermaLink="false">http://www.dbnewz.com/?p=61</guid>
		<description><![CDATA[Le prochain article de notre série consacrée aux index MySQL approche et j&#8217;ai besoin pour ce prochain épisode de générer une table de test de la forme suivante :  CREATE TABLE `t` (
`id` mediumint(8) unsigned NOT NULL auto_increment,
`date` timestamp NOT NULL,
`flag` tinyint(4) NOT NULL default '0',
PRIMARY KEY  (`id`),
KEY `flag` (`flag`)
) ENGINE=MyISAM;La structure est définie, [...]]]></description>
			<content:encoded><![CDATA[<p>Le prochain article de <a href="http://www.dbnewz.com/tag/index/" target="_blank">notre série consacrée aux index</a> MySQL approche et j&#8217;ai besoin pour ce prochain épisode de générer une table de test de la forme suivante :<br id="d7d0" /> <br id="zkp0" /> <code>CREATE TABLE `t` (<br />
`id` mediumint(8) unsigned NOT NULL auto_increment,<br />
`date` timestamp NOT NULL,<br />
`flag` tinyint(4) NOT NULL default '0',<br />
PRIMARY KEY  (`id`),<br />
KEY `flag` (`flag`)<br />
) ENGINE=MyISAM;</code><br id="yr-u" /><br id="yr-u0" />La structure est définie, reste à alimenter la table, disons 1 million d&#8217;enregistrements.<br id="ix.u" /> <br id="ix.u0" /> La valeur du champ &laquo;&nbsp;flag&nbsp;&raquo; est importante pour nos tests ultérieurs, sa valeur doit pour le moment être comprise entre 0 et 1, le tout à peu près uniformément distribué.<br id="g-s4" /> <br id="g-s40" /> La requête sera la suivante :<br id="ef7s" /> <br id="g-s41" /> <code>INSERT INTO test.t (flag) VALUES (ROUND(RAND()))</code>;<br id="d0-j" /> <br id="d0-j0" /> Il faut maintenant exécuter celle-ci 1 million de fois.<br id="gx_b" /> Voyons ce que cela donne en utilisant le shell, mysqlslap ou bien encore une procédure stockée.</p>
<p><span id="more-61"></span></p>
<p><strong>Le shell</strong></p>
<p>L&#8217;idée : générer un fichier texte des données à insérer puis exécuter le fichier de sortie comme entrée du client mysql.</p>
<p>Cette méthode n&#8217;est pas la plus efficace du lot, surtout pour 1 million de lignes, mais on peut néanmoins arriver à ses fins :</p>
<p><code>debian:/tmp# (for i in `seq 1 1000000`; do echo "insert into test.t(flag) values(round(rand()));"; done;) &gt; /tmp/insertions.txt</code></p>
<p>Puis :</p>
<p><code>debian:/tmp# mysql test &lt; /tmp/insertions.txt</code></p>
<p>Notre million de lignes est inséré.</p>
<p><strong>mysqlslap</strong></p>
<p>mysqlslap est un outil de benchmark que <a href="http://www.dbnewz.com/2008/07/07/mysqlslap-et-supersmack-deux-outils-de-benchmark-pour-mysql/" target="_blank">nous avons étudié récemment</a>. Nous détournons ici son utilisation première pour finalement exécuter notre million de requêtes :</p>
<p><code>debian:/usr/local/mysql51# ./bin/mysqlslap --user=root --socket=/tmp/mysql.sock --concurrency=1 --iterations=1000000 --query="insert into test.t (flag) values (round(rand()))"</code></p>
<p>Benchmark<br id="bel10" /> Average number of seconds to run all queries: 0.000 seconds<br id="bel11" /> Minimum number of seconds to run all queries: 0.000 seconds<br id="bel12" /> Maximum number of seconds to run all queries: 0.609 seconds<br id="bel13" /> Number of clients running queries: 1<br id="bel14" /> Average number of queries per client: 1</p>
<p>Cette solution est plus rapide que la précédente, aussi si vous disposez de mysqlslap, n&#8217;hésitez pas.</p>
<p><strong>La procédure stockée</strong> : REPEAT&#8230; UNTIL</p>
<p>Dès lors qu&#8217;on envisage d&#8217;insérer 1 million de lignes dans une table, la notion de &laquo;&nbsp;boucle&nbsp;&raquo; n&#8217;est jamais très loin, et finalement la procédure stockée non plus. Celle-ci est en effet un moyen simple et rapide d&#8217;implémenter un traitement répétitif.</p>
<p>Basique et à peine paramétrable (seul le nombre d&#8217;enregistrements à insérer est dynamique), voici à quoi ressemble la procédure stockée permettant d&#8217;insérer notre million de lignes :</p>
<p><code>delimiter //<br id="bfwc" />CREATE PROCEDURE fill_table(nb_rows INT)<br id="bfwc0" />BEGIN<br id="bfwc1" /> DECLARE i INT DEFAULT 0;<br id="bfwc2" /> REPEAT<br id="bfwc3" /> SET i = i + 1;<br id="bfwc4" /> INSERT INTO t (flag) VALUES(round(rand()));<br id="bfwc5" /> UNTIL i &gt;= nb_rows<br id="bfwc6" /> END REPEAT;<br id="bfwc7" />END;<br id="bfwc8" />//<br id="bfwc9" />delimiter ;</code><br id="bfwc10" /><br id="bfwc11" />Pas vraiment besoin de commenter le code, on note simplement les &laquo;&nbsp;delimiter&nbsp;&raquo; qui permettent de définir une autre terminaison que &laquo;&nbsp;;&nbsp;&raquo; le temps d&#8217;écrire la procédure dans le client mysql.</p>
<p>Pour appeler celle-ci :<br id="z6fd" /><br id="z6fd0" /><code>mysql&gt; CALL fill_table(1000000);</code><br id="z6fd1" />Query OK, 1 row affected (1 min 37.98 sec)</p>
<p>Cette solution est la plus rapide des trois présentées ici.</p>
<p>Mission accomplie ? Presque&#8230;</p>
<p>Avant de lancer la procédure stockée, la commande SHOW INDEX affichait une cardinalité de 0 pour la clé primaire et de NULL pour notre index situé sur &laquo;&nbsp;flag&nbsp;&raquo;.</p>
<p>Une fois la procédure terminée, un SHOW INDEX indique cette fois une cardinalité mise à jour pour la clé primaire (1 000 000) mais celle-ci est toujours à NULL pour l&#8217;index &laquo;&nbsp;flag&nbsp;&raquo;.</p>
<p>La commande <a href="http://dev.mysql.com/doc/refman/5.1/en/analyze-table.html" target="_blank">ANALYZE </a>permet de laver cet affront :</p>
<p><code>mysql&gt; ANALYZE TABLE t;<br />
+---------+---------+----------+----------+<br />
| Table   | Op      | Msg_type | Msg_text |<br />
+---------+---------+----------+----------+<br />
| world.t | analyze | status   | OK       |<br />
+---------+---------+----------+----------+</code></p>
<p>Les statistiques de notre index sont désormais à jour et la commande SHOW INDEX indique désormais une cardinalité de 2 pour l&#8217;index &laquo;&nbsp;flag&nbsp;&raquo;, le champ indexé ne comporte effectivement que des 0 ou des 1.</p>
<p>Nous voilà face à un index de cardinalité 2&#8230; Peu sélectif ? Inutile ? Mieux que rien ? Les paris sont ouverts.</p>
<p>Réponse au prochain numéro, stay tuned.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dbnewz.com/2008/08/19/generer-un-jeu-de-donnees-shell-mysqlslap-et-procedure-stockee/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Les index MySQL : types, placements, efficacité</title>
		<link>http://www.dbnewz.com/2008/06/27/les-index-mysql-types-placements-efficacite/</link>
		<comments>http://www.dbnewz.com/2008/06/27/les-index-mysql-types-placements-efficacite/#comments</comments>
		<pubDate>Fri, 27 Jun 2008 06:52:44 +0000</pubDate>
		<dc:creator>arnaud</dc:creator>
				<category><![CDATA[4.0]]></category>
		<category><![CDATA[4.1]]></category>
		<category><![CDATA[5.0]]></category>
		<category><![CDATA[5.1]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[index]]></category>
		<category><![CDATA[tuning]]></category>

		<guid isPermaLink="false">http://www.dbnewz.com/?p=48</guid>
		<description><![CDATA[Déjà trois semaines d&#8217;écoulées depuis que certains d&#8217;entre vous, les &#171;&#160;héros&#160;&#187;, ont posé leurs questions (oui il est possible de devenir un héros rien qu&#8217;en lisant dbnewz ! Les véritables héros sont d&#8217;ailleurs abonnés au tout nouveau flux feedburner   )
Trois semaines d&#8217;attente, cela mérite un billet digne de ce nom, c&#8217;est parti.
Indexer, pourquoi [...]]]></description>
			<content:encoded><![CDATA[<p>Déjà trois semaines d&#8217;écoulées depuis que certains d&#8217;entre vous, les &laquo;&nbsp;héros&nbsp;&raquo;, ont posé leurs questions (oui il est possible de <a href="http://www.dbnewz.com/2008/06/05/les-index-mysql-la-serie-dont-vous-etes-le-heros/" target="_blank">devenir un héros</a> rien qu&#8217;en lisant dbnewz ! Les véritables héros sont d&#8217;ailleurs abonnés au tout nouveau flux <a href="http://feeds.feedburner.com/Dbnewz" target="_blank">feedburner</a> <img src='http://www.dbnewz.com/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' />  )</p>
<p>Trois semaines d&#8217;attente, cela mérite un billet digne de ce nom, c&#8217;est parti.</p>
<p><strong>Indexer, pourquoi ?</strong><br id="dz-w" /><br id="yo1g" />L&#8217;indexation peut avoir plusieurs buts : <br id="gpy7" />- Accéder à ses données plus rapidement, les index sont en effet l&#8217;outil le plus puissant pour <strong>accélérer les temps d&#8217;exécution de vos requêtes</strong> jusqu&#8217;à parfois plusieurs centaines de % !<br id="gpy70" />- Définir le degré d&#8217;unicité d&#8217;une colonne donnée : chaque champ doit-il être unique ? les doublons sont-ils autorisés ?<br id="e0_r" /><br id="uby5" /><strong>Principe de fonctionnement</strong><br id="kvk1" /><br id="kvk10" />Lorsque vous envoyez une requête à votre serveur MySQL, celle-ci est d&#8217;abord confiée au &laquo;&nbsp;parseur&nbsp;&raquo; SQL qui a pour but de vérifier si la syntaxe de votre demande est correcte. Cette étape franchie, la requête passe par &laquo;&nbsp;l&#8217;optimiseur&nbsp;&raquo;. Il s&#8217;agit ici de déterminer le <strong>plan d&#8217;exécution</strong> de la requête afin que celle-ci s&#8217;exécute le plus rapidement possible.</p>
<p>L&#8217;optimiseur détecte si d&#8217;éventuels index sont disponibles, si c&#8217;est le cas il décidera de s&#8217;en servir&#8230; ou pas : il est parfois plus rapide de ne pas se servir d&#8217;un index <strong>!</strong> Nous verrons pourquoi au cours de cette série d&#8217;articles.</p>
<p>Une fois le plan d&#8217;exécution achevé, c&#8217;est le moteur de stockage qui prend le relais, celui-ci peut être vu comme un &laquo;&nbsp;module&nbsp;&raquo; de MySQL :</p>
<p><a href="http://dev.mysql.com/tech-resources/articles/mysql_5.0_psea1.html" target="_blank"><img class="alignnone" src="http://dev.mysql.com/tech-resources/articles/mysql_5.0_psea1.jpg" alt="Les moteurs de stockage MySQL sont des \" width="362" height="261" /></a></p>
<p><span id="more-48"></span>Pour schématiser, et dans un monde idéal, lorsqu&#8217;un index est disponible et &laquo;&nbsp;compatible&nbsp;&raquo; avec votre requête, l&#8217;optimiseur MySQL peut décider de l&#8217;utiliser afin d&#8217;éviter de parcourir l&#8217;ensemble des données des tables concernées.</p>
<p>Un exemple couramment employé pour illustrer ce propos consiste à imaginer la difficultée que nous aurions à retrouver quelqu&#8217;un dans l&#8217;annuaire si nous connaissions son nom mais pas l&#8217;alphabet (qui est notre index)&#8230; Transposé au monde informatique cela donne un serveur MySQL qui compare une à une les entrées du botin pour trouver toutes celles qui correspondent au nom recherché. Si au contraire ce nom est indexé, et si celui-ci commence par exemple par &#8216;T&#8217;, le serveur sait directement qu&#8217;il doit démarrer sa recherche à partir du &#8216;T&#8217;. Imaginez l&#8217;impact en terme de gain de temps lorsque plusieurs jointures sont concernées : deux tables de 10 000 lignes chacune forment un produit cartésien de 100 000 000 lignes environ à étudier&#8230;<br id="mxtu" /><br id="mxtu0" />Les index n&#8217;ont hélas pas que des avantages :<br id="mxtu4" />- Les opérations de mises à jour (INSERT, UPDATE, DELETE) sont en effet ralenties puisqu&#8217;en plus des données, les index doivent eux aussi être mis à jour lors de ces opérations, c&#8217;est le prix à payer&#8230; Ce prix peut néamoins se &laquo;&nbsp;négocier&nbsp;&raquo;, nous verrons cela plus tard.<br id="q.-8" /><br id="q.-80" /><strong>Notre terrain de jeu, la base &laquo;&nbsp;world&nbsp;&raquo;</strong><br id="w4ef" /></p>
<p>MySQL propose au téléchargement plusieurs bases d&#8217;exemple dont <a href="http://dev.mysql.com/doc/" target="_blank">&laquo;&nbsp;world&nbsp;&raquo; et &laquo;&nbsp;sakila&nbsp;&raquo;</a>. Elles épargnent le soin aux utilisateurs de MySQL souhaitant tester quelques requêtes de se constituer eux-mêmes une base de test, celles-ci sont prêtes à l&#8217;emploi.</p>
<p>Nous utiliserons pour nos tests la base &laquo;&nbsp;world&nbsp;&raquo;. Très simple puisque constituée uniquement de trois petites tables MyISAM (Country, CountryLanguage et City), elle permet de se concentrer uniquement sur les index sans perdre de temps à assimiler un schéma plus complexe.<br id="m4x6" /><br id="m4x60" />Si vous souhaitez transformer le script de création SQL de la base &laquo;&nbsp;world&nbsp;&raquo; en une version graphique &laquo;&nbsp;presque&nbsp;&raquo; MCD (les relations entre les tables ne sont pas générées automatiquement pour cette base), le billet précédent est fait pour vous, <a href="http://www.dbnewz.com/2008/06/22/dbdesigner-4-generer-son-mcd-par-reverse-engineering/" target="_blank">les étapes d&#8217;installation et de génération du MCD par &laquo;&nbsp;reverse engineering&nbsp;&raquo; avec DBDesigner 4</a> y sont décrites.<br id="lfl2" /><br id="lfl20" />Voici ce qu&#8217;on peut obtenir pour cette base à partir de DBDesigner :<br id="lfl21" /><br id="lfl22" /><a href="http://www.dbnewz.com/wp-content/uploads/2008/06/world_db_small.png" target="_blank"><img class="alignnone size-thumbnail wp-image-50" title="world_db_small" src="http://www.dbnewz.com/wp-content/uploads/2008/06/world_db_small-150x150.png" alt="" width="150" height="150" /></a><br id="lfl23" /><br id="lfl24" /></p>
<p><strong id="xz5z">Quel type d&#8217;index choisir : PRIMARY KEY, UNIQUE, ou INDEX ?</strong><br id="txto" /><br id="txto0" />Choisissez le type de vos index avec soin :<br id="txto1" />- Une clé primaire (<strong>PRIMARY KEY</strong>) est strictement unique, les NULL ne sont pas autorisés.<br id="e.px" />- Un index de type <strong>UNIQUE </strong>est comparable à une clé primaire, mis à part pour les valeurs NULL puisque celles-ci sont autorisées (et potentiellement en plusieurs occurences).<br id="fy.m" />- Un index de type <strong>INDEX </strong>ou <strong>KEY </strong>(c&#8217;est un alias) signifie simplement que l&#8217;on souhaite indexer une colonne susceptible de contenir des doublons.<br id="blrx" /><br id="blrx0" />Les index de type <strong>FULLTEXT</strong> et <strong>SPATIAL</strong> sont particuliers et méritent un épisode à eux seuls, ils seront donc évoqués ultérieurement.<br id="k7_h" /><br id="k7_h0" />Passons rapidement sur l&#8217;étape de <a href="http://dev.mysql.com/doc/refman/5.0/fr/create-index.html" target="_blank">déclaration d&#8217;un index</a>, celle-ci s&#8217;effectue soit au moment de la création de la table, soit plus tard comme ici :<br id="or3." /><br id="or3.0" />mysql&gt; <code>CREATE INDEX idx_district ON City (District);<br id="n0.-" />Query OK, 4079 rows affected (0.04 sec)<br id="n0.-0" />Records: 4079  Duplicates: 0  Warnings: 0</code><br id="n0.-1" /><br id="n0.-2" />Nous venons de créer un index de type INDEX (autorise les doublons) sur la colonne District de la table City.<br id="n0.-3" /><br id="n0.-4" />Attention, si nous avions tenté la même chose avec un index de type UNIQUE&#8230;<br id="l3as" /><br id="l3as0" />mysql&gt; <code>CREATE UNIQUE INDEX idx_district ON City (District);<br id="l3as1" />ERROR 1062 (23000): Duplicate entry 'Zuid-Holland' for key 2</code><br id="dh1k" /><br id="dh1k0" />&#8230; MySQL nous signale qu&#8217;il y&#8217;a déjà des doublons dans la colonne District, impossible donc de créer un index de type UNIQUE sur celle-ci.<br id="najp" /><br id="najp0" /><strong>Pour supprimer cet index :</strong><br id="najp1" />mysql&gt; <code>DROP INDEX idx_district ON City;</code><br id="a8y02" />Ou :<br id="a8y04" />mysql&gt; <code>ALTER TABLE City DROP INDEX idx_district;</code><br id="bim:" /><strong><br id="bim:0" />Pour visualiser les index d&#8217;une table :</strong><br id="zw.m" /><br id="zw.m0" />mysql&gt; SHOW INDEX FROM Country;</p>
<p><strong>Attention aux doublons !</strong><br id="zw.m17" /><br id="bim:2" />Inutile de rajouter un index de type &laquo;&nbsp;INDEX&nbsp;&raquo; ou encore &laquo;&nbsp;UNIQUE&nbsp;&raquo; sur un champ qui est déjà clé primaire par exemple&#8230; Vous dupliqueriez inutilement les index avec à la clé un gaspillage d&#8217;espace disque/mémoire, des ralentissements inutiles lors des mises à jour, davantage de travail pour l&#8217;optimiseur&#8230;<br id="fq0r" /><br id="p-i7" /><strong id="kw9g2">Quels types de champ indexer ?</strong><br id="bo1r" /><br id="bo1r0" />INT, VARCHAR, BLOB&#8230; ? Quels sont les meilleurs candidats à l&#8217;indexation ?</p>
<p><strong>Plus l&#8217;index est court, mieux c&#8217;est</strong> : un index est en permanence comparé à d&#8217;autres valeurs (celles recherchées), ces comparaisons sont plus rapides si la zone à comparer est plus courte. Des index concis occupent également moins de place sur disque, génèrent moins d&#8217;I/O (activité disque s&#8217;ils ne sont pas en mémoire) et peuvent ainsi être stockés en plus grand nombre dans une même quantité de RAM (pensez au <a href="http://dev.mysql.com/doc/refman/5.0/en/server-system-variables.html#option_mysqld_key_buffer_size" target="_blank">key_buffer_size</a> de MyISAM par exemple).<br id="e1fk" /><br id="e1fk0" />Bref, si vous désirez stocker une liste de noms de villes sous forme de chaînes de caractères, sachez qu&#8217;il est inutile de réserver un champ de type CHAR(255) : rares sont celles qui atteindront cette longueur, pensez plutôt au VARCHAR qui s&#8217;adaptera à la longueur de vos valeurs.<br id="g6j1" />Plus malin encore, lors de la conception de votre base de données, intéressez-vous aux différentes formes de normalisation : 1NF, 2NF, 3NF, ces méthodes permettent d&#8217;obtenir un schéma qui permet de partir sur de bonnes bases.<br id="ist_" />En gardant cet exemple des villes, si vous stockez dans une table &laquo;&nbsp;inscrits&nbsp;&raquo; toutes les infos contextuelles à un utilisateur, dont sa ville, envisagez de stocker dans une table &laquo;&nbsp;ville&nbsp;&raquo;, toutes les infos qui s&#8217;y rapportent : nom, population, etc. Reliez ensuite votre table &laquo;&nbsp;inscrits&nbsp;&raquo; à la table &laquo;&nbsp;ville&nbsp;&raquo; par la valeur de la clé primaire de ville et vous supprimerez ainsi tous ces libellés de villes identiques au profit d&#8217;un ID bien plus rapide et économique.<br id="yxd7" /></p>
<p><strong>Soyez radins !</strong></p>
<p>Vos index seront d&#8217;autant plus efficaces s&#8217;ils sont apposés sur des champs bien adaptés à vos données. <strong>Ne gaspillez pas le capital &laquo;&nbsp;performance&nbsp;&raquo; de vos index</strong> en utilisant un INT pour stocker par exemple la vitesse légale sur autoroute en France (par temps sec) : 130 km/h&#8230;  Un TINY INT UNSIGNED suffira (permet de stocker les valeurs de 0 à 255). Un INT permet lui de stocker des valeurs comprises entre <code id="cps40" class="literal" style="font-family: Verdana;">-2.147.483.648</code> <code id="cps42" class="literal" style="font-family: Verdana;">2.147.483.647, et en rajoutant l'attri</code>but UNSIGNED, on obtient un rayon d&#8217;action de 0 à plus de 4 milliards ! A quoi bon utiliser un INT dans cet exemple ? Quand on sait de plus qu&#8217;un INT occupe quatre fois plus de place qu&#8217;un TINY INT, l&#8217;impact sur les performances et la perte d&#8217;espace avec une table de plusieurs millions d&#8217;enregistrements est évident&#8230;</p>
<p>Prenez connaissance des <a href="http://dev.mysql.com/doc/refman/5.0/en/numeric-types.html" target="_blank">caractéristiques des types de données</a> que vous utilisez. Visualisez également <a href="http://dev.mysql.com/doc/refman/5.0/fr/storage-requirements.html" target="_blank">la taille requise</a>, c&#8217;est un élement qui peut s&#8217;avérer dissuasif.<br id="x-cm" /><br id="x-cm0" />Pour résumer, n&#8217;indexez pas une colonne en fonction de son type, mais prenez soin dans un premier temps de définir celles-ci avec le type de données qui leur convient le mieux, le plus économique. Prévoyez une marge néanmoins : n&#8217;utilisez pas un TINY INT même UNSIGNED pour un identifiant de clé primaire AUTO_INCREMENT concernant une newsletter d&#8217;un grand service commercial : si tout se passe bien vous devriez rapidement dépasser le seuil des 255 inscrits&#8230; Le type de données juste &laquo;&nbsp;au-dessus&nbsp;&raquo;, SMALLINT UNSIGNED, qui permet d&#8217;aller jusqu&#8217;à 65535, est sans doute plus confortable.<br id="t9cg" /><br id="t9cg0" /><strong id="n1bv">Le tips dbnewz : </strong>utilisez la commande <strong id="dwmm">PROCEDURE ANALYSE()</strong><br id="n1bv0" /><br id="n1bv1" />Cette commande analyse pour vous vos tables et vous propose le type idéal pour vos données&#8230; si vous en avez bien sûr, ça ne peut pas vous aider lors d&#8217;une création de table. En revanche, elle permet &laquo;&nbsp;d&#8217;auditer&nbsp;&raquo; vos enregistrements actuels, d&#8217;en tirer quelques statistiques et propose le type le plus adapté :<br id="n3yt" /><br id="uv4e" />mysql&gt; <code>SELECT Name FROM Country PROCEDURE ANALYSE(10,256)\G<br id="uv4e1" /> Field_name: world.Country.<strong>Name</strong><br id="uv4e2" /> Min_value: Afghanistan<br id="uv4e3" /> Max_value: Zimbabwe<br id="uv4e4" /> <strong>Min_length: 4</strong><br id="uv4e5" /> <strong>Max_length: 44</strong><br id="uv4e6" /> Empties_or_zeros: 0<br id="uv4e7" /> Nulls: 0<br id="uv4e8" /><strong>Avg_value_or_avg_length: 10.0962</strong><br id="uv4e9" /> Std: NULL<br id="uv4e10" /> <strong>Optimal_fieldtype</strong>: VARCHAR(44) NOT NULL<br id="uv4e11" />1 row in set (0.00 sec)</code><br id="uv4e12" /><br id="n3yt0" />Vous pouvez bien sûr effectuer cette requête sur l&#8217;intégralité des champs de l&#8217;une de vos tables (SELECT *&#8230;)<br id="lgar" />Les paramètres fournis à <a href="http://dev.mysql.com/doc/refman/5.1/en/procedure-analyse.html" target="_blank">PROCEDURE ANALYSE ()</a> sont à modifier en fonction de vos souhaits. Par défaut cette fonction a tendance à vouloir transformer toutes vos chaînes de caractères en champs <a href="http://dev.mysql.com/doc/refman/5.1/en/enum.html" target="_blank">ENUM</a> (stockés sous forme numérique en interne), à vous de définir combien de champs ENUM vous êtes prêts à utiliser. Réservez-les pour les cas où le champ représente une courte liste &laquo;&nbsp;fermée&nbsp;&raquo;, ex &laquo;&nbsp;M&nbsp;&raquo; ou &laquo;&nbsp;F&nbsp;&raquo;, les jours de la semaine par exemple, etc.<br id="hj59" /><br id="hj590" /><strong id="ny9g">Où placer ses index : quels sont les champs à indexer ?</strong><br id="ny9g0" /><br id="ny9g1" />Les champs concernés par une clause <strong>WHERE, ORDER BY, GROUP BY, MIN(), MAX()</strong>, ainsi que les champs qui permettent de <strong>relier des tables entre elles</strong>, sont de bons candidats à l&#8217;indexation, exemple :<br id="abxv" /><br id="aln7" /><code>SELECT ci.Name, ci.Population <br id="aln70" />FROM City ci INNER JOIN Country co ON ci.CountryCode = co.Code<br id="aln71" />WHERE ci.Population &gt; 5000000<br id="aln72" />ORDER BY ci.District ASC LIMIT 3</code><br id="aln73" /><br id="ulja" />- Le champ CountryCode de la table City ainsi que le champ Code de la table Country sont tous les deux à indexer. C&#8217;est d&#8217;ailleurs le cas ici puisque ces deux champs sont respectivement clés primaires de la table City et Country.<br id="ulja0" />- Le champ Population est intéressant à indexer, il permet à MySQL de parcourir très rapidement les villes par leur population triée et évite de comparer l&#8217;intégralité des populations de chaque ville, une à une.<br id="fv9-" />- Le champ District est également un candidat à l&#8217;indexation, il peut aider MySQL à trier les données plus rapidement.<br id="o0-s" /><br id="o0-s0" />A retenir : on indexe en priorité les champs impliqués dans les clauses évoqués ci-dessus (en gras), pas forcément ceux présents dans le SELECT.<br id="aln75" /><br id="o98j6" /><strong>Les index composés (ou multiples) et la règle du leftmost prefixing</strong><br id="ebx3" /><br id="ebx30" />&laquo;&nbsp;Faut-il préférer un index unique ou composé&nbsp;&raquo; était <a href="http://www.dbnewz.com/2008/06/05/les-index-mysql-la-serie-dont-vous-etes-le-heros/#comments" target="_blank">l&#8217;une des questions posées</a> par l&#8217;un d&#8217;entre vous il y&#8217;a quelques semaines&#8230;<br id="zkm0" /><br id="zkm00" />Un index composé doit se construire en fonction des requêtes que vous effectuez sur la table concernée.<br id="ku.e" />Prenons pour exemple la table City et ses cinq champs (ID, Name, CountryCode, District, Population).<br id="ku.e0" /><br id="ku.e1" />Si les seules requêtes que vous avez sur City sont du type :</p>
<p><code>SELECT ... FROM City WHERE Name = "..."</code></p>
<p>&#8230; Indexez Name et tout ira bien.</p>
<p>Si en revanche il vous arrive de trier non seulement sur &laquo;&nbsp;Name&nbsp;&raquo; mais également sur le code du pays :</p>
<p><code>SELECT ... FROM City WHERE Name = "..." AND CountryCode = "..."</code><br id="f50i0" /><br id="f50i1" />Dans ce cas, plutôt que de laisser MySQL comparer tous les CountryCode de la table avec votre recherche (ex : &laquo;&nbsp;FRA&nbsp;&raquo;), indexez la colonne CountryCode&#8230; Oui mais pas toute seule !<br id="lkbv" />Considérez en effet pour l&#8217;instant que MySQL n&#8217;utilise qu&#8217;un index par table, l&#8217;optimiseur MySQL choisit donc le plus restrictif afin que votre requête s&#8217;exécute le plus rapidement possible (nous verrons plus tard les cas particuliers où MySQL peut tirer parti de plusieurs index).<br id="w0mb" />Conséquence, il vous faut trouver un index qui soit utilisable pour vos deux critères de recherche : &laquo;&nbsp;Name&nbsp;&raquo; et &laquo;&nbsp;CountryCode&nbsp;&raquo;. La solution : créez un index multiple sur ces deux champs.<br id="tilr" /><br id="tilr0" />Dès lors que vous utilisez un index multiple, <strong>la règle </strong><strong>du leftmost prefixing</strong> rentre en jeu. Trop souvent méconnue, elle permet pourtant de créer ses index de façon efficace et d&#8217;éviter les doublons.<br id="ny9g2" /><br id="mqai" />Afin d&#8217;illustrer cette règle, ajoutons cette fois à la table City un index multiple sur les champs Name, CountryCode, District et Population :</p>
<p>mysql&gt; <code>CREATE INDEX name_cc_dis_pop ON City (Name, CountryCode, District, Population);</code><br id="ub980" /></p>
<p>Voici ce que l&#8217;on obtient avec le <strong>SHOW INDEX</strong> correspondant (la clé primaire existait déjà à la création de la table, ci-dessous une vue partielle des résultats réels) :</p>
<p>mysql&gt; <code>SHOW INDEX FROM City;<br id="cvh60" /></code></p>
<p>|<code> <strong>Key_name</strong> | <strong>Seq_in_index</strong> | <strong>Column_name</strong> <br id="cvh62" />+-------+------------+-----------------+</code></p>
<p><code><strong>|name_cc_dis_pop</strong> |   <strong>1</strong> |  <strong>Name</strong> </code></p>
<p><strong>|</strong><code><strong>name_cc_dis_pop</strong> |   <strong>2</strong> | <strong>CountryCode</strong> </code></p>
<p><code><strong>|</strong><strong>name_cc_dis_pop</strong> |   <strong>3</strong> | <strong>District</strong> </code></p>
<p><code> <strong>|name_cc_dis_pop</strong> |   <strong>4</strong> | <strong>Population</strong> |  <br id="cvh68" />+-------+------------+-----------------+</code></p>
<p>On remarque par rapport au SHOW INDEX précédent que cette fois-ci nous avons la colonne &laquo;&nbsp;Seq_in_index&nbsp;&raquo; qui s&#8217;incrémente pour chaque colonne qui compose notre index multiple. La position de chaque index dans cette séquence a une importance, c&#8217;est ce que nous allons voir maintenant.</p>
<p>Tel quel, cet index multiple sera potentiellement utilisé pour les requêtes de ce type :</p>
<p><code>SELECT ... FROM City WHERE Name = ... AND CountryCode = ... AND District = ... AND Population = ...<br id="w2qj" />SELECT ... FROM City WHERE Name = ... AND CountryCode = ... AND District = ... <br id="w2qj0" />SELECT ... FROM City WHERE Name = ... AND CountryCode = ... <br id="w2qj1" />SELECT ... FROM City WHERE Name = ...</code><br id="w2qj2" /><br id="w2qj3" />Une fois cette &laquo;&nbsp;logique&nbsp;&raquo; acquise, on comprend qu&#8217;il est inutile de rajouter un index sur Name par exemple puisque cette colonne est déjà indexée grâce à cet index multiple. Idem pour notre exemple précédent  d&#8217;index multiple concernant les champs Name et CountryCode, là encore inutile de recréer un index sur ces deux champs puisque ces derniers sont déjà représentés dans notre dernier exemple.<br id="niuy" /><br id="niuy0" />En revanche, si l&#8217;ordre de vos champs ne respecte pas l&#8217;ordre de séquence de votre index, comme ici :</p>
<p><code>SELECT ... FROM City WHERE Name = ... AND Population = ..</code></p>
<p>Cette requête ne bénéficiera pas complètement de l&#8217;index multiple précedemment crée, cela dit l&#8217;optimiseur tirera sûrement parti de cet index pour la colonne Name, mais pas pour le second critère de recherche.<br id="ewwh0" /><br id="ewwh1" />De même si votre requête est du type :</p>
<p><code>SELECT ... FROM City WHERE CountryCode = ...<br id="ewwh3" />SELECT ... FROM City WHERE District = ...<br id="ewwh4" />SELECT ... FROM City WHERE Population = ...<br id="ewwh5" />SELECT ... FROM City WHERE CountryCode = ... AND District = ... AND Population = ...</code><br id="ewwh6" /></p>
<p>&#8230; et autres variations qui ne débutent pas avec &laquo;&nbsp;Name&nbsp;&raquo; et ne respectent pas ensuite l&#8217;ordre de séquence de l&#8217;index (Name, CountryCode, District, et Population), l&#8217;index ne sera pas utilisé.<br id="z9sx" /><br id="z9sx0" />En conséquence, il est donc tout à fait légitime d&#8217;indexer par ailleurs la colonne Population seule si vous avez des requêtes du type :</p>
<p><code>SELECT ... FROM City WHERE Population &gt; ... </code><br id="p_16" /><br id="ny9g4" /><strong>Mesurez l&#8217;efficacité des index avec EXPLAIN</strong><br id="n6b2" /></p>
<p>Impossible d&#8217;évoquer les index sans parler de la commande <strong>EXPLAIN</strong>. Absolument <strong id="kjkb">fondamentale</strong>, elle affiche le plan d&#8217;exécution décidé par l&#8217;optimiseur MySQL et vous permet de mesurer si oui ou non vos index sont réellement utilisés.<br id="tqy0" /><br id="tqy00" />Reprenons une des premières requêtes de ce billet et rajoutons-lui le mot clé EXPLAIN :<br id="fo2v" />(on considère ici que la table City ne contient que sa clé primaire, pas les index rajoutés précedemment)</p>
<p>mysql&gt; <code><strong>EXPLAIN </strong>SELECT ci.Name, ci.Population<br id="dhkz" />FROM City ci INNER JOIN Country co ON ci.CountryCode = co.Code<br id="dhkz0" />WHERE ci.Population &gt; 5000000<br id="dhkz1" />ORDER BY ci.District ASC LIMIT 3\G<br id="dhkz2" /></code></p>
<p><code>*************************** 1. row *************<br />
id: 1<br />
select_type: SIMPLE<br />
table: ci<br />
type: ALL<br />
<strong>possible_keys: NULL</strong><br />
key: NULL<br />
key_len: NULL<br />
ref: NULL<br />
<strong>rows: 4079</strong><br />
Extra: Using where; Using filesort<br />
*************************** 2. row *************<br />
id: 1<br />
select_type: SIMPLE<br />
table: co<br />
type: eq_ref<br />
<strong>possible_keys: PRIMARY</strong><br />
<strong>key: PRIMARY</strong><br />
key_len: 3<br />
<strong>ref: world.ci.CountryCode</strong><br />
<strong>rows: 1</strong><br />
<strong>Extra: Using index</strong><br />
2 rows in set (0.00 sec)</code></p>
<p>La commande EXPLAIN sera étudiée plus précisemment dans un autre épisode, pour le moment contentons-nous de prêter attention aux champs en gras :</p>
<p>- Sur la première ligne le type ALL signale que MySQL doit effectuer un &laquo;&nbsp;full table scan&nbsp;&raquo; c&#8217;est à dire parcourir entièrement la table City qui compte 4079 enregistrements, ceci afin de repérer quelles sont les villes qui ont une population supérieure à 5M d&#8217;habitants. Aucun index/key n&#8217;a pu être utilisé (possible_keys : NULL) pour résoudre cette partie de la requête. La colonne &laquo;&nbsp;rows&nbsp;&raquo; indique le nombre approximatif d&#8217;enregistrements que MySQL pense devoir analyser pour mener à bien l&#8217;opération.</p>
<p>- La seconde ligne nous indique que cette fois MySQL a un candidat pour l&#8217;indexation, il s&#8217;agit de la clé primaire de la table Country, la jointure s&#8217;effectuant avec le champ &laquo;&nbsp;CountryCode&nbsp;&raquo; de la table City, qui est également une clé primaire. Résultat : MySQL effectue la correspondance très rapidement (rows : 1 et extra : Using index).<br id="sewv" /><br id="sewv0" />Ceci répond à une des questions posées précedemment par un lecteur : &laquo;&nbsp;Quand préférer le FULL TABLE SCAN à l&#8217;index ?&nbsp;&raquo; C&#8217;est en fait le travail de l&#8217;optimiseur, il doit déterminer si oui ou non un index vous fera gagner du temps. Il se peut qu&#8217;il se trompe (rarement), nous verrons comment orienter ses choix si besoin.<br id="ai:q" /><br id="ai:q0" />Rajoutons maintenant un index de type &laquo;&nbsp;INDEX&nbsp;&raquo; sur la colonne Population :<br id="ai:q1" /></p>
<p>mysql&gt; <code>CREATE INDEX idx_pop ON City (Population);</code><br id="j6rd" /><br id="j6rd0" />Puis appliquons à nouveau la même commande EXPLAIN, on obtient cette fois :</p>
<p><code>*************************** 1. row ************<br />
id: 1<br />
select_type: SIMPLE<br />
table: ci<br />
type: range<br />
<strong>possible_keys: idx_pop</strong><br />
<strong>key: idx_pop</strong><br />
key_len: 4<br />
ref: NULL<br />
<strong>rows: 25</strong><br />
Extra: Using where; Using filesort<br />
*************************** 2. row ************<br />
id: 1<br />
select_type: SIMPLE<br />
table: co<br />
type: eq_ref<br />
<strong>possible_keys: PRIMARY<br />
key: PRIMARY</strong><br />
key_len: 3<br />
ref: world.ci.CountryCode<br />
<strong>rows: 1</strong><br />
Extra: Using index<br />
2 rows in set (0.02 sec)<br id="ecl19" /></code><br id="ecl110" />Notre index a permis à MySQL de lire <strong>160 fois moins de lignes</strong> cette fois-ci par rapport à l&#8217;exemple précédent&#8230; Seules 25 lignes de la table City sont lues désormais (au lieu des 4079 lignes précedemment parcourues). C&#8217;est un gain très intéressant en termes de ressources serveur ! Amateurs de chiffres, des benchmarks sont prévus dans la suite de cette série.<br id="laec" /><br id="laec0" />Afin de patienter jusqu&#8217;aux prochains épisodes justement, vous pouvez commencer par appliquer les quelques conseils de ce billet tout en relisant pourquoi pas l&#8217;article concernant <a href="http://www.dbnewz.com/2008/05/19/choisir-limplementation-de-ses-index-b-tree-ou-hash-quelles-differences/" target="_blank">l&#8217;implémentation des index (BTREE ou HASH)</a> selon le type de votre moteur de stockage (MyISAM, InnoDB ou MEMORY). <br id="dhkz10" /><br id="m:ru" />Il reste certaines de vos questions en suspens, elles ne sont pas oubliées et seront débattues ici très prochainement.</p>
<p>Si vous avez des questions ou des remarques concernant cette première étape, n&#8217;hésitez pas.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dbnewz.com/2008/06/27/les-index-mysql-types-placements-efficacite/feed/</wfw:commentRss>
		<slash:comments>17</slash:comments>
		</item>
		<item>
		<title>DBDesigner 4 : générer son MCD par reverse engineering</title>
		<link>http://www.dbnewz.com/2008/06/22/dbdesigner-4-generer-son-mcd-par-reverse-engineering/</link>
		<comments>http://www.dbnewz.com/2008/06/22/dbdesigner-4-generer-son-mcd-par-reverse-engineering/#comments</comments>
		<pubDate>Sun, 22 Jun 2008 10:54:07 +0000</pubDate>
		<dc:creator>arnaud</dc:creator>
				<category><![CDATA[4.0]]></category>
		<category><![CDATA[4.1]]></category>
		<category><![CDATA[5.0]]></category>
		<category><![CDATA[5.1]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[outils]]></category>
		<category><![CDATA[pratique]]></category>

		<guid isPermaLink="false">http://www.dbnewz.com/?p=46</guid>
		<description><![CDATA[Disposer d&#8217;un MCD (modèle conceptuel de données) lorsqu&#8217;on travaille sur une requête SQL impliquant différentes tables représente un gain de temps.Il est en effet plus rapide de jeter un coup d&#8217;oeil sur un MCD afin de repérer quels sont les champs qui lient une table à une autre plutôt que d&#8217;enchaîner les &#171;&#160;DESC ma_table&#160;&#187;, puis [...]]]></description>
			<content:encoded><![CDATA[<p>Disposer d&#8217;un MCD (modèle conceptuel de données) lorsqu&#8217;on travaille sur une requête SQL impliquant différentes tables représente un <strong id="tqz_">gain de temps</strong>.<br id="d0eo" />Il est en effet plus rapide de jeter un coup d&#8217;oeil sur un MCD afin de repérer quels sont les champs qui lient une table à une autre plutôt que d&#8217;enchaîner les &laquo;&nbsp;DESC ma_table&nbsp;&raquo;, puis repérer la clé primaire et les éventuelles clés étrangères, et rebolote sur la ou les tables de destination&#8230;<br id="ixuk" /><br id="ixuk0" />La prochaine série d&#8217;articles sur les index MySQL va nous amener à enchaîner quelques requêtes sur une des deux bases d&#8217;exemple disponibles sur le site de MySQL : <a href="http://dev.mysql.com/doc/" target="_blank">world et sakila</a>, le prétexte est donc tout trouvé pour évoquer ici la solution que j&#8217;ai retenu pour obtenir le MCD de ces tables : DBDesigner 4.</p>
<p>Cet outil n&#8217;est pas nouveau, son successeur officiel est même déjà connu, il s&#8217;agit de <a href="http://dev.mysql.com/workbench/" target="_blank">MySQL Workbench</a>. Celui-ci n&#8217;étant pas encore disponible sous linux, nous utiliserons son ancêtre et plus particulièrement sa fonctionnalité de &laquo;&nbsp;reverse engineering&nbsp;&raquo; : une fois connecté à votre base, DBDesigner 4 va générer sous forme graphique vos tables, leurs descriptions, et si tout se passe bien, les relations entre vos tables.</p>
<p><span id="more-46"></span><strong>Installation (fastidieuse) sous linux</strong><br id="m-dl0" /><br id="m-dl1" />L&#8217;installation de DBDesigner 4 sous linux (ubuntu 8.04 pour ma part) n&#8217;est pas de tout repos&#8230; J&#8217;ai rencontré toutes les erreurs potentielles croisées sur les différents tutoriels sur le sujet. Résultat des courses : il apparaît plus simple d&#8217;installer DBDesigner 4 en passant par <a href="http://www.winehq.org/" target="_blank">wine</a>, les <strong>problèmes de librairies manquantes</strong> disparaissent et les polices sont plus travaillées.<br id="swio" />Si néanmoins vous souhaitez vous passer de wine, voici quelques pistes : <a href="http://wiki.splitbrain.org/dbdesigner" target="_blank">http://wiki.splitbrain.org/dbdesigner</a> et un <a href="http://ubuntuforums.org/showthread.php?t=125911" target="_blank">how-to</a> tiré des forums ubuntu. Des solutions sont proposées pour ces fameuses libraries manquantes, cela dit j&#8217;avais toujours des problèmes malgré ces manipulations.<br id="tw3i" /><br id="tw3i0" />Une fois wine installé (<code>apt-get install wine</code>), téléchargez simplement <a href="http://www.fabforce.net/downloads.php" target="_blank">le fichier d&#8217;installation de DBDesigner 4</a> pour windows.</p>
<p>L&#8217;installation s&#8217;effectue simplement :</p>
<p><code>root@u200:/opt# wine DBDesigner4.0.5.6_Setup.exe</code> <br id="lq1_" /><br id="vkpd" />Si vous êtes sous MySQL 5, vous devez <strong>modifier le mot de passe</strong> de l&#8217;utilisateur à partir duquel vous vous connectez à DBDesigner de la façon suivante :<br id="vkpd0" /><br id="lq1_0" />mysql&gt; <code>SET PASSWORD FOR 'dbdesign'@'localhost' = OLD_PASSWORD('dbdesign');</code></p>
<p>Depuis sa version 5, MySQL utilise en effet un mécanisme d&#8217;authentification qui est incompatible avec les versions précédentes (&lt; 4.1). Afin d&#8217;assurer une rétro compatibilité, il est néanmoins possible avec MySQL 5 d&#8217;utiliser la commande &laquo;&nbsp;<a href="http://dev.mysql.com/doc/refman/5.1/en/encryption-functions.html#function_old-password" target="_blank">OLD_PASSWORD</a>&laquo;&nbsp;, le format utilisé pour le mot de passe sera alors compatible avec MySQL 4 (short hash contre long hash pour MySQL 5).<br id="fvds" /><br id="rwen" />Lancement de l&#8217;application:</p>
<p><code>root@u200:~/.wine/drive_c/Program Files/fabFORCE# wine DBDesigner4.exe</code></p>
<p><strong>Obtenir le schéma de sa base</strong></p>
<p>Pour générer votre MCD, et selon votre langue d&#8217;installation :</p>
<p>- Choisissez le menu &laquo;&nbsp;Database&nbsp;&raquo;, puis &laquo;&nbsp;Reverse Engineering&nbsp;&raquo;.</p>
<p>- Selectionnez une connexion à la base de données ou créez la vôtre : &laquo;&nbsp;Localhost 127.0.0.1&#8243; par exemple, puis renseignez vos login/pwd et validez, n&#8217;oubliez pas la commande &laquo;&nbsp;OLD_PASSWORD&nbsp;&raquo; si besoin.</p>
<p>- La fenêtre &laquo;&nbsp;Reverse Engineering&nbsp;&raquo; affiche alors la liste des tables de la base, par défaut elles sont toutes cochées. Remarquez un peu plus bas l&#8217;option &laquo;&nbsp;<a href="http://www.fabforce.net/dbdesigner4/doc/db.html#reveng" target="_blank">Build Relations</a>&laquo;&nbsp;, cochez-là puis indiquez &laquo;&nbsp;Build Relations based on primary keys&nbsp;&raquo;. C&#8217;est grâce à cette option que vous indiquez à DBDesigner de relier vos tables entre elles (si possible).</p>
<p>- Si tout s&#8217;est bien passé, l&#8217;étape suivante affiche vos tables, leurs champs, leurs clés&#8230;</p>
<p><strong>Remarques : </strong></p>
<p>- DBDesigner ne parvient pas à joindre les tables de la base &laquo;&nbsp;world&nbsp;&raquo; entre elles, les noms des clés primaires sont différents et les clés étrangères absentes de la définition des tables (MyISAM), vous pouvez néanmoins rajouter vos différentes relations à la main.</p>
<p>- En ce qui concerne la base &laquo;&nbsp;sakila&nbsp;&raquo; les relations entre les tables sont bien présentes :</p>
<p><a href="http://www.dbnewz.com/wp-content/uploads/2008/06/sakila_db_small.png"><img class="alignnone size-full wp-image-47" title="sakila_db_small" src="http://www.dbnewz.com/wp-content/uploads/2008/06/sakila_db_small.png" alt="Vue partielle de la base sakila après reverse engineering" width="400" height="304" /></a></p>
<p>Vous pouvez à présent d&#8217;un seul coup d&#8217;oeil construire vos requêtes, les jointures entre les tables sont ici évidentes.</p>
<p>- La console dans laquelle vous lancez DBDesigner va peut-être contenir quelques messages d&#8217;erreur du type:</p>
<p><code>QPixmap::operator=: Cannot assign to pixmap during painting<br id="yfb50" />QPopupMenu: (unnamed) Popup has invalid menu item</code></p>
<p>Cela n&#8217;a pas bloqué le comportement de l&#8217;application lors de mes tests cependant DBDesigner a la facheuse tendance à <strong>ouvrir des boites de dialogues sous les fenêtres déjà existantes</strong>, n&#8217;hésitez pas notamment lors de votre connexion à la base, à déplacer vos fenêtres (la principale et celle consacrée au reverse engineering) pour faire apparaître la boîte de dialogue vous invitant à renseigner vos identifiants de connexion. A noter qu&#8217;il existe là aussi des solutions pour régler ce problème de pop-up, cf les tutoriels cités en début d&#8217;article.</p>
<p>Pour explorer les autres fonctionnalités de DBDesigner 4, ou bien encore tester (sous windows uniquement pour le moment) son successeur MySQL Workbench, rendez-vous sur <a href="http://www.fabforce.net/dbdesigner4/" target="_blank">fabforce.net</a>, vous y trouverez les fichiers d&#8217;installation, des captures d&#8217;écran et de la documentation.</p>
<p>Si vous connaissez d&#8217;autres outils intéressants disposant d&#8217;un module de &laquo;&nbsp;reverse engineering&nbsp;&raquo;, partagez-les dans les commentaires, ils sont là pour ça.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.dbnewz.com/2008/06/22/dbdesigner-4-generer-son-mcd-par-reverse-engineering/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
