Archive for the ‘Astuce’ Category

29
août

Duplicate key causé par un SELECT

   Posted by: freshdaz Tags: ,

Parmi les erreurs courantes en MySQL, ont trouve la fameuse ERROR 1062 (23000): Duplicate entry ’2984′ for key ‘PRIMARY’ causée par la tentative d’insertion d’une données, déjà présente, dans un colonne ayant une contrainte d’unicité.

Dans la même famille il y a aussi l’erreur 1022…

Une banale requête de lecture avec un GROUP BY:

mysql> SELECT DISTINCT(name), COUNT(name) FROM membre WHERE name <>  » GROUP BY name
ERROR 1022 (23000): Can’t write; duplicate key in table ‘/tmp/#sql_29lm_0′
 

C’est assez surprenant de trouver une telle erreur sur une requête aussi triviale !

En regardant le plan d’exécution, on remarque que outre le fait qu’ un index est plus que recommandé, MySQL créé une table temporaire (/tmp/#sql_29lm_0) et c’est en insérant les données dans cette dernière que le « duplicate key » se déclenche.

 
mysql> explain  SELECT DISTINCT(name), COUNT(name) FROM membre WHERE name <>  » GROUP BY name\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: membre
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 92633696
Extra: Using where; Using temporary; Using filesort
 
Alors ça à tout l’air d’un bug, d’ailleurs 3 sont ouverts à ce sujet, pour les version 5.1 et 5.5 (le 1er depuis le 9 Nov 2010…):

http://bugs.mysql.com/bug.php?id=58081
http://bugs.mysql.com/bug.php?id=60808
http://bugs.mysql.com/bug.php?id=62097

Le contournement que j’ai trouvé à été d’augmenter pour la session, les caches utilisés par la requêtes, à savoir le max_heap_table_size et le tmp_table_size:

mysql> SET SESSION max_heap_table_size=536870912; SET SESSION tmp_table_size=536870912;
mysql> SELECT DISTINCT(name), COUNT(name) FROM membre WHERE name <>  » GROUP BY name
+—————+————-+
| name          | COUNT(name) |
+—————+————-+
|     Abano     |           3 |
 

Cela dit, ça serait quand même pas mal, qu’ Oracle fasse le nécessaire pour enfin corriger ce bug…

1
août

MariaDB 5.3, Progress reporting

   Posted by: freshdaz

Parmi les nouveautés de MariaDB 5.3, l’une d’entre elle, bien pratique est le « progress reporting », qui permet de savoir où le serveur en est dans l’exécution d’une commande.

Le principe est simple, j’exécute une commande et MariaDB m’indique le pourcentage effectué pour une étape donnée. Un exemple:

MariaDB-5.3> ALTER TABLE client ENGINE=InnoDB;
Stage: 1 of 2 'copy to tmp table'   44.5% of stage done

MariaDB-5.3> ALTER TABLE client ENGINE=InnoDB;
Stage: 2 of 2 'Enabling keys'      0% of stage done    

MariaDB-5.3> ALTER TABLE client ENGINE=InnoDB;
Query OK, 3000000 rows affected (5 min 15.24 sec)      
Records: 3000000  Duplicates: 0  Warnings: 0

Les commandes implémentant le « progress reporting » sont pour le moment: ALTER TABLE, ADD INDEX, DROP INDEX, LOAD DATA INFILE.

Le moteur Aria supporte également les commandes suivantes: CHECK TABLE, REPAIR TABLE, ANALYZE TABLE, OPTIMIZE TABLE

MariaDB-5.3> > CHECK TABLE aria_table;
Stage: 3 of 3 'Checking data'      0% of stage done

Plus d’informations sur AskMonty.org.

Je suis amené à réaliser régulièrement des audits de serveurs MySQL.Voici le premier volet d’une série d’articles où je vais essayer de vous donner quelques points clés pour mieux comprendre le fonctionnement de MySQL.

La configuration du serveur est un des points que je regarde, et l’une des erreurs les plus courantes concerne le paramétrage des options tmp_table_size et max_heap_table_size.

tmp_table_size permet de fixer la taille maximale au-delà de laquelle les tables temporaires en mémoire créées par MySQL (avec le moteur Memory) se transforment en table MyISAM en migrant les données sur le disque. max_heap_table_size permet de fixer la taille maximale des tables avec pour moteur de stockage Memory (Heap est l’ancien nom de Memory).

Ce qui est important de savoir c’est que la limite des tables temporaires en mémoire créées par MySQL est la plus petite de ces deux valeurs. Ce que je vois souvent dans les fichier de configurations, c’est un tmp_table_size à 64 Mo (par exemple) et un max_heap_table_size qui lui n’y figure pas et qui par conséquent prend ça valeur par défaut, c’est à dire 16Mo. Votre table temporaire créée par le serveur (lors de votre GROUP BY par exemple) sera sur disque dès 16Mo de données et non 64 Mo comme vous le pensiez.
Alors pourquoi faut il limiter le nombre de tables temporaires créées sur le disque ? Tous simplement car sur disque, le temps d’exécution de la requête sera beaucoup, beaucoup plus long, de l’ordre de fois 10 voir fois 100 !
Lorsque vous renseignez l’option tmp_table_size, pensez également à donner la même valeur à l’option max_heap_table_size.
13
août

!include

   Posted by: freshdaz Tags: , ,

my.cnf, comme vous le savez certainement est le nom du fichier de configuration de MySQL. Vous pouvez également le retrouver sous le nom de my.ini en environnement MS Windows. Il permet comme son nom l’indique de configurer votre serveur MySQL en y centralisant les options de … configuration. Le but ici n’est pas de détailler la longue liste des paramètres de MySQL, la documentation officielle est (presque) bien faite, mais de mettre le « focus » sur la directive !include (avec un « ! » devant).

Cette dernière permet d’inclure un fichier de configuration dans un autre (sic). Autant je suis un adèpte de la non duplication du code en dévellopement (des require_once en PHP utilisés avec modération ça vous simplifie bien votre code), autant je ne suis pas un grand fan pour éclater la configuration de mon serveur MySQL en plusiseurs endroits. Cependant :) il faut avouer que parfois cela peut servir !

Alors comment ça marche ?

C’est (apparemment) très simple. Mon client mysql va chercher sa configuration dans différents endroits (définit à la compilation):

daz@daz-laptop:~$ mysql –help
Default options are read from the following files in the given order:
/etc/my.cnf /etc/mysql/my.cnf /usr/local/mysql/etc/my.cnf ~/.my.cnf


Mon fichier de configuration se nomme  /etc/mysql/my.cnf et il contient simplement la directive !include suivit du « vraie » fichier de configuration:

daz@daz-laptop:~$ cat /etc/mysql/my.cnf
!include /home/daz/sandboxes/msb_5_1_35/my.sandbox.cnf


Et au final dans mon fichier de configuration (au passage j’utilise le très pratique soft développé par Giuseppe Maxia : MySQL Sandbox):

daz@daz-laptop:~$ cat /home/daz/sandboxes/msb_5_1_35/my.sandbox.cnf
[mysql]
prompt=’mysql [\h] {\u} (\d) > ‘
[client]
port = 5135
socket = /tmp/mysql_sandbox5135.sock


Simple non ?

C’est (presque) tout, et ça marche…pas :(

daz@daz-laptop:~$ mysql
ERROR 2002 (HY000): Can’t connect to local MySQL server through socket ‘/tmp/mysql.sock’ (2)


Une petite vérification s’impose:

daz@daz-laptop:~$ mysql –help
mysql  Ver 14.14 Distrib 5.1.35, for pc-linux-gnu (i686) using readline 5.1
port                              0
socket
(No default value)


Les paramètres ne sont pas prit en compte. (commentaire de l’expert: ça ne peut donc pas fonctionner)



(30 minutes et quelques insultes plus tard…)


En fait, la subtilité,  c’est de rajouter un saut à la ligne, à la fin de la commande !include /home/daz/sandboxes/msb_5_1_35/my.sandbox.cnf

daz@daz-laptop:~$ mysql –help
mysql  Ver 14.14 Distrib 5.1.35, for pc-linux-gnu (i686) using readline 5.1
port 5135
socket /tmp/mysql_sandbox5135.sock


Vérification (au cas où):

daz@daz-laptop:~$ mysql

Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 5
Server version: 5.1.35-log MySQL Community Server (GPL)
Type ‘help;’ or ‘\h’ for help. Type ‘\c’ to clear the current input statement.
mysql [localhost] {daz} ((none)) >


(commentaire de l’expert: ça fonctionne !)

:)

Giuseppe Maxia

MySQL possède un grand nombre de fonctions pour gérer les dates et le temps. Il est possible de récupérer le numéro calendaire d’une semaine avec la fonction week. Dimanche 5 avril nous étions semaine 14. La fonction week nous le confirme:

mysql> SELECT week('2009-04-05');
+--------------------+
| week('2009-04-05') |
+--------------------+
|                 14 |
+--------------------+

Lundi 6 avril, nouvelle semaine, on passe donc à la semaine 15:

mysql> SELECT week('2009-04-06');
+--------------------+
| week('2009-04-06') |
+--------------------+
|                 14 |
+--------------------+

Souci !!! Le résultat n’est pas celui espéré (14 au lieu de 15). En fait la fonction week prend un deuxième paramètre optionnel, qui permet de « régler la précision » (compter sur 53 ou 54 semaines, commencer la semaine un dimanche ou un lundi…) bref elle est complète et complexe…

Le réglage usuel est de mettre le deuxième paramètre à 3 ! ou alors d’utiliser la fonction WEEKOFYEAR.

mysql> SELECT weekofyear('2009-04-05');
+--------------------------+
| weekofyear('2009-04-05') |
+--------------------------+
|                       14 |
+--------------------------+
mysql> SELECT weekofyear('2009-04-06');
+--------------------------+
| weekofyear('2009-04-06') |
+--------------------------+
|                       15 |
+--------------------------+

Il est pafois utile de désactiver le log binaire lors d’une restauration. MySQL permet de le désactiver pour une session avec la commande SQL_LOG_BIN:

mysql> SET SESSION sql_log_bin = 0;

Lors de la restauration avec le client mysql on peut donc utiliser la ligne de commande suivante:

shell> mysql –execute=« SET SESSION sql_log_bin=0;  SOURCE mon_fichier_dump.sql; »

En cas de perte du mot de passe root surtout si c’est votre seul compte (super) administrateur, vous vous trouvez dans une situation pour le moins embarrassante.
MySQL propose un moyen de s’en sortir. Certes, si le mot de passe est perdu vous ne pourrai pas le récupérer, car il est stocké haché dans la base:

mysql> SELECT user, password FROM mysql.user;
 +---------+-------------------------------------------+
 | user    | password                                  |
 +---------+-------------------------------------------+
 | root    | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
 +---------+-------------------------------------------+
 1 rows in set (0.39 sec)

Néanmoins il est possible de le changer. Voici les étapes à suivre:

1/ Arrêter le serveur MySQL

Cela ne devrait pas poser trop de problèmes:
mysql stop (sous linux)
NET STOP MySQL (sous windows)

ou dans le pire des cas, débranchez la machine :)

2/ Démarrer le serveur en désactivant la vérifications des droits

shell> mysqld --skip-grant-tables

Il important de noter qu’une fois démarré avec skip-grant-table, n’importe qui peut se connecter au serveur MySQL et avec tout les droits… Inutile de préciser que le serveur est à ce moment particulièrement vulnérable.

3/ Connexion au serveur MySQL

Connectez vous au serveur mysql, comme à l’accoutumée.

shell> mysql --user=UtilisateurExistantPas --password=MotDePasse
Welcome to the MySQL monitor.  Commands end with ; or \g.
 Your MySQL connection id is 2
 Server version: 5.1.30-community-log MySQL Community Server (GPL)
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql>

4/ Protéger le serveur

La première chose à faire, une fois connecté est de sécuriser le serveur en remettant en place la vérification des droits:

mysql> FLUSH PRIVILEGES;
shell> mysql --user=UtilisateurExistantPas --password=MotDePasse
 ERROR 1045 (28000): Access denied for user 'UtilisateurExistantPas'@'localhost'
(using password: YES)

5/ Changer le mot de passe

L’heure est enfin venue de se créer un nouveau mot de passe root

mysql> SET PASSWORD FOR root@localhost=PASSWORD('m0T2pA55e');
 Query OK, 0 rows affected (0.06 sec)

6/ Arrêter le serveur

Pour sortir de la configuration skip-grant-tables, il faut arrêter le serveur, pour mieux le redémarrer…

7/ Redémarrer le serveur normalement

et le tour est joué :)

shell> mysql --user=root --password=m0T2pA55e
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 5.1.30-community-log MySQL Community Server (GPL)

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql>