Améliorer Les Performances (MySQL 5.1)
Améliorer Les Performances (MySQL 5.1)
Améliorer Les Performances (MySQL 5.1)
1)
Forums Tutoriels Magazine FAQs Blogs Chat Newsletter Études Emploi Rechercher
Club Contacts
Accueil ALM Java .NET Dév. Web EDI Programmation SGBD Office Solutions d'entreprise Applications Mobiles
Systèmes SGBD 4D Access Big Data DB2 Firebird InterBase MySQL NoSQL PostgreSQL Oracle Sybase SQL-Server
ACCUEIL MYSQL FORUM MYSQL F.A.Q MYSQL SOURCES MYSQL TUTORIELS MYSQL OUTILS MYSQL LIVRES MYSQL
Faisons tout d'abord un rappel sur le partitionnement d'un disque dur. L'intérêt de
créer différentes partitions est de pouvoir organiser les données : des partitions sont
réservées aux fichiers des systèmes d'exploitation installés et d'autres pour les
données personnelles (photos, téléchargements…).
https://krierjon.developpez.com/mysql/partitionnement/#LIII 1/25
19/12/2018 Améliorer les performances(MySQL 5.1)
Dans cet article, nous allons nous intéresser au partitionnement dans le domaine
des bases de données, et plus exactement au partitionnement de tables. Le but est,
comme pour les disques durs, d'organiser les données. Néanmoins, nous n'allons
pas utiliser cette organisation pour simplifier nos requêtes, mais bien pour en
améliorer les performances !
deux partitions :
Exemple : nous disposons d'une table Amis contenant les prénom et photo de
chacun de nos amis. Les photos prenant de la place et étant rarement accédées,
nous décidons de les écarter des autres données.
Comme vous pouvez le remarquer, les deux partitions contiennent l'identifiant des
amis. Cela est nécessaire afin de garder le lien entre les données de chaque
enregistrement. Ainsi, pour récupérer toutes les informations des amis, il suffit de
faire une jointure entre les deux partitions.
Nous connaissons maintenant les deux types de partitionnement. Il est bien sûr
possible d'utiliser un partitionnement vertical et un partitionnement horizontal sur
une même table, ainsi que de partitionner sur plusieurs niveaux, c'est-à-dire définir
des partitions de partitions.
https://krierjon.developpez.com/mysql/partitionnement/#LIII 2/25
19/12/2018 Améliorer les performances(MySQL 5.1)
Le partitionnement apporte plusieurs avantages à un administrateur de base de
données. Voici les principaux intérêts du partitionnement :
pouvoir créer des tables plus grandes que la taille permise par un disque dur
ou par une partition du système de fichiers : il est tout à fait possible de
stocker des partitions à des endroits (partitions, disques, serveurs…)
différents ;
pouvoir supprimer très rapidement des données qui ne sont plus utiles et
utilisées : si ces données sont placées sur une partition séparée, il suffit de
détruire la partition pour supprimer toutes les données ;
optimiser grandement certaines requêtes : les données étant organisées dans
différentes partitions, le SGBD n'accède qu'aux données nécessaires lors des
requêtes. Sans partitionnement, tous les enregistrements sont pris en compte.
Enfin, notons que le partitionnement de tables est généralement utilisé avec des
bases de données réparties où les données sont placées sur des sites géographiques
(éloignés ou proches) différents. Cet article se limite au partitionnement sur site
local, mais les principes évoqués sont valides pour le partitionnement distant.
Sélectionnez
(1, 'Dupond', 'Benard', 'Administratif', 3100) --> Partition 3
(2, 'Smith', 'John', 'Technique', 1400) --> Partition 1
(3, 'Lucaza', 'Sophie', 'RHC', 1800) --> Partition 2
Sélectionnez
(1, 'Dupond', 'Benard', 'Administratif', 3100) --> Partition 1
(2, 'Smith', 'John', 'Technique', 1400) --> Partition 2
(3, 'Lucaza', 'Sophie', 'RHC', 1800) --> Partition 1
https://krierjon.developpez.com/mysql/partitionnement/#LIII 3/25
19/12/2018 Améliorer les performances(MySQL 5.1)
Actuellement, MySQL 5.1.11 ne supporte que les listes de valeurs de nombres
entiers. Pour implémenter notre exemple, il sera donc nécessaire de créer une table
« Service » et assigner un identifiant numérique à chaque service.
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
CREATE TABLE transac
(
id INT UNSIGNED PRIMARY KEY,
montant INT UNSIGNED NOT NULL,
jour DATE NOT NULL,
codePays ENUM('FR', 'BE', 'UK', 'US', 'CA', 'JP') NOT NULL
);
Chaque transaction est identifiée par un numéro unique (id). De plus, pour chaque
transaction, nous stockons le montant, la date ainsi que le code du pays dans lequel
la transaction a eu lieu.
Dans la suite de l'article, nous allons analyser plusieurs méthodes pour optimiser les
requêtes. Mais afin de réaliser nos tests dans de bonnes conditions, nous allons
avoir besoin de remplir notre table de transactions. Nous n'allons bien sûr pas
insérer plusieurs millions d'enregistrements à la main. La procédure suivante
permettra de générer et d'ajouter autant d'enregistrements que nous le désirerons :
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
DELIMITER //
CREATE PROCEDURE remplir_transaction(nbTransacs INT)
BEGIN
DECLARE i INT DEFAULT 1;
DECLARE nbAlea DOUBLE;
https://krierjon.developpez.com/mysql/partitionnement/#LIII 4/25
19/12/2018 Améliorer les performances(MySQL 5.1)
DECLARE _jour DATE;
DECLARE _montant INT UNSIGNED;
DECLARE _codePays TINYINT UNSIGNED;
Vous pouvez exécuter le code précédent, mais ne lancez pas encore la procédure.
Nous le ferons en temps voulu. Avant tout, il nous faut créer nos partitions.
La première question qui doit vous venir à l'esprit est : « Qu'est-ce qu'un bon
partitionnement ? » La réponse est simple : un bon partitionnement est un
partitionnement adapté aux requêtes fréquentes et lourdes en exécution.
Après avoir listé les requêtes à optimiser, il faut les analyser. Le but est de
déterminer les données qui sont accédées indépendamment et ainsi répartir ces
données dans des partitions différentes. Par exemple, si des enregistrements sont
souvent accédés par mois (statistiques mensuelles), il peut être judicieux de créer
une partition pour chaque mois enregistré.
Dans les exemples qui suivent, nous allons voir des partitionnements efficaces et
d'autres complètement inutiles et donc à éviter.
Sélectionnez
1.
2.
3.
SELECT SUM(montant) FROM transac WHERE jour BETWEEN '2003-01-01' AND '2003-03-31';
SELECT * FROM transac WHERE jour BETWEEN '2005-01-01' AND '2005-01-31' LIMIT x, y;
Vous remarquez rapidement que les transactions sont accédées par leur date. Ainsi,
vous décidez de répartir les millions de transactions par leur année. Vous choisissez
donc un partitionnement par intervalles, où un intervalle de 12 mois est assigné à
chaque partition.
Mettons maintenant en place la même table de transactions, mais avec une partition
par intervalles.
Sélectionnez
https://krierjon.developpez.com/mysql/partitionnement/#LIII 5/25
19/12/2018 Améliorer les performances(MySQL 5.1)
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
CREATE TABLE transac_part
(
id INT UNSIGNED NOT NULL,
montant INT UNSIGNED NOT NULL,
jour DATE NOT NULL,
codePays ENUM('FR', 'BE', 'UK', 'US', 'CA', 'JP') NOT NULL
) PARTITION BY RANGE(YEAR(jour))
(
PARTITION p1 VALUES LESS THAN(1997),
PARTITION p2 VALUES LESS THAN(1998),
PARTITION p3 VALUES LESS THAN(1999),
PARTITION p4 VALUES LESS THAN(2000),
PARTITION p5 VALUES LESS THAN(2001),
PARTITION p6 VALUES LESS THAN(2002),
PARTITION p7 VALUES LESS THAN(2003),
PARTITION p8 VALUES LESS THAN(2004),
PARTITION p9 VALUES LESS THAN(2005),
PARTITION p10 VALUES LESS THAN(2006),
PARTITION p11 VALUES LESS THAN MAXVALUE
);
Vous pouvez remarquer la syntaxe utilisée pour créer les partitions. Tout d'abord
nous signalons que nous voulons un partitionnement par intervalles en fonction de
l'année de la colonne jour, et cela grâce à PARTITION BY RANGE(YEAR(jour)).
Ensuite, nous définissons chaque partition avec le mot clé PARTITION. Comme vous
le voyez, 11 partitions ont été créées. Pour chacune d'entre elles, nous donnons la
valeur maximale qui peut prendre l'année de la colonne jour, et cela grâce à
l'opérateur LESS THAN(). Ainsi, la partition p1 comportera toutes les transactions où
l'année est inférieure à 1997 (exclus). Ensuite, la partition p2 regroupera les
transactions de l'année 1997, etc.
La dernière partition est un peu spéciale. Grâce au mot clé MAXVALUE, nous
signalons au serveur MySQL que l'année du champ jour pourra prendre toute valeur
inférieure à la valeur maximum autorisée pour un champ de type DATE. Ainsi, si
notre table n'est pas modifiée en 2007, les transactions de cette année seront
placées dans la partition p11.
Si nous avions mis LESS THAN (2007) au lieu de LESS THAN MAXVALUE, l'insertion
d'une transaction datée de 2007 aurait provoqué une erreur !
Dès que vous avez créé les deux tables de transactions, exécutez la procédure de
remplissage. Les tests qui suivent ont été réalisés avec 10 millions
d'enregistrements. Cependant, la génération et l'insertion de ceux-ci peuvent
prendre plusieurs dizaines de minutes. Vous pouvez diminuer ce nombre
d'enregistrements si vous le souhaitez, grâce au paramètre d'entrée de la
procédure. Mais gardez à l'esprit que plus il y a d'enregistrements, plus les tests de
performance seront pertinents.
Sélectionnez
1.
2.
3.
4.
5.
mysql> CALL remplir_transaction(10000000);
Query OK, 1 row affected (10 min 41.63 sec)
https://krierjon.developpez.com/mysql/partitionnement/#LIII 6/25
19/12/2018 Améliorer les performances(MySQL 5.1)
Si tout s'est bien passé, nos deux tables sont maintenant remplies avec les mêmes
enregistrements. Nous pouvons donc commencer nos tests de comparaison.
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
mysql> SELECT SUM(montant), AVG(montant) FROM transac WHERE jour BETWEEN '1998-01-01' A
+--------------+--------------+
| SUM(montant) | AVG(montant) |
+--------------+--------------+
| 2064947969 | 2275.0362 |
+--------------+--------------+
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
mysql> SELECT SUM(montant), AVG(montant) FROM transac_part WHERE jour BETWEEN '1998-01-
+--------------+--------------+
| SUM(montant) | AVG(montant) |
+--------------+--------------+
| 2064947969 | 2275.0362 |
+--------------+--------------+
Cela commence plutôt bien ! Le gain réalisé est de 19,76 secondes, soit une
optimisation de 87 %. Je vous laisse faire le calcul si des requêtes similaires sont
exécutées des centaines de fois par jour.
Comment expliquer ce gain ? C'est très simple. Dans le premier cas, sans
partitionnement, tous les enregistrements de table sont analysés par le SGBD. Dans
l'autre, avec partitionnement, le SGBD sait qu'il n'a besoin que d'analyser la
partition p3, qui contient tous les enregistrements de 1998.
Pour nous assurer des partitions scannées pour une requête, MySQL a introduit la
commande EXPLAIN PARTITIONS. Voici ce qu'elle retourne pour la requête
précédente :
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
mysql> EXPLAIN PARTITIONS SELECT SUM(montant), AVG(montant) FROM transac_part
-> WHERE jour BETWEEN '1998-01-01' AND '1998-12-31'\G
https://krierjon.developpez.com/mysql/partitionnement/#LIII 7/25
19/12/2018 Améliorer les performances(MySQL 5.1)
partitions: p3
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 907655
Extra: Using where
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
mysql> SELECT SUM(montant), AVG(montant) FROM transac WHERE jour BETWEEN '1996-01-01' A
+--------------+--------------+
| SUM(montant) | AVG(montant) |
+--------------+--------------+
| 10354975423 | 2275.8358 |
+--------------+--------------+
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
mysql> SELECT SUM(montant), AVG(montant) FROM transac_part WHERE jour BETWEEN '1996-01-
+--------------+--------------+
| SUM(montant) | AVG(montant) |
+--------------+--------------+
| 10354975423 | 2275.8358 |
+--------------+--------------+
Jusqu'à présent, nous avons vu que lorsqu'une partition n'a pas besoin d'être
scannée, nous obtenons un gain de performance. Mais que se passe-t-il si toutes les
partitions sont scannées ? En d'autres termes, nous pouvons nous demander si le
partitionnement réduit grandement la vitesse pour les autres requêtes qui ne
peuvent pas profiter de cette optimisation. Pour tester cela, calculons le montant
total et le montant moyen de toutes les transactions.
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
mysql> SELECT SUM(montant), AVG(montant) FROM transac;
+--------------+--------------+
| SUM(montant) | AVG(montant) |
+--------------+--------------+
| 49997440867 | 4999.7441 |
+--------------+--------------+
https://krierjon.developpez.com/mysql/partitionnement/#LIII 8/25
19/12/2018 Améliorer les performances(MySQL 5.1)
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
mysql> SELECT SUM(montant), AVG(montant) FROM transac_part;
+--------------+--------------+
| SUM(montant) | AVG(montant) |
+--------------+--------------+
| 49997440867 | 4999.7441 |
+--------------+--------------+
Comme prévu, cette requête est ralentie par le partitionnement. Cependant, si cette
requête est exécutée moins souvent que les autres requêtes que nous avons
optimisées, cette perte de performance devient négligeable !
Revenons à notre situation en entreprise. Après avoir réalisé tous ces tests, vous
êtes convaincu que ce partitionnement sera fortement bénéfique. Vous décidez donc
de le mettre en place. Bien sûr, vous n'avez pas trop envie de recréer une nouvelle
table partitionnée puis de copier le contenu de l'ancienne table vers la nouvelle.
Rassurez-vous, MySQL a tout prévu pour partitionner une table existante.
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
ALTER TABLE transac PARTITION BY RANGE(YEAR(jour))
(
PARTITION p1 VALUES LESS THAN(1997),
PARTITION p2 VALUES LESS THAN(1998),
PARTITION p3 VALUES LESS THAN(1999),
PARTITION p4 VALUES LESS THAN(2000),
PARTITION p5 VALUES LESS THAN(2001),
PARTITION p6 VALUES LESS THAN(2002),
PARTITION p7 VALUES LESS THAN(2003),
PARTITION p8 VALUES LESS THAN(2004),
PARTITION p9 VALUES LESS THAN(2005),
PARTITION p10 VALUES LESS THAN(2006),
PARTITION p11 VALUES LESS THAN MAXVALUE
)
Nous venons de voir l'un des principaux avantages du partitionnement, qui est
l'optimisation de requêtes. Mais si vous vous souvenez bien, un autre avantage du
partitionnement est de pouvoir se débarrasser rapidement de données devenues
inutiles. Imaginons que votre entreprise décide que l'historique des transactions de
https://krierjon.developpez.com/mysql/partitionnement/#LIII 9/25
19/12/2018 Améliorer les performances(MySQL 5.1)
1996 peut être supprimé, car les historiques de 10 ans n'intéressent plus les
employés. La première méthode pour les supprimer est bien évidemment un simple
DELETE FROM :
Sélectionnez
1.
2.
mysql> DELETE FROM transac WHERE jour BETWEEN '1996-01-01' AND '1996-12-31';
Query OK, 911306 rows affected (5.88 sec)
Sélectionnez
1.
2.
mysql> ALTER TABLE transac DROP PARTITION p1;
Query OK, 0 rows affected (0.42 sec)
Maintenant, imaginons que nos données sur les transactions soient plutôt accédées
par emplacement géographique. Par exemple, certains employés sont responsables
des transactions réalisées en Europe, d'autres gèrent les transactions en Asie et
enfin, les derniers sont responsables des transactions d'Amérique du Nord.
Rappelons-nous tout d'abord comment est stocké le code du pays dans lequel une
transaction a eu lieu :
Sélectionnez
1.
codePays ENUM('FR', 'BE', 'UK', 'US', 'CA', 'JP') NOT NULL
Créons maintenant nos partitions avec MySQL. Mais avant tout, faisons le ménage
dans ce que nous avons créé précédemment :
Sélectionnez
1.
2.
TRUNCATE TABLE transac;
DROP TABLE transac_part;
Id code
1 BE
https://krierjon.developpez.com/mysql/partitionnement/#LIII 10/25
19/12/2018 Améliorer les performances(MySQL 5.1)
2 FR
3 UK
4 US
5 CA
6 JP
Voyons maintenant le code pour créer la table de transactions partitionnée par listes
de pays :
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
CREATE TABLE transac_part
(
id INT UNSIGNED NOT NULL,
montant INT UNSIGNED NOT NULL,
jour DATE NOT NULL,
codePays TINYINT UNSIGNED NOT NULL
) PARTITION BY LIST(codePays)
(
PARTITION pEurope VALUES IN (1, 2, 3),
PARTITION pAmeriqueNord VALUES IN (4, 5),
PARTITION pAsie VALUES IN (6)
);
Rien de bien difficile comme vous pouvez le constater. Nous utilisons PARTITION BY
LIST(x) où x est une expression de type entier, ici la valeur de la colonne codePays.
Ensuite, pour chaque partition, nous définissions une liste de valeurs grâce à
l'opérateur VALUES IN. Toutes les valeurs d'une liste sont séparées par une virgule.
Pour pouvoir réaliser nos tests, remplissons à nouveau nos tables. Comme tout à
l'heure, j'ai choisi d'insérer 10 millions d'enregistrements.
Sélectionnez
1.
2.
3.
4.
5.
mysql> CALL remplir_transaction(10000000);
Query OK, 1 row affected (11 min 27.53 sec)
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
mysql> SELECT SUM(montant), AVG(montant) FROM transac WHERE codePays IN ('BE', 'FR', 'U
+--------------+--------------+
| SUM(montant) | AVG(montant) |
+--------------+--------------+
| 12494719959 | 2499.8364 |
https://krierjon.developpez.com/mysql/partitionnement/#LIII 11/25
19/12/2018 Améliorer les performances(MySQL 5.1)
+--------------+--------------+
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
mysql> SELECT SUM(montant), AVG(montant) FROM transac_part WHERE codePays IN (1, 2, 3);
+--------------+--------------+
| SUM(montant) | AVG(montant) |
+--------------+--------------+
| 12494719959 | 2499.8364 |
+--------------+--------------+
Un gain de 4 secondes : ce n'est pas si mal ! Si cette requête est souvent exécutée
sur un serveur, notre partitionnement par listes se montre assez efficace.
Refaisons la même requête, mais pour les transactions en Asie, qui dans notre cas
sont limitées au Japon.
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
mysql> SELECT SUM(montant), AVG(montant) FROM transac WHERE codePays = 'JP';
+--------------+--------------+
| SUM(montant) | AVG(montant) |
+--------------+--------------+
| 15268321752 | 9166.1114 |
+--------------+--------------+
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
mysql> SELECT SUM(montant), AVG(montant) FROM transac_part WHERE codePays = 6;
+--------------+--------------+
| SUM(montant) | AVG(montant) |
+--------------+--------------+
| 15268321752 | 9166.1114 |
+--------------+--------------+
Pour cette requête, le nombre d'enregistrements à analyser est beaucoup plus petit
que précédemment avec les pays européens. Notre partitionnement est donc encore
plus efficace dans ce cas.
Supposons maintenant que notre entreprise se soit implantée sur le marché italien.
Nous devons donc modifier notre partitionnement par listes :
Sélectionnez
1.
2.
3.
4.
5.
https://krierjon.developpez.com/mysql/partitionnement/#LIII 12/25
19/12/2018 Améliorer les performances(MySQL 5.1)
INSERT INTO pays VALUES (7, 'IT');
Comme vous pouvez le constater, il n'aurait pas été aussi simple de modifier le
partitionnement si nous avions utilisé des intervalles. Il aurait fallu renuméroter les
codes de pays pour avoir un intervalle valide de 1 à 4.
Sélectionnez
1.
2.
3.
mysql> INSERT INTO transac_part VALUES (10000001, 15, '2005-11-25', 7);
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
DROP TABLE transac_part;
La syntaxe est beaucoup plus simple comme vous pouvez le remarquer. Mais nous
allons voir si les requêtes sont toujours aussi optimisées. Exécutons donc à nouveau
une des requêtes précédentes.
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
mysql> SELECT SUM(montant), AVG(montant) FROM transac WHERE jour BETWEEN '1998-01-01' A
+--------------+--------------+
| SUM(montant) | AVG(montant) |
+--------------+--------------+
| 2068877605 | 2275.1570 |
https://krierjon.developpez.com/mysql/partitionnement/#LIII 13/25
19/12/2018 Améliorer les performances(MySQL 5.1)
+--------------+--------------+
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
mysql> SELECT SUM(montant), AVG(montant) FROM transac_part WHERE jour BETWEEN '1998-01-
+--------------+--------------+
| SUM(montant) | AVG(montant) |
+--------------+--------------+
| 2068877605 | 2275.1570 |
+--------------+--------------+
Voici un exemple d'optimisation complètement ratée ! Mais grâce à cela, nous allons
pouvoir comprendre un peu mieux le partitionnement par hachage. Toute d'abord,
demandons à MySQL le schéma d'exécution de notre requête en utilisant la
commande EXPLAIN PARTITIONS.
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
mysql> EXPLAIN PARTITIONS SELECT SUM(montant), AVG(montant) FROM transac_part
-> WHERE jour BETWEEN '1998-01-01' AND '1998-12-31'\G
Les onze partitions sont scannées pour notre requête, ce qui explique le mauvais
temps d'exécution. MySQL n'est donc pas capable d'optimiser notre requête comme
il le faisait si bien avec un partitionnement par intervalles. De même, si nous
réécrivons la clause where sous la forme YEAR(jour) = 1998, cela ne fonctionnera
pas non plus.
Néanmoins, ne croyez pas que le partitionnement par hachage soit inutile avec
MySQL. Avec des valeurs auto-incrémentées par exemple, ce type de
partitionnement permet de répartir de manière équitable les enregistrements dans
les différentes partitions. Ceci permettra dans un futur proche à MySQL de réaliser
une parallélisation de requêtes pour les systèmes disposant de plusieurs CPU. Mais à
l'heure actuelle, le partitionnement par hachage peut quand même se montrer
intéressant. Voyons comment grâce à l'exemple suivant.
Imaginons de nouveau que les employés de notre entreprise accèdent aux données
principalement en fonction de leur emplacement géographique. Nous pouvons, pour
accélérer les requêtes, partitionner en fonction du pays.
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
https://krierjon.developpez.com/mysql/partitionnement/#LIII 14/25
19/12/2018 Améliorer les performances(MySQL 5.1)
10.
11.
DROP TABLE transac_part;
Avec ce code, nous avons créé 6 partitions. Si nous supposons que 6 codes pays
soient valides, chaque partition contiendra les transactions d'un seul pays. S'il y a
plus de 6 codes, une partition pourra contenir des transactions de pays différents.
Dans notre exemple, il y a 6 pays différents. Donc chaque partition sera utilisée et
réservée pour un seul pays. Testons dès à présent une requête :
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
mysql> SELECT SUM(montant), AVG(montant) FROM transac WHERE codePays = 1;
+--------------+--------------+
| sum(montant) | avg(montant) |
+--------------+--------------+
| 1388016754 | 833.2588 |
+--------------+--------------+
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
mysql> SELECT SUM(montant), AVG(montant) FROM transac_part WHERE codePays = 1;
+--------------+--------------+
| sum(montant) | avg(montant) |
+--------------+--------------+
| 1388016754 | 833.2588 |
+--------------+--------------+
Et maintenant, si nous voulons les transactions d'un continent : par exemple les
codes pays 1, 2 et 3 :
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
mysql> SELECT SUM(montant), AVG(montant) FROM transac WHERE codePays IN (1, 2, 3);
+--------------+--------------+
| SUM(montant) | AVG(montant) |
+--------------+--------------+
| 12504285818 | 2500.8422 |
+--------------+--------------+
https://krierjon.developpez.com/mysql/partitionnement/#LIII 15/25
19/12/2018 Améliorer les performances(MySQL 5.1)
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
mysql> SELECT SUM(montant), AVG(montant) FROM transac_part WHERE codePays IN (1, 2, 3);
+--------------+--------------+
| SUM(montant) | AVG(montant) |
+--------------+--------------+
| 12504285818 | 2500.8422 |
+--------------+--------------+
Tout comme pour le partitionnement par hachage, le nombre de partitions désiré est
spécifié grâce à PARTITIONS nb. L'intérêt du partitionnement par clé est, selon moi,
très limité pour l'instant, mais sera fort intéressant lorsque la parallélisation des
requêtes sera permise par MySQL.
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
CREATE TABLE transac_part
(
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
montant INT UNSIGNED NOT NULL,
jour DATE NOT NULL,
codePays ENUM('FR', 'BE', 'UK', 'US', 'CA', 'JP') NOT NULL
) PARTITION BY KEY() PARTITIONS 5;
Nous n'allons pas réaliser de tests de requêtes sur cette table. Cela serait
complètement inutile : l'index de la clé primaire nous permet déjà un accès très
rapide sans partitionnement. Par contre, il serait intéressant de réaliser quelques
tests quand la parallélisation de requêtes sera possible. Personnellement, je ne
disposerai sûrement pas du matériel nécessaire pour ces tests, je compte donc sur
vous :-)
https://krierjon.developpez.com/mysql/partitionnement/#LIII 16/25
19/12/2018 Améliorer les performances(MySQL 5.1)
partitionnement sur plusieurs niveaux est appelé partitionnement composite, ou
encore sous-partitionnement. Avec MySQL, il est possible de faire un
partitionnement composite, mais seulement sur 2 niveaux. De plus, le
partitionnement de second niveau ne peut être qu'un partitionnement par clé ou par
hachage.
Une solution de partitionnement serait de répartir les transactions selon leur date
(partitionnement par intervalles) puis selon leur emplacement géographique
(partitionnement par listes). Malheureusement, cela n'est pas possible avec MySQL
5.1 puisque le partitionnement du deuxième niveau ne peut être que par clé ou par
hachage.
En tenant compte de cette contrainte un peu gênante, nous allons mettre en place
un partitionnement par intervalles pour les dates qui sera lui-même partitionné par
hachage pour les pays. Voici le code qui permet de faire ce sous-partitionnement :
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
DROP TABLE transac_part;
Exécutons maintenant une requête qui concerne l'année, mais aussi l'emplacement
géographique des transactions :
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
https://krierjon.developpez.com/mysql/partitionnement/#LIII 17/25
19/12/2018 Améliorer les performances(MySQL 5.1)
8.
9.
mysql> SELECT SUM(montant), AVG(montant) FROM transac WHERE codePays=2 AND jour BETWEEN
+--------------+--------------+
| SUM(montant) | AVG(montant) |
+--------------+--------------+
| 567261305 | 4058.0115 |
+--------------+--------------+
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
mysql> SELECT SUM(montant), AVG(montant) FROM transac_part WHERE codePays=2 AND jour BE
+--------------+--------------+
| SUM(montant) | AVG(montant) |
+--------------+--------------+
| 567261305 | 4058.0115 |
+--------------+--------------+
Et voilà le travail ! Une optimisation très impressionnante. Cela prouve que le sous-
partitionnement est certes un peu plus complexe à mettre en place, mais s'il est
adapté aux requêtes fréquentes, le gain est énorme. C'est avec le sous-
partitionnement que vous pourrez obtenir les meilleurs résultats d'optimisation. Il
est donc conseillé de le maîtriser au mieux.
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
mysql> SELECT SUM(montant), AVG(montant) FROM transac;
+--------------+--------------+
| SUM(montant) | AVG(montant) |
+--------------+--------------+
| 49998688174 | 4999.8688 |
+--------------+--------------+
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
mysql> SELECT SUM(montant), AVG(montant) FROM transac_part;
+--------------+--------------+
| SUM(montant) | AVG(montant) |
+--------------+--------------+
| 49998688174 | 4999.8688 |
+--------------+--------------+
https://krierjon.developpez.com/mysql/partitionnement/#LIII 18/25
19/12/2018 Améliorer les performances(MySQL 5.1)
être rarement exécutées sans quoi vous ne feriez qu'alourdir le temps d'occupation
du serveur !
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
CREATE TABLE transac
(
-- Définition des colonnes de la table
)
PARTITION BY RANGE(YEAR(jour))
SUBPARTITION BY HASH(codePays)
(
PARTITION p0 VALUES LESS THAN (1997) (
SUBPARTITION s0
DATA DIRECTORY = '/disk0/data'
INDEX DIRECTORY = '/disk0/idx',
SUBPARTITION s1
DATA DIRECTORY = '/disk1/data'
INDEX DIRECTORY = '/disk1/idx',
...
),
...
);
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
https://krierjon.developpez.com/mysql/partitionnement/#LIII 19/25
19/12/2018 Améliorer les performances(MySQL 5.1)
8.
9.
CREATE TABLE nom_table
(
-- Définition des colonnes de la table
)
PARTITION BY { RANGE(expr) | LIST(expr) }
(
PARTITION nom_part1 VALUES { LESS THAN (expr) | IN (liste_valeurs) },
...
)
Sélectionnez
1.
2.
3.
4.
5.
6.
CREATE TABLE nom_table
(
-- Définition des colonnes de la table
)
PARTITION BY { KEY(liste_colonnes) | HASH(expr) }
PARTITIONS nb_partitions
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
CREATE TABLE nom_table
(
-- Définition des colonnes de la table
)
PARTITION BY { RANGE(expr) | LIST(expr) }
SUBPARTITION BY { KEY(liste_colonnes) | HASH(expr) }
SUBPARTITIONS nb_sous-partitions_par_partition
(
PARTITION nom_part1 VALUES { LESS THAN (expr) | IN (liste_valeurs) },
...
)
Sélectionnez
1.
ALTER TABLE nom_table DROP PARTITION (nom_part1, nom_part2, ...)
Sélectionnez
1.
ALTER TABLE nom_table ADD PARTITION (PARTITION nom_part VALUES ...)
Sélectionnez
1.
2.
ALTER TABLE nom_table REORGANIZE PARTITION (nom_part1, nom_part2) INTO
(PARTITION nom_part VALUES ...)
Sélectionnez
https://krierjon.developpez.com/mysql/partitionnement/#LIII 20/25
19/12/2018 Améliorer les performances(MySQL 5.1)
1.
2.
ALTER TABLE nom_table REORGANIZE PARTITION (nom_part) INTO
(PARTITION nom_part1 VALUES ..., PARTITION nom_part2 VALUES ...)
Sélectionnez
1.
2.
3.
4.
ALTER TABLE nom_table OPTIMIZE PARTITION (nom_part1, nom_part2, ...);
ALTER TABLE nom_table CHECK PARTITION (nom_part1, ...);
ALTER TABLE nom_table ANALYZE PARTITION (nom_part1, ...);
ALTER TABLE nom_table REPAIR PARTITION (nom_part1, ...);
Sélectionnez
1.
2.
ALTER TABLE nom_table PARTITION BY { HASH(expr) | KEY(cols) | LIST(expr) | RANGE(expr)
-- Définition de partitions (voir création de tables partitionnées)
Dans certains cas, le partitionnement horizontal sera sûrement très apprécié pour
500 000 enregistrements. Tout ce que je peux vous conseiller, c'est de faire des
tests. Comme vous l'avez vu, il est très rapide de faire une copie d'une table et de la
partitionner.
Ceci dit, je doute fortement que le partitionnement horizontal soit très utile aux
webmasters amateurs. À moins que le trafic soit vraiment élevé !
Pour l'instant, tout s'est déroulé pour le mieux puisque nous avons réussi à
optimiser plusieurs types de requêtes. Néanmoins, tout au long des tests, nous
avons été confrontés à des limitations de MySQL. Même si des substituts ont pu être
trouvés simplement, il est nécessaire de faire le point sur les possibilités offertes par
MySQL 5.1.
Voici une liste non exhaustive des limitations actuelles (MySQL 5.1.11 bêta) :
Malgré ces limitations, qui seront probablement vite comblées, j'espère que vous
avez pu apprécier autant que moi ces nouvelles fonctionnalités. Il est évident qu'il
reste beaucoup de travail pour les développeurs de MySQL AB, mais tout cela est de
bon augure pour le futur de leur SGBD.
Peu importe, nous pouvons nous-mêmes simuler des partitions à partir de tables et
pour les gérer au mieux, nous allons utiliser deux fonctionnalités apparues dans
MySQL 5 : les vues et les procédures stockées.
Afin de nous assurer que le gain de performances est bien au rendez-vous avec le
partitionnement vertical, nous allons prendre l'exemple (très simplifié) d'un site de
e-commerce qui possède une table de produits où celle-ci contient, pour chaque
produit, différentes données ainsi qu'une photo. Pour tenter d'optimiser nos
requêtes, nous allons placer les champs des photos, accédés moins fréquemment,
dans une partition séparée des autres données.
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
CREATE TABLE produit
(
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
libelle VARCHAR(50) NOT NULL,
prix DECIMAL(10,2) NOT NULL,
photo BLOB NOT NULL
);
Pour le champ photo, nous utilisons le type BLOB qui permet de stocker jusqu'à
64 Ko de données binaires.
Comme prévu, nous allons séparer les photos. Pour cela, nous créons une nouvelle
table Photo_Produit qui contiendra, en plus des photos, l'identifiant de chaque
produit pour pouvoir relier les photos à leur produit.
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
CREATE TABLE produit2
(
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
libelle VARCHAR(50) NOT NULL,
prix DECIMAL(10,2) NOT NULL
);
https://krierjon.developpez.com/mysql/partitionnement/#LIII 22/25
19/12/2018 Améliorer les performances(MySQL 5.1)
Nos tables de tests sont maintenant créées. Il ne nous manque plus qu'un jeu
d'essai volumineux. La procédure suivante va nous permettre de remplir les tables
des produits et des photos. Nous n'allons pas stocker de vraies photos : une chaîne
de 20 Ko fera l'affaire.
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
DELIMITER //
DELIMITER ;
Remplissons maintenant nos tables avec 10 000 produits, ce qui nous fera environ
200 Mo de photos :
Sélectionnez
1.
2.
mysql> CALL remplir_produit(10000);
Query OK, 1 row affected (1 min 58.87 sec)
Testons dès à présent une requête simple : identifiant, libellé et prix de tous les
produits.
Sélectionnez
1.
2.
3.
mysql > SELECT id, libelle, prix FROM produit;
Sélectionnez
1.
2.
3.
mysql> SELECT id, libelle, prix FROM produit2;
https://krierjon.developpez.com/mysql/partitionnement/#LIII 23/25
19/12/2018 Améliorer les performances(MySQL 5.1)
Jusqu'ici, nous avons vu que le partitionnement vertical, réalisé en séparant les
champs volumineux et peu accédés, se montre efficace pour les performances.
Néanmoins, nous disposons maintenant de deux tables au lieu d'une. Ceci rend
l'écriture de certaines requêtes plus longue et difficile.
Pour insérer un nouveau produit, il faut donc faire deux requêtes d'insertion. Ce
n'est pas bien grave, mais dans le cas d'un partitionnement vertical en 10 tables,
cela peut gêner. Grâce à une procédure stockée, il est très simple de simplifier
l'écriture des requêtes, comme le montre l'exemple ci-dessous :
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
DELIMITER //
DELIMITER ;
En appelant cette procédure, il est très facile d'insérer un nouveau produit. Le lien
entre la table Produit et la table Photo_Produit est réalisé automatiquement grâce à
la fonction LAST_INSERT_ID().
De plus, pour faciliter l'écriture de vos SELECT qui concernent toutes les données de
produits, une vue peut être très utile. Grâce à celle-ci, vous pourrez exprimer vos
requêtes comme si une seule table était présente. Voici la vue qui permettrait de le
faire dans notre exemple :
Sélectionnez
1.
2.
CREATE VIEW produit_complet AS
SELECT * FROM produit2 p INNER JOIN photo_produit ph ON p.id = ph.idProd;
Après la création de cette vue, il est donc possible d'exécuter des requêtes telles
que :
Sélectionnez
1.
2.
3.
SELECT * FROM produit_complet WHERE id = 5;
L'exécution de ces requêtes avec notre partitionnement est certes un peu plus
longue que si toutes les données n'étaient pas séparées dans deux tables,
puisqu'une jointure doit être réalisée. Cependant, les jointures SQL étant très
optimisées dans les SGBD, cette perte de performances est très minime. Et
rappelons que si le partitionnement est bien réalisé, ces requêtes ralenties seront
beaucoup plus rares que celles que nous avons optimisées !
IV. Nettoyage▲
Si vous avez réalisé les différents tests proposés dans l'article, n'oubliez pas de
nettoyer votre base de données, car dans le cas contraire, vous perdriez presque
500 Mo d'espace disque ! Le script suivant efface tout ce qui a pu être créé en
reproduisant les exemples :
Sélectionnez
1.
2.
3.
4.
5.
DROP TABLE transac, transac_part, produit, produit2, photo_produit;
V. Conclusion▲
Dans cet article, nous avons d'abord découvert les deux méthodes de
partitionnement :
Après cela, nous avons mis en œuvre un partitionnement vertical, qui n'est pas géré
automatiquement par MySQL. Le partitionnement vertical ajoute du travail et rend
plus difficile l'expression de certaines requêtes. Mais comme nous l'avons vu, tout
cela en vaut la peine. Le but recherché est de séparer les données volumineuses
rarement accédées, des autres données fréquemment utilisées.
VI. Ressources▲
http://dev.mysql.com/doc/refman/5.1/en/partitioning.html;
http://mikaelronstrom.blogspot.com;
http://forums.mysql.com/list.php?106.
VII. Remerciements▲
Nous tenons à remercier plusieurs personnes, sans qui cet article n'aurait jamais vu
le jour :
Alexandre Tranchant, rédacteur sur developpez.com, pour son très bon article sur le
programmateur d'événements de MySQL5.1 qui m'a donné envie de m'intéresser
aux nouveautés de MySQL.
Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants :
Partager
https://krierjon.developpez.com/mysql/partitionnement/#LIII 25/25