Affichage des articles dont le libellé est mysql. Afficher tous les articles
Affichage des articles dont le libellé est mysql. Afficher tous les articles

lundi 17 octobre 2011

Indexation et booléens

Voilà encore une raison pour laquelle il faut toujours tester vos requêtes!
En effet, il s'avère que l'optimiseur de MySQL 5.5 (vérifié aussi en 5.1.60) ne fait pas le même choix selon qu'un filtre sur la valeur d'un booléen indexé utilise l'opérateur = ou IS. Voici un exemple extrait du rapport de Bug que j'ai posté sur bugs.mysql.com :

mysql> CREATE TABLE t(id INT, b BOOLEAN DEFAULT FALSE);
Query OK, 0 rows affected (0.01 sec)
mysql> INSERT INTO t(id) SELECT 1;
Query OK, 1 row affected (0.01 sec)
Records: 1 Duplicates: 0 Warnings: 0
mysql> INSERT INTO t(id) SELECT id FROM t;
Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0
mysql> INSERT INTO t(id) SELECT id FROM t;
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> INSERT INTO t(id) SELECT id FROM t;
Query OK, 4 rows affected (0.00 sec)
Records: 4 Duplicates: 0 Warnings: 0
mysql> INSERT INTO t(id) SELECT id FROM t;
Query OK, 8 rows affected (0.00 sec)
Records: 8 Duplicates: 0 Warnings: 0
mysql> INSERT INTO t(id) SELECT id FROM t;
Query OK, 16 rows affected (0.00 sec)
Records: 16 Duplicates: 0 Warnings: 0
mysql> INSERT INTO t(id) SELECT id FROM t;
Query OK, 32 rows affected (0.00 sec)
Records: 32 Duplicates: 0 Warnings: 0
mysql> INSERT INTO t(id) SELECT id FROM t;
Query OK, 64 rows affected (0.00 sec)
Records: 64 Duplicates: 0 Warnings: 0
mysql> INSERT INTO t(id) SELECT id FROM t UNION ALL SELECT id FROM t UNION ALL SELECT id FROM t;
Query OK, 387 rows affected (0.00 sec)
Records: 387 Duplicates: 0 Warnings: 0
mysql> INSERT INTO t(id) SELECT id FROM t UNION ALL SELECT id FROM t UNION ALL SELECT id FROM t;
Query OK, 1548 rows affected (0.02 sec)
Records: 1548 Duplicates: 0 Warnings: 0
mysql> INSERT INTO t(id) SELECT id FROM t UNION ALL SELECT id FROM t UNION ALL SELECT id FROM t;
Query OK, 6192 rows affected (0.03 sec)
Records: 6192 Duplicates: 0 Warnings: 0
mysql> INSERT INTO t(id) SELECT id FROM t UNION ALL SELECT id FROM t UNION ALL SELECT id FROM t;
Query OK, 24768 rows affected (0.18 sec)
Records: 24768 Duplicates: 0 Warnings: 0
mysql> INSERT INTO t(id) SELECT id FROM t UNION ALL SELECT id FROM t UNION ALL SELECT id FROM t;
Query OK, 99072 rows affected (0.43 sec)
Records: 99072 Duplicates: 0 Warnings: 0
mysql> alter table t add index(b);
Query OK, 0 rows affected (0.22 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> INSERT INTO t values(10,TRUE);
Query OK, 1 row affected (0.00 sec)

mysql> EXPLAIN SELECT COUNT(*) FROM t WHERE b IS TRUE\G
******** 1. row ********
id: 1
select_type: SIMPLE
table: t
type: index
possible_keys: NULL
key: b
key_len: 2
ref: NULL
rows: 131783
Extra: Using where; Using index
1 row in set (0.00 sec)

mysql> EXPLAIN SELECT COUNT(*) FROM t WHERE b = TRUE\G
******** 1. row ********
id: 1
select_type: SIMPLE
table: t
type: ref
possible_keys: b
key: b
key_len: 2
ref: const
rows: 1
Extra: Using where; Using index
1 row in set (0.00 sec)

Comme vous pouvez le voir, lorsque l'on utilise l'opérateur "=" , MySQL effectue un accès unique dans l'index b, alors qu'avec l'opérateur "IS" il effectue un parcours total de l'index... Il est donc très important pour l'instant de privilégier l'opérateur d'égalité en attendant la correction du bug.
Lire la suite...

mercredi 12 octobre 2011

Sauvegarder physiquement certaines tables InnoDB

Avec les tables MyISAM, il est possible de sauvegarder directement les fichiers MYD et MYI lorsque le serveur MySQL tourne, en supposant bien sûr que vous avez verrouillé les tables concernées avec la commande LOCK TABLES ou qu'il n'y a aucune requête en cours. Cependant, il n'en est pas de même avec le moteur InnoDB. En effet, à la différence de MyISAM, il y a des threads qui continue à s'exécuter après que les modifications aient été faites et qui modifient les fichiers de données. Seules les journaux de transactions (iblogfiles) sont réellement écrits à chaque fin de transaction (je considère que vous ne jouez pas avec le paramètre innodb_flush_log_at_trx_commit). De plus, il y a un fichier spécial, nommé par défaut ibdata1, qui contient le dictionnaire de données et toutes les tables (et index) InnoDB ayant été créées avec le paramètre innodb_file_per_table désactivé).
C'est la raison pour laquelle il n'est pas possible de copier un fichier innodb d'un serveur à un autre. Il est par contre tout a fait possible de sauvegarder un fichier innodb et de le restaurer plus tard sur le même serveur ou sur un serveur hébergeant une sauvegarde physique, sans avoir à restaurer la base dans son ensemble.
Il faut savoir qu'InnoDB associe un identifiant à chaque fichier innoDB nommé tablespace (oui cela n'a rien à voir avec les tablespaces d'Oracle). Cet id est stocké dans le catalogue InnoDB ainsi que dans le fichier concerné. C'est ce qui empêche de restaurer le fichier sur un autre serveur sur lequel le même tablespace n'existe pas ou a un identifiant différent, ou sur le même serveur sur lequel on aurait fait un TRUNCATE de la table, et donc généré implicitement un nouvel identifiant !

La méthode à suivre est la suivante, détaillée dans la document MySQL :

mysql> USE test
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> CREATE TABLE t1(id int);
Query OK, 0 rows affected (0.03 sec)
mysql> SELECT @i:=0;
+-------+
| @id:=0 |
+-------+
| 0 |
+-------+
1 row in set (0.00 sec
mysql> INSERT INTO t1 SELECT @id:=@id+1 FROM information_schema.TABLES;
Query OK, 198 rows affected (0.03 sec)
Records: 198 Duplicates: 0 Warnings: 0
mysql> INSERT INTO t1 SELECT @id:=@id+1 FROM information_schema.TABLES;
Query OK, 198 rows affected (0.00 sec)
Records: 198 Duplicates: 0 Warnings: 0
mysql> INSERT INTO t1 SELECT @id:=@id+1 FROM information_schema.TABLES;
Query OK, 198 rows affected (0.00 sec)
Records: 198 Duplicates: 0 Warnings: 0
mysql> SELECT COUNT(*) FROM t1;
+----------+
| COUNT(*) |
+----------+
| 594 |
+----------+
1 row in set (0.00 sec)
mysql> SHOW ENGINE INNODB STATUS\G ... Main thread process no. 5109, id 140547993949952, state: waiting for server activity
Number of rows inserted 594, updated 0, deleted 0, read 1193
0.00 inserts/s, 0.00 updates/s, 0.00 deletes/s, 12.91 reads/s
----------------------------
END OF INNODB MONITOR OUTPUT
============================
1 row in set (0.00 sec)

Une fois que le statut du moteur InnoDB est bien à "waiting for server activity", On sauvegarde le fichier InnoDB

cp -p /var/lib/mysql/test/t1.ibd /tmp/

La sauvegarde effectuée, on simule la perte de données

mysql> DELETE FROM t1 WHERE id>400;
Query OK, 194 rows affected (0.04 sec)
mysql> SELECT MAX(id) FROM t1;
+---------+
| MAX(id) |
+---------+
| 400 |
+---------+
1 row in set (0.00 sec)

- On tente maintenant de restaurer nos données en prévenant MySQL

mysql> ALTER TABLE t1 DISCARD TABLESPACE;
Query OK, 0 rows affected (0.00 sec)

- On restaure le fichier à sa place d'origine

root@lizzie:~# cp -p /tmp/t1.ibd /var/lib/mysql/test/t1.ibd

- On avertit MySQL qu'il peut utiliser à présent le fichier

mysql> ALTER TABLE t1 IMPORT TABLESPACE;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT MAX(id) FROM t1;
+---------+
| MAX(id) |
+---------+
| 594 |
+---------+
1 row in set (0.02 sec)

Et voilà !
Par contre, comme je vous l'ai dit ça ne fonctionne plus quand l'id du tablespace a été modifié dans le catalogue :

mysql> TRUNCATE TABLE t1;
Query OK, 0 rows affected (0.01 sec)
mysql> ALTER TABLE t1 DISCARD TABLESPACE;
Query OK, 0 rows affected (0.00 sec)
root@lizzie:~# cp -p /tmp/t1.ibd /var/lib/mysql/test/t1.ibd
mysql> ALTER TABLE t1 IMPORT TABLESPACE;
ERROR 1030 (HY000): Got error -1 from storage engine

Vous trouverez un message plus explicite dans le fichier d'erreur :
grep -A 10 "InnoDB: Error" /var/lib/mysql/lizzie.err
111012 18:09:31 InnoDB: Error: tablespace id and flags in file './test/t1.ibd' are 209768 and 0, but in the InnoDB
InnoDB: data dictionary they are 209769 and 0.
InnoDB: Have you moved InnoDB .ibd files around without using the
InnoDB: commands DISCARD TABLESPACE and IMPORT TABLESPACE?
InnoDB: Please refer to
InnoDB: http://dev.mysql.com/doc/refman/5.5/en/innodb-troubleshooting-datadict.html
InnoDB: for how to resolve the issue.
111012 18:09:31 InnoDB: cannot find or open in the database directory the .ibd file of
InnoDB: table `test`.`t1`
InnoDB: in ALTER TABLE ... IMPORT TABLESPACE
Lire la suite...

dimanche 27 février 2011

Contraintes uniques et valeurs nulles

Vous risquez d'être surpris si vous utiliser une contrainte d'unicité sur un groupe de colonnes qui peuvent être nulles. En effet, il faut savoir que la contrainte d'unicité différencie par défaut les valeurs nulles. Ainsi si vous créez la table suivante :

mysql> CREATE TABLE t1(id1 int,id2 int,id3 int);
Query OK, 0 rows affected (0.00 sec)

mysql> ALTER TABLE t1 ADD UNIQUE KEY (id1,id2,id3);
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0

Vous pourrez ajouter les triplets (1,1,1), (1,2,1), (2,1,1) une seule fois. Par contre, vous pourrez ajouter autant de fois que vous le voulez les triplets (1,1,NULL), (1,NULL,1), (1,NULL,NULL), (NULL,NULL,NULL) etc...

mysql> INSERT INTO t1 VALUES(1,1,1);
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO t1 VALUES(1,2,1);
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO t1 VALUES(2,1,1);
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO t1 VALUES(2,1,1);
ERROR 1062 (23000): Duplicate entry '2-1-1' for key 'id1'

mysql> INSERT INTO t1 VALUES(1,1,NULL);
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO t1 VALUES(1,1,NULL);
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO t1 VALUES(1,NULL,NULL);
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO t1 VALUES(1,NULL,NULL);
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO t1 VALUES(NULL,NULL,NULL);
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO t1 VALUES(NULL,NULL,NULL);
Query OK, 1 row affected (0.00 sec)

Apparemment, MySQL respecte le SQL 2003 qui spécifie que les contraintes d'unicité ne s'appliquent que sur les valeurs non nulles, ce qui ne semble pas très intuitif quand on choisi ce type de contrainte. Oracle ne prend pas en compte cette considération et vérfie l'unicité sur l'ensemble des valeurs, ce que nombre de personnes auraient certainement voulu retrouver chez MySQL...

Pour plus d'information, vous pouvez lire le bug report 25544

lundi 18 octobre 2010

Mais où est mysqld_safe ?

Vous l'aurez peut être remarqué, mais dans la distribution Lucid d'Ubuntu,  mysqld_safe n'est plus présent.
Pour rappel, mysqld_safe est un script fourni avec MySQL pour lancer mysqld, le monitorer et le relancer s'il vient à mourir. C'est pourquoi lorsque mysqld_safe tourne, si vous arrêtez mysqld il est automatiquement relancé.
Cependant, il a disparu depuis la version mysqld 5.1.37 fournie dans la Lucid (la version actuelle étant la 5.1.41). Ceci ne veut cependant pas dire que le démon mysqld n'est plus monitoré afin d'être redémarré au cas où. En fait, c'est upstart qui est utilisé pour effectuer cette tâche.
Upstart , qui est un remplaçant du système sysvinit, s'occupe de démarrer et gérer les services au démarrage, ainsi que durant l'activité du système Linux. Des évènements sont déclenchés à l'arrêt ou démarrage de tâches et services et peuvent être captés par d'autres processus afin de déclencher des opérations.

Vous saurez maintenant qu'il n'y a pas à s'inquiéter sur un système Ubuntu où vous ne voyez pas de processus mysqld_safe tourne !

jeudi 24 juin 2010

Un livre MySQL à acquérir

Après 6 bons mois de rédactions, d'échanges de mails et de relectures, je vous annonce la sortie d'un nouveau livre sur MySQL 5 en français :
MySQL5, Administration et optimisation

Il reprend et explique tous les points propres à l'administration (configuration, mise à jour, sauvegardes/restaurations, maintenance, sécurité, ..) et à l'optimisation (nouvelles fonctionnalités, systèmes de caches, indexation, tuning, ..) en rendant abordables des concepts complexes.

En attendant de vous le procurer, vous pouvez consulter la TDM_MySQL5_Admin_Optim et un Extrait_MySQL5_Admin_Optim consacré aux verrous et transactions.

Inutile de vous dire que le livre est disponible dans toutes les bonnes librairies informatiques (FNAC, Amazon, ...). Pensez donc à vous le procurer pour l'étudier pendant vos vacances !

lundi 3 mai 2010

MySQL Cluster impose des limites aux méta-données

Lorsque vous mettez en place une configuration MySQL Cluster, ayez à l'esprit que celui-ci impose par défaut des limites aux méta-données. Vous ne pourrez donc pas créer autant de tables, d'index, de colonnes que vous le désirez sans modifier sa configuration. Il est possible de le faire plus tard, mais cela nécessitera d'effectuer un rolling restart (un redémarrage de l'ensemble des composants du cluster).
Voici les quelques paramètres qu'il faudra modifier selon les besoins de votre cluster (les valeurs par défaut sont indiquées entre parenthèses) :

- MaxNoOfAttributes fixe le nombre maximum de colonnes pouvant être créées au total dans l'ensemble des tables stockées (1000)
- MaxNoOfOrderedIndexes fixe le nombre maximum d'index ordonnés (128)
- MaxNoOfUniqueHashIndexes, comme le précédent mais pour les index uniques (64)
- MaxNoOfTables fixe le nombre maximum de tables (128)

Vous pourrez donc modifier la section [NDBD DEFAULT] de votre fichier de configuration ndb_mgmd.cnf et y ajouter la configuration suivante par exemple :

MaxNoOfAttributes=10000
MaxNoOfOrderedIndexes=3000
MaxNoOfUniqueHashIndexes=1500
MaxNoOfTables=1000

Pour plus d'information, vous pouvez visiter la documentation en ligne à l'adresse http://dev.mysql.com/doc/mysql-cluster-excerpt/5.1/en/mysql-cluster-mgm-definition.html

Vous ne pourrez pas dire que vous n'avez pas été prévenu :)

vendredi 26 mars 2010

2 bases exemple pour MySQL

Sur le site de MySQL vous pouvez télécharger les bases sakila et world afin de vous familiariser avec le SGBD.
Pour installer ces 2 bases sur votre serveur sous Ubuntu, suivez la procédure suivante :

sudo wget -c http://downloads.mysql.com/docs/sakila-db.tar.gz
sudo tar Ozvxf sakila-db.tar.gz sakila-db/sakila-schema.sql|sudo mysql --defaults-file=/etc/mysql/debian.cnf
sudo tar Ozvxf sakila-db.tar.gz sakila-db/sakila-data.sql|sudo mysql --defaults-file=/etc/mysql/debian.cnf sakila

sudo wget http://downloads.mysql.com/docs/world.sql.gz
sudo mysql --defaults-file=/etc/mysql/debian.cnf -e 'CREATE DATABASE world'
sudo zcat world.sql.gz|sudo mysql --defaults-file=/etc/mysql/debian.cnf world

Voila vos 2 bases sont créées et prêtes à être utilisées :

sudo mysql --defaults-file=/etc/mysql/debian.cnf
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 46
Server version: 5.1.37-1ubuntu5.1 (Ubuntu)

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> SELECT count(*) TABLES, table_schema,
-> concat(round(sum(table_rows)/1000000,2),'M') rows,
-> concat(round(sum(data_length)/(1024*1024),2),'M') DATA,
-> concat(round(sum(index_length)/(1024*1024),2),'M') idx,
-> concat(round(sum(data_length+index_length)/(1024*1024),2),'M') total_size,
-> round(sum(index_length)/sum(data_length),2) idxfrac
-> FROM information_schema.TABLES
-> WHERE table_schema IN ('sakila','world')
-> GROUP BY table_schema;
+--------+--------------------+-------+--------+-------+------------+---------+
| TABLES | table_schema | rows | DATA | idx | total_size | idxfrac |
+--------+--------------------+-------+--------+-------+------------+---------+
| 23 | sakila | 0.05M | 4.10M | 2.52M | 6.62M | 0.62 |
| 3 | world | 0.01M | 0.36M | 0.07M | 0.43M | 0.19 |
+--------+--------------------+-------+--------+-------+------------+---------+
2 rows in set (0,03 sec)

mysql> exit
Bye

Pour en savoir plus sur ces 2 bases vous pouvez vous rendre sur le site web de MySQL aux adresses http://dev.mysql.com/doc/sakila/en/sakila.html et http://dev.mysql.com/doc/world-setup/en/world-setup.html

A vous de jouer !

lundi 4 janvier 2010

Nouvelles fonctionnalités dans le partitionnement de MySQL 5.5

Etant donné que cela fait un long moment que je n'ai pas bloggé je vais tenter de me rattrapper un peu :)

Je me suis penché sur l'une des nouvelles fonctionnalités de MySQL 5.5 concernant le partitionnement multi-colonnes en mode RANGE sur des types qui ne sont plus limités à l'entier.

En effet, il est possible de partitionner une table t1 sur 2 colonnes comme suit :

CREATE TABLE t1 (
valeur TINYINT UNSIGNED NOT NULL,
quand DATE NOT NULL,
libelle varchar(120)
)
PARTITION BY RANGE COLUMNS(valeur,quand) (
PARTITION p0 VALUES LESS THAN (10,'2006-10-02'),
PARTITION p1 VALUES LESS THAN (10,'2008-04-12'),
PARTITION p2 VALUES LESS THAN (100,MAXVALUE),
PARTITION p3 VALUES LESS THAN (MAXVALUE,MAXVALUE)
);

Cependant l'algorithme qui répartit les données sur les différentes partitions créées n'est pas si intuitif que cela. Ainsi, j'imaginais dans un premier temps que l'enregistrement (100,'2005-10-02') ne pouvait se retrouver dans la partition p2 car 100 n'est pas inférieur à 100 ! En effet, je pensais que l'opérator LESS THAN sur un couple sous entendait que pour qu'un enregistrement (valeur,quand,libelle) appartienne à la partition p0 il fallait que valeur<10 et quand<'2006-10-02'.
Or ce n'est pas le cas, la preuve :

mysql> SELECT IF(10<10,'TRUE','FALSE'),IF('2005-10-02'<'2006-10-02','TRUE','FALSE'),IF((10,'2005-10-02')<(10,'2006-10-02'),'TRUE','FALSE')\G
*************************** 1. row ***************************
                              IF(10<10,'TRUE','FALSE'): FALSE
          IF('2005-10-02'<'2006-10-02','TRUE','FALSE'): TRUE
IF((10,'2005-10-02')<(10,'2006-10-02'),'TRUE','FALSE'): TRUE

Il faut dire qu'entre la documentation officielle qui a été corrigée (BUG 49875) suite à mon premier BUG 49861, et l'article de Giuseppe Maxia qui affirmait que si toutes les premières valeurs des listes de colonnes assignées aux partitions étaient différentes alors le partitionnement était identique au partitionnement sur cette seule colonne (corrigé depuis) j'ai un peu perdu la tête...

Cependant, cette histoire a bien fait de débuter puisqu'elle a débouché sur la correction de la documentation officielle, la correction d'un article avancé sur les nouveautés de la 5.5 concernant le partitionnement et sur la remise en cause des mots clés LESS THAN dans ce type de partitionnement avec une proposition de remplacement par NO GREATER THAN ou RANGE BOUNDED BY.

Nous verrons bien ce qu'il se passera dans les semaines à venir.

mardi 12 mai 2009

Index et valeurs nulles

Une différence importante entre MySQL et Oracle est l'indexation. En effet, Oracle n'indexe pas les données entièrement nulles. Par entièrement cela signifie que si vous indexez 2 colonnes, le couple null ne sera pas stocké dans l'index et cela a son importance !

Par exemple, si nous utilisons le schema SCOTT pour tenter d'utiliser un index sur une colonne pouvant être nulle :

SQL> select * from emp;

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
7369 SMITH CLERK 7902 17-DEC-80 800 20
7499 ALLEN SALESMAN 7698 20-FEB-81 1600 300 30
7521 WARD SALESMAN 7698 22-FEB-81 1250 500 30
7566 JONES MANAGER 7839 02-APR-81 2975 20
7654 MARTIN SALESMAN 7698 28-SEP-81 1250 1400 30
7698 BLAKE MANAGER 7839 01-MAY-81 2850 30
7782 CLARK MANAGER 7839 09-JUN-81 2450 10
7788 SCOTT ANALYST 7566 19-APR-87 3000 20
7839 KING PRESIDENT 17-NOV-81 5000 10
7844 TURNER SALESMAN 7698 08-SEP-81 1500 0 30
7876 ADAMS CLERK 7788 23-MAY-87 1100 20
7900 JAMES CLERK 7698 03-DEC-81 950 30
7902 FORD ANALYST 7566 03-DEC-81 3000 20
7934 MILLER CLERK 7782 23-JAN-82 1300 10

14 rows selected.

SQL> create index idx_emp_ename on emp(ename);

Index created.

SQL> set autotrace trace explain
SQL> select 1 from emp where ename is null;

Execution Plan
----------------------------------------------------------
Plan hash value: 3956160932

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 7 | 3 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| EMP | 1 | 7 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter("ENAME" IS NULL)


Oracle décide donc d'effectuer un FULL SCAN de la table car la donnée nulle ne pouvant être stockée dans un index il est nécessaire de parcourir la table entièrement. Ce qui n'est pas le cas si on index une colonne supplémentaire non nulle (le couple ne sera dans ce cas jamais nul)

SQL> create index idx_emp_ename_1 on emp(ename,1);

Index created.

SQL> select 1 from emp where ename is null;

Execution Plan
----------------------------------------------------------
Plan hash value: 2365361045

------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 7 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IDX_EMP_ENAME_1 | 1 | 7 | 1 (0)| 00:00:01 |
------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("ENAME" IS NULL)


On peut donc en déduire l'importance de rajouter la contrainte not null quand vous savez que ce champ ne peut être null. En effet, un count(*) pourra dans ce cas effectuer un INDEX FULL SCAN sachant qu'aucune donnée ne peut être nulle et donc il n'y a aucune entrée qui manque dans l'index.

Contrairement à Oracle, MySQL stocke aussi les valeurs nulles dans ses index comme l'indique la colonne NULL dans la sortie de la commande "SHOW INDEX FROM MATABLE". Ainsi si l'on recherche le nombre d'entrée nulles d'une table, MySQL utilisera l'index disponible :

mysql [localhost] {msandbox} (test) > explain select count(*) from t3 where id is null;
+----+-------------+-------+------+---------------+------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+-------+------+--------------------------+
| 1 | SIMPLE | t3 | ref | id | id | 5 | const | 100 | Using where; Using index |
+----+-------------+-------+------+---------------+------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)

mysql [localhost] {msandbox} (test) > show index from t3;
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| t3 | 1 | id | 1 | id | A | 1 | NULL | NULL | YES | BTREE | |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
1 row in set (0.00 sec)

De la même manière il est important d'ajouter la contrainte NOT NULL si le champ ne sera jamais nul, ce qui permet à MySQL d'effectuer certaines optimisations et d'économiser un bit par enregistrement.
Blogged with the Flock Browser
Lire la suite...

mardi 28 avril 2009

MySQL Conference 2009

La 7ème conférence MySQL co-présentée par SUN, MySQL et Oreilly a eu lieu du 20 avril au 23 avril 2009 à Santa Clara. Pour les heureux participants ils ont eu droit à un ensemble assez impressionnant de sessions. Bien sûr, impossible de toutes les suivre, puisqu'un grand nombre d'entre elles étaient dispensées en parallèle. Cependant, nous avons la chance de pouvoir accéder aux slides de certaines dont les auteurs ont eu l'amabilité de les mettre à disposition sur mysqlconf.

Bonne lecture.
Blogged with the Flock Browser

lundi 20 avril 2009

Oracle rachète SUN

En janvier 2008, SUN rachetait MySQL AB pour 1 milliard de $. Eh bien, aujourd'hui Oracle a annoncé sur  son site avoir accepté de racheter SUN pour 7 milliards de dollars. Il va falloir à nouveau s'interroger sur les conséquences, bonnes comme mauvaises, que cela pourra avoir pour les utilisateurs de MySQL.
Blogged with the Flock Browser

mardi 24 mars 2009

Vive le strip binaire !

Je pense que nous sommes nombreux à utiliser MySQL Sandbox qui est un outil très utile pour pouvoir par exemple tester rapidement une version particulière de MySQL, installer plusieurs versions sur le même serveur, configurer une réplication multi-slaves en quelques commandes etc...
Cependant, cela nécessite de récupérer une archive binaire pour chaque version que vous voulez utiliser, ce qui peut consommer rapidement pas mal d'espace !

Par exemple, j'ai récupéré la version 5.0.67 qui, sous forme d'archive, occupe 113Mo et une fois décompressée près de 398Mo ! Il était donc temps que je prenne quelques initiatives :-)

du -sh mysql-5.0.67-linux-i686.tar.gz
113M mysql-5.0.67-linux-i686.tar.gz

tar xzf mysql-5.0.67-linux-i686.tar.gz
du -sh mysql-5.0.67-linux-i686
398M mysql-5.0.67-linux-i686

Je commence par supprimer les répertoires qui me seront inutiles

rm -rf docs/ sql-bench/ mysql-test/ tests/ man/

Ceci me permet d'économiser 45Mo. Ensuite je strippe les binaires et les bibliothèques

strip bin/* lib/* 2>/dev/null

Et encore 242 Mo d'économisé !

Enfin, je supprime les binaires qui me seront inutiles

rm -f *debug* *test* *ndb*

Cette dernière étape me récupère encore 20Mo. A la fin de cette série d'actions, l'archive décompressée n'occupe plus que 68 Mo soit près de 6 fois moins que la taille de départ.

La question que vous devez vous poser est la suivante : que fait la commande strip ?

elle supprime tous les symbols et numéros de lignes des binaires et bibliothèques, qui peuvent être utiliseés lorsque vous debuggez vos programmes, ce qui n'est pas mon cas.
Blogged with the Flock Browser
Lire la suite...

jeudi 12 mars 2009

Défaillance du Query Cache de MySQL

Il arrive que le Query Cache ne fonctionne pas :-(

J'ai par exemple était confronté à un problème concernant le Query Cache lorsque j'ai voulu optimiser certaines requêtes effectuées sur un serveur. En fait, l'optimiseur de MySQL, en version 4.1.11, ne faisait pas le bon choix d'index selon certains cas, et scinder la requête en 2 sous requêtes unifiées ensuite permettait d'obtenir de meilleurs performances. Attention, ce n'est pas toujours le cas !
En exécutant plusieurs fois les 2 requêtes unitairement le Query Cache fonctionnait correctement comme vous pouvez le voir au temps d'exécution qui était > 2 s.

mysql> SELECT idancetre,max(idmess) mxd FROM messages USE INDEX (idx_tst3) WHERE idancetre not in (1021576,0) AND avalider=0 AND idmess>1021576 GROUP BY idancetre ORDER BY mxd DESC LIMIT 5;
+-----------+---------+
| idancetre | mxd |
+-----------+---------+
| 1993983 | 1995071 |
| 1994149 | 1995069 |
| 1995061 | 1995061 |
| 1994957 | 1995059 |
| 1994250 | 1995058 |
+-----------+---------+
5 rows in set (0.00 sec)


mysql> SELECT idmess,idmess FROM messages WHERE idancetre=0 AND avalider=0 AND idmess>1021576 GROUP BY idmess ORDER BY idmess DESC LIMIT 5;
+---------+---------+
| idmess | idmess |
+---------+---------+
| 1169825 | 1169825 |
| 1169813 | 1169813 |
| 1169795 | 1169795 |
| 1169758 | 1169758 |
| 1169756 | 1169756 |
+---------+---------+
5 rows in set (0.00 sec)


Par contre, la requête UNION composée des 2 requêtes précédentes n'était jamais stockée dans le Query Cache :

mysql> (SELECT idancetre,max(idmess) mxd FROM messages USE INDEX (idx_tst3) WHERE idancetre not in (1021576,0) AND avalider=0 AND idmess>1021576 GROUP BY idancetre ORDER BY mxd DESC LIMIT 5) UNION (SELECT idmess,idmess FROM messages WHERE idancetre=0 AND avalider=0 AND idmess>1021576 GROUP BY idmess ORDER BY idmess DESC LIMIT 5);
+-----------+---------+
| idancetre | mxd |
+-----------+---------+
| 1993983 | 1995071 |
| 1994149 | 1995069 |
| 1995061 | 1995061 |
| 1994957 | 1995059 |
| 1994250 | 1995058 |
| 1169825 | 1169825 |
| 1169813 | 1169813 |
| 1169795 | 1169795 |
| 1169758 | 1169758 |
| 1169756 | 1169756 |
+-----------+---------+
10 rows in set (2.55 sec)


Vous le comprendrez bien, dans ce cas là, il est préférable d'exécuter les 2 requêtes de façon distincte et d'effectuer l'UNION côté applicatif plutôt qu'en une seule et même requête. En fait, dans cette version c'était dû à un BUG référencé chez MySQL et corrigé en 4.1.17, 5.0.17 et 5.1.4

SELECT queries that began with an opening parenthesis were not being placed in the query cache. (Bug#14652)


En 5.0.67, qui contient le bugfix, ce n'est pas la même chose :

mysql> (SELECT idancetre,max(idmess) mxd FROM messages USE INDEX (idx_tst3) WHERE idancetre not in (1021576,0) AND avalider=0 AND idmess>1021576 GROUP BY idancetre ORDER BY mxd DESC LIMIT 5) UNION ALL (SELECT idmess,idmess FROM messages WHERE idancetre=0 AND avalider=0 AND idmess>1021576 GROUP BY idmess ORDER BY idmess DESC LIMIT 5);
+-----------+---------+
| idancetre | mxd |
+-----------+---------+
| 1993983 | 1995071 |
| 1994149 | 1995069 |
| 1995061 | 1995061 |
| 1994957 | 1995059 |
| 1994250 | 1995058 |
| 1169825 | 1169825 |
| 1169813 | 1169813 |
| 1169795 | 1169795 |
| 1169758 | 1169758 |
| 1169756 | 1169756 |
+-----------+---------+
10 rows in set (0.00 sec)


Il est donc très important de vérifier les changement apportés par les versions mineures qui suivent la version que vous utilisez pour savoir quels bugs ont été corrigés, et lesquels peuvent vous concerner.

Blogged with the Flock Browser
Lire la suite...

jeudi 5 mars 2009

Calcul de la médiane en MySQL

Comme vous avez pu le lire dans mon article précédent où je donne la méthode de calcul de la médiane en SQL cette formule fonctionne correctement mais peut prendre un temps extrêmement longs lorsque le nombre de lignes à étudier augmente.
Ainsi dans cette article j'obtenais le résultat au bout de 5 secondes pour une table qui contenait 4874 lignes. J'ai eu à faire le test pour une table contenant plus de 600 000 lignes et là, après 2 heures de calculs, je n'ai plus eu la patience d'attendre le résultat.
J'ai donc cherché sur le net et trouvé un projet qui fourni une fonction median en UDF. Cependant, elle ne fonctionne pas pour les versions de MySQL > 4.1.0. Je me suis donc intéressé au code et après quelques minutes j'ai pu le faire tourner sur ma version 5.0.32 !
Miracle! En utilisant cette fonction ma requête prend moins d'1 seconde !
Finalement, j'ai bien fait de me pencher vers l'utilisation d'une UDF (user-defined function)

Pour ceux qui désirent utiliser cette méthode, il suffit de suivre la procédure suivante :

cd /tmp
wget http://sites.google.com/site/filesrepository01/Home/udf_median.cc -O udf_median.cc
apt-get install libmysqlclient15-dev
gcc -Wall -I /usr/include/mysql -c udf_median.cc -o udf_median.o
ld -shared -o udf_median.so udf_median.o
cp udf_median.so /usr/lib
rm -f udf_median.*


Il ne vous reste plus qu'à vous connecter à votre serveur MySQL et à créer la fonction median :

mysql test
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 5
Server version: 5.0.32-Debian_7etch8 Debian etch distribution

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

mysql> DROP FUNCTION median;
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE AGGREGATE FUNCTION median RETURNS REAL SONAME 'udf_median.so';Query OK, 0 rows affected (0.00 sec)

mysql> SELECT semaine,MEDIAN(duree) FROM t1 GROUP BY semaine;
+---------+---------------+
| semaine | MEDIAN(duree) |
+---------+---------------+
| 1 | 11.50 |
| 2 | 22.00 |
| 3 | 13.00 |
+---------+---------------+
3 rows in set (0.84 sec)

C'est donc plus rapide que nos 5 secondes de mon dernier article et sur une table qui est 125 fois plus volumineuse. Autres chiffres, sur une table de 10000 enregistrements composée de 4 colonnes (int, varchar(255),int,varchar(20)), la 1ère méthode (pur SQL) obtient le résultat en 2 min 55.24 sec, tandis que cette méthode prend 10ms.
Blogged with the Flock Browser

Lire la suite...

mardi 30 décembre 2008

MySQL Cluster 6.4

La version 6.4 du moteur de stockage NDB utilisé par MySQL Cluster pointe son nez et nous offre les nouvelles fonctionnalités suivantes :

- Possibilité d'ajouter des noeuds et des groupes de noeuds online, c'est à dire sans recréer totalement le cluster et donc d'avoir un arrêt de service non négligeable
- Support du multithreading au niveau des datanodes
- Base NDB$INFO permettant d'avoir accès aux informations du cluster
- Disponibilité sous Windows

Pour les fonctionnalités apportées par chaque version 6.x vous pouvez vous rendre sur la roadmap du développement de MySQL Cluster. Les versions 6.x étaient disponibles en GPL ou en version commercial dans l'édition Carrier Grade de MySQL Cluster qui maintenant est obsolète et devient tout simplement MySQL Cluster. Toutes les sources (GPL/mysql et Commerciales/mysql-com) sont disponibles ici.

Concernant les performances, le blog de Jonas Oreland, Leader de l'équipe d'ingénieur MySQL (Cluster) depuis Août 2003, annonce des résultats très probants, avec par exemple 950 000 lectures/seconde sur un seul datanode grâce au multithreading du processus ndbd, dont le nouveau binaire est ndbmt, et jusqu'à 600 000 écritures/seconde. Et selon Mikael Ronstrom, Architecte logiciel sénior chez MySQL AB (SUN), on peut donc imaginer atteindre des dizaines de millions de lectures/seconde en multipliant le nombre de noeuds de données.

Ce sont donc de très bonnes nouvelles pour les heureux utilisateurs de MySQL Cluster et d'après Jonas on peut s'attendre à avoir une beta-release pour l'année 2009. Il faudra donc encore être patient mais on devrait pouvoir en profiter bientôt.

Enfin, pour ceux qui utilisent le moteur 6.3 je vous conseille de passer au moins à la version 6.3.19, si vous en avez la possibilité, car elle apporte une modification dans la création des redos en ne flushant sur disque qu'après la création du fichier et non plus tous les 32k blocs, ce qui ralentissait énormément le temps d'initialisation d'un noeud. Ainsi le blog de Shinguz a pu diviser le temps d'initialisation d'un noeud par 8.
Blogged with the Flock Browser

mercredi 17 décembre 2008

Calculer une durée en MySQL

Il est très simple avec PostgreSQL de calculer une différence entre deux dates avec simplement une commande du genre :

SELECT TIMESTAMP '2008-12-17 09:17:31' - TIMESTAMP '2008-12-15 11:28:06';


Mais qu'en est il avec MySQL ?

Eh bien ce n'est pas aussi simple ! En effet, MySQL dispose d'un tas de fonctions mais il ne surcharge pas l'opérateur de soustraction si les arguments sont des dates, à moins que vous utilisiez la syntaxe date-INTERVAL X UNIT
Il faut donc jongler avec la fonction TIMESTAMPDIFF pour obtenir le même résultat qu'avec Postgresql et ne pas rencontrer des problèmes avec les heures d'hiver/d'été.

Vous utiliserez donc la requête suivante :

SELECT @s:= '2008-12-15 11:28:06',@e:='2008-12-17 09:17:31',CONCAT(IF((@days:=TIMESTAMPDIFF(DAY,@s,@e))>0,CONCAT(@days,IF(@days>1,' days ',' day ')),''),SEC_TO_TIME(TIMESTAMPDIFF(SECOND,@s,@e)-86400*@days)) duree;

Vous remarquerez que j'utilise des variables utilisateurs afin de ne pas alourdir la requête en répétant les dates de début et de fin.
Blogged with the Flock Browser

mardi 9 décembre 2008

Utilisation de LAST_INSERT_ID()

La fonction MySQL LAST_INSERT_ID() permet de récupérer la valeur du dernier incrément généré lors de l'insertion de données dans une table contenant une colonne auto_increment.
Il peut être utile de récupérer cette valeur pour la réinjecter dans une seconde table. J'ai par exemple sur mon serveur les 2 tables suivantes :

mysql [localhost] {msandbox} (test) > select * from t1;
+----+------+
| id | name |
+----+------+
| 1 | c |
| 2 | c2 |
| 3 | c3 |
+----+------+
3 rows in set (0.00 sec)

mysql [localhost] {msandbox} (test) > select * from t2;
+------+------------+
| id | dt |
+------+------------+
| 2 | 2008-12-09 |
+------+------------+
1 row in set (0.00 sec)

Ayant eu l'occasion de vérifier le code de certains développeurs j'ai repéré du code utilisant cette fonction mais faisant appel à la table en question sans pour autant que cela soit nécessaire pour le résultat. Je me suis alors posé la question suivante : Y a t il un impact à faire appel à la table sans que cela soit nécessaire ?
Si on regarde les 2 plans d'exécution cela donne :

mysql [localhost] {msandbox} (test) > explain select last_insert_id();
+----+-------------+-------+------+---------------+------+---------+------+------+----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+----------------+
| 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used |
+----+-------------+-------+------+---------------+------+---------+------+------+----------------+
1 row in set (0.00 sec)

mysql [localhost] {msandbox} (test) > explain select last_insert_id() from t2;
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| 1 | SIMPLE | t2 | ALL | NULL | NULL | NULL | NULL | 1 | |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
1 row in set (0.00 sec)


L'appel à la table semble avoir besoin d'accéder à une ligne. Cela semble louche...

Vous l'aurez compris il faut bien sûr bencher pour savoir s'il y a une grosse différence. Pour cela MySQL met à disposition la fonction BENCHMARK() qui n'est pas très connue :

mysql [localhost] {msandbox} (test) > insert into t1(name) values('c4');
Query OK, 1 row affected (0.01 sec)

mysql [localhost] {msandbox} (test) > SELECT BENCHMARK(1000000,(SELECT last_insert_id() from t2));
+------------------------------------------------------+
| BENCHMARK(1000000,(SELECT last_insert_id() from t2)) |
+------------------------------------------------------+
| 0 |
+------------------------------------------------------+
1 row in set (5.09 sec)

mysql [localhost] {msandbox} (test) > SELECT BENCHMARK(1000000,(SELECT last_insert_id()));
+----------------------------------------------+
| BENCHMARK(1000000,(SELECT last_insert_id())) |
+----------------------------------------------+
| 0 |
+----------------------------------------------+
1 row in set (0.03 sec)


Voilà donc notre réponse. Accéder à la table, chose inutile pour notre résultat, prend plus de temps.

lundi 8 décembre 2008

Calcul de la médiane en SQL

Depuis Oracle 10g il est possible d'effectuer le calcul de la médiane très facilement en utilisant la nouvelle fonction MEDIAN disponible.
Je suppose que vous disposez d'une table data constituée de 2 colonnes semaine et duree. Pour calculer la médiane sous Oracle vous n'avez plus qu'à utiliser la requête suivante :

SELECT semaine,MEDIAN(duree) FROM data GROUP BY semaine;

Pour MySQL c'est beaucoup plus compliqué puisqu'on ne dispose pas de cette magnifique fonction. J'ai donc fait quelques recherches et je suis tombé sur les travaux de frédéric Brouard. J'ai pu trouver la procédure adéquate basée sur la solution de Chris Date.

En supposant que vous disposez toujours de la table data qui chez moi utilise le moteur CSV, la méthode est la suivante :

CREATE TABLE d AS SELECT * FROM data;
ALTER TABLE d ADD COLUMN id INT AUTO_INCREMENT PRIMARY KEY FIRST, ADD INDEX (semaine,duree) ;

SELECT semaine,round(AVG(duree),2) AS MEDIANE
FROM (
   SELECT semaine,MIN(duree) AS duree
   -- valeurs au dessus
   FROM
   (
       SELECT ST1.semaine,ST1.id, ST1.duree
       FROM    d AS ST1
       INNER JOIN d AS ST2
       ON ST1.duree <= ST2.duree AND ST1.semaine=ST2.semaine
       GROUP BY ST1.id, ST1.duree
       HAVING COUNT(*) <= (SELECT CEILING(COUNT(*) / 2.0)
       FROM d d2 where d2.semaine=ST1.semaine)
   ) SUR
   GROUP BY semaine
   UNION
   SELECT semaine,MAX(duree) AS duree
   -- valeurs en dessous
   FROM
   (
       SELECT ST1.semaine, ST1.duree
       FROM    d AS ST1
       INNER JOIN d AS ST2
       ON ST1.duree >= ST2.duree AND ST1.semaine=ST2.semaine
       GROUP BY ST1.id, ST1.duree
       HAVING COUNT(*) <= (SELECT CEILING(COUNT(*) / 2.0)
       FROM d d2 where d2.semaine=ST1.semaine)
   ) SOU
   GROUP BY semaine
   ) T
GROUP BY semaine;

DROP TABLE d;

Sous Oracle, la requête est très rapide et il n'est pas nécessaire de créer un index particulier. Par contre sous MySQL, si vous vous amusez à supprimer l'index vous le ressentirez très vite si vous disposez de beaucoup de données. Dans mon cas, ma table contient 4874 lignes. Sans l'index, la requête dure près de 5 minutes contre moins de 5 secondes une fois indexée.
Lire la suite...

vendredi 28 novembre 2008

Bienvenue à MySQL 5.1 GA

Si vous surfez régulièrement sur la blogosphère MySQL, vous avez pu voir que la release MySQL 5.1 est enfin sortie. Elle était prévue pour le 6 décembre mais finalement elle est sortie 10 jours plus tôt. Ainsi, la version 5.1.30  est la première GA (Generally Available) release. Remercions aussi l'équipe Debian MySQL Maintainers qui a déjà sorti le paquet debian pour l'architecture amd64 ce qui démontre une certaine réactivité.

Cependant, il ne faut pas perdre à l'esprit que la version 5.1.31 commence déjà à montrer son nez pour corriger certains bugs.

Enfin pour un petit récapitulatif des nouvelles fonctionnalités de cette version n'hésitez pas à lire l'article de Jay Pipes sur le sujet "MySQL 5.1 Article Recap" ou a lire la section "What's New in MySQL 5.1" de la documentation MySQL.
Blogged with the Flock Browser

vendredi 21 novembre 2008

Utilisation du moteur Blackhole de MySQL

Le moteur Blackhole, comme son nom peut le sous-entendre, ne stocke aucune information. Tous les ordres DML sont effectués mais les données ne sont pas enregistrées.

Voici quelques cas où il devient intéressant d'utiliser ce moteur :
* Estimer la taille de vos binlogs générer en simulant un nombre hypothétique de requêtes par secondes sur votre serveur avec vos tables utilisant le moteur Blackhole
* Utiliser une table Blackhole pour générer des ordres DML sur d'autres tables sans avoir besoin de stocker d'information
* Peut jouer le rôle de distributeur de binary logs pour soulager la charger i/o d'un maître

Nous allons étudier la deuxième proposition qui consiste à utiliser une sorte de table de routage pour nos ordres DML.

Création d'une table Blackhole :

CREATE TABLE `mes_logs` (
`program` enum('MySQL','Apache','Autre',) DEFAULT NULL,
`msg` varchar(1000) DEFAULT NULL
) ENGINE=BLACKHOLE;


Création d'un trigger :

DELIMITER ;;
CREATE TRIGGER `redirect_logs` BEFORE INSERT ON `mes_logs` FOR EACH ROW BEGIN

IF ucase(NEW.program)='MYSQL' THEN
INSERT INTO mysql_logs VALUES(NEW.msg);

ELSEIF ucase(NEW.program)='APACHE' THEN
INSERT INTO apache_logs VALUES(NEW.msg);

ELSE
INSERT INTO autre_logs VALUES(NEW.msg);

END IF;
END */;;
DELIMITER ;


A partir de là, toute insertion dans la table mes_logs déclenchera le trigger redirect_logs qui mettra à jour la table correspondant au type de log tandis que les données ne seront pas stockées dans la table mes_logs. Ceci permet d'effectuer le routage des données de logs au niveau MySQL et non applicatif, dans le but par exemple d'archiver les tables, de les partitionner (et de dépasser la limite de 1024 sous partitions pour une table), de les vider sans influer sur les autres types de logs et tout en étant transparent pour les utilisateurs. Il sera aussi possible de modifier le trigger pour modifier ce routage.
Blogged with the Flock Browser