Format CSV, JSON, XML, SQL,
Lequel est le plus rapide?
Quel format de fichier est le plus rapide pour modifier une valeur (UPDATE), par exemple l'email d'un client?
Aller directement au tableau comparatifd des performances
Un fichier clients.csv 42.9Ko comporte 311 lignes (il a été généré avec des valeurs aléatoires, mais
prénoms et noms,
noms de rue de Saint-Nazaire,
villes réels,
j'utlise ces données pour générer des clients et des commandes afin de tester les performances d'une boutique en ligne dans 10 ans quand les tables seront bien pleines)
Chaque ligne du CSV est du type
#Spec: identifiant unique|identifiant de connexion|mot de passe crypté|nom de famille|prénom|rue|ville|code postal|tél|email|date de création du compte
1a0b92051ae9|yJp|3pNLbRu.nE2sY|BOZORRGIGAZIANI|Alperen|5 QUAI DEMANGE|APPY|09250|06 89 35 19 17|bozorrgigaziani.alperen@njjgsvm.fr|1713437891
Ce CSV a permis de générer le même fichier clients au format JSON clients.json 74.5Ko (écrit sur le disque en une seule ligne):
{
"clients":[
{
"id":"0092051fbacc",
"identifiant":"yJp",
"mdp":"3pNLbRu.2nEsY",
"nom":"BOZORRGIGAZIANI",
"prenom":"Alperen",
"rue":"5 QUAI DEMANGE",
"ville":"APPY",
"cp":"09250",
"tel":"06 89 35 19 17",
"email":"bozorrgigaziani.alperen@njjgsvm.fr",
"date":1713437891
},
...
]
Le fichier XML clients.xml 98.6Ko:
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<clients>
<client>
<id>aea6ea2ed27a</id>
<identifiant>kk</identifiant>
<mdp>3pPOuXDXNGRMU</mdp>
<nom>Lancaster</nom>
<prenom>Kurt</prenom>
<rue>1 rue Paradis</rue>
<ville>MARSEILLE</ville>
<cp>13001</cp>
<tel>0605040302</tel>
<email>joe@orange.fr</email>
<date>1713027624</date>
</client>
...
</clients>
La structure de la table innoDB de la base de données MySQL:
CREATE TABLE `clients_innodb` (
`id` varchar(12) NOT NULL,
`identifiant` varchar(16) NOT NULL,
`mdp` varchar(13) NOT NULL,
`nom` varchar(32) NOT NULL,
`prenom` varchar(32) NOT NULL,
`rue` varchar(32) NOT NULL,
`ville` varchar(32) NOT NULL,
`cp` varchar(5) NOT NULL,
`tel` varchar(11) NOT NULL,
`email` varchar(64) NOT NULL,
`creation` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Maintenant les scripts dont le temps d'exécution est mesuré, modifient le client index 280 (id 56823b1fa645) en remplaçant la valeur de son email.
Mesure du temps d'exécution du CSV avec fgetcsv()
<?php
$id = '56823b1fa645';
$newemail = 'truc@machin.fr';
$start = microtime(true);
if (($fp = fopen("clients.csv","r"))!== FALSE){
$s='';
while (($t = fgetcsv($fp,1000,"|"))!== FALSE){
if($t[0]==$id) $t[9] = $newemail;
$s .= $t[0].'|'.$t[1].'|'.$t[2].'|'.$t[3].'|'.$t[4].'|'.$t[5].'|'.$t[6].'|'.$t[7].'|'.$t[8].'|'.$t[9].'|'.$t[10]."\n";
}
fclose($fp);
file_put_contents('clients.csv',$s);
}
$end = microtime(true);
$duree = number_format(($end - $start)*1000,3);
echo "duree:$duree ms<br>";
?>
Durée moyenne de plusieurs tests: 20.659 ms
C'est vrai fgetcsv() est lente, mais elle gère les ; dans les valeurs en mettant les valeurs entre guillemets
Ce qui permet d'avoir un caractère séparateur dans une valeur (et même un saut de ligne).
Mesure du temps d'exécution CSV avec file_get_contents() et explode()
<?php
$id = '56823b1fa645';
$newemail = 'truc@machin.fr';
$start = microtime(true);
$tab = explode("\n", file_get_contents("clients.csv"));
$nb=count($tab);
$s='';
for($n=0; $n<$nb-1; $n++){
$t = explode('|',$tab[$n]);
if($t[0]==$id) $t[9] = $newemail;
$s .= $t[0].'|'.$t[1].'|'.$t[2].'|'.$t[3].'|'.$t[4].'|'.$t[5].'|'.$t[6].'|'.$t[7].'|'.$t[8].'|'.$t[9].'|'.$t[10]."\n";
}
file_put_contents('clients.csv',$s);
$end = microtime(true);
$duree = number_format(($end - $start)*1000,3);
echo "duree:$duree ms<br>";
?>
Durée moyenne de plusieurs tests: 2.213 ms
explode() est rapide, mais les valeurs ne doivent absolument pas contenir un séparateur ou un saut à la ligne.
Comme quoi fgetcsv() est une fonction créée pour le format CSV, mais minable pour les performances.
Mesure du temps d'exécution CSV avec explode() et implode()
<?php
$id = '56823b1fa645';
$newemail = 'truc@machin.fr';
$start = microtime(true);
$lignes = explode("\n", file_get_contents("clients.csv"));
$nb = count($lignes);
for($n=0; $n<$nb-1; $n++){
$t = explode('|',$lignes[$n]);
if($t[0]==$id){
$t[9] = $newemail;
$lignes[$n] = implode('|', $t);
break;
}
}
file_put_contents('clients.csv',implode("\n", $tab));
$end = microtime(true);
$duree = number_format(($end - $start)*1000,3);
echo "duree:$duree ms<br>";
?>
Durée moyenne de plusieurs tests: 1.488 ms
Mesure du temps d'exécution CSV avec Parser1 PHP (sans accepter de séprateur dans les valeurs)
<?php
$id = '56823b1fa645';
$newemail = 'truc@machin.fr';
$start = microtime(true);
$s = file_get_contents('clients.csv');
$len = strlen($s);
$mot='';
$b=0;
$cpt=0;
$csv=''; // string concaténé à écrire
for ($n=0; $n<$len; $n++){
if($s[$n]=='|'){
$csv .= "$mot|";
// c'est le bon id! Avance jusqu'au champ email pour le remplacer
if($cpt==0 && $mot==$id){
while($cpt!=9){
if($s[$n]=='|') $cpt++;
$csv .= $s[$n++];
}
$csv.=$newemail;
while($s[$n]!='|'){$n++;} //avance jusqu'à | pour survoler l'ancien email
$csv .= substr($s,$n,$len-$n); // puis copie le restant du fichier dans csv
break;
}
$mot='';$b=0; $cpt++;
}
else if($s[$n]=="\n"){
$csv .= "$mot\n";
$mot='';$b=0; $cpt=0;
}
else $mot[$b++]=$s[$n];
}
file_put_contents('clients2.csv',$csv);
$end = microtime(true);
$duree = number_format(($end - $start)*1000,3);
echo "duree:$duree ms<br>";
?>
Durée moyenne de plusieurs tests: 5.091 ms
Mesure du temps d'exécution CSV avec Parser2 PHP (accepte séprateur dans les valeurs)
<?php
$id = '56823b1fa645';
$newemail = 'truc@machin.fr';
$start = microtime(true);
$s = file_get_contents('clients.csv');
$len = strlen($s);
$mot='';
$b=0;
$cpt=0;
$csv=''; // string concaténé à écrire
for ($n=0; $n<$len; $n++){
if($s[$n]=='|'){
$csv .= "$mot|";
// c'est le bon id! Avance jusqu'au champ email pour le remplacer
if($cpt==0 && $mot==$id){
while($cpt!=9){
if($s[$n]=='|') $cpt++;
$csv .= $s[$n++];
}
$csv.=$newemail;
while($s[$n]!='|'){$n++;} //avance jusqu'à | pour survoler l'ancien email
$csv .= substr($s,$n,$len-$n); // puis copie le restant du fichier dans csv
break;
}
$mot='';$b=0; $cpt++;
}
// Guillemet rencontré
else if($s[$n]=='"'){
// avance jusqu'à la fin du mot
$n++;
while($s[$n]!='"'){$mot[$b++]=$s[$n++];}
$csv .= $mot;
$mot='';$b=0; $cpt++;
$n++; if($s[$n]=="\n") {$csv.="\n"; $cpt=0;}
else $csv.= '|'; $cpt++;
}
else if($s[$n]=="\n"){
$csv .= "$mot\n";
$mot='';$b=0; $cpt=0;
}
else $mot[$b++]=$s[$n];
}
file_put_contents('clients2.csv',$csv);
$end = microtime(true);
$duree = number_format(($end - $start)*1000,3);
echo "duree:$duree ms<br>";
?>
Durée moyenne de plusieurs tests: 7.234 ms
Mesure du temps d'exécution JSON (sans tableau associatif)
<?php
$id = '56823b1fa645';
$newemail = 'truc@machin.fr';
$start = microtime(true);
$json = json_decode(file_get_contents('clients.json'));
foreach($json->clients as $client){
if($client->id==$id){
$client->email = $newemail;
break;
}
}
file_put_contents('clients.json', json_encode($json));
$end = microtime(true);
$duree = number_format(($end - $start)*1000,3);
echo "duree:$duree ms<br>";
?>
Durée moyenne de plusieurs tests: 5.608 ms
Mesure du temps d'exécution JSON avec tableau associatif
<?php
$id = '56823b1fa645';
$newemail = 'truc@machin.fr';
$start = microtime(true);
$json = json_decode(file_get_contents('clients.json'), true);
foreach($json["clients"] as $client){
if($client["id"]==$id){
$client["email"] = $newemail;
break;
}
}
file_put_contents('clients.json', json_encode($json));
$end = microtime(true);
$duree = number_format(($end - $start)*1000,3);
echo "duree:$duree ms<br>";
?>
Durée moyenne de plusieurs tests: 5.352 ms
Mesure du temps d'exécution XML avec SimpleXMLElement
<?php
$id = '56823b1fa645';
$newemail = 'truc@machin.fr';
$start = microtime(true);
$xmlstr = file_get_contents("clients.xml");
$clients = new SimpleXMLElement($xmlstr);
foreach ($clients as $client) {
if($client->id==$id){
$client->email = $newemail;
break;
}
}
//file_put_contents('clients.xml',$clients->asXML());
$clients->asXML('clients.xml');
$end = microtime(true);
$duree = number_format(($end - $start)*1000,3);
echo "duree:$duree ms<br>";
?>
Durée moyenne de plusieurs tests: 15.395 ms
Mesure du temps d'exécution XML avec XMLWriter
<?php
$id = '56823b1fa645';
$newemail = 'truc@machin.fr';
$start = microtime(true);
$xmlstr = file_get_contents("clients.xml");
$clients = new SimpleXMLElement($xmlstr);
$xml = new XMLWriter();
$xml->openURI('clients.xml');
$xml->setIndent(true);
$xml->setIndentString("\t");
$xml->startDocument('1.0', 'UTF-8');
$xml->startElement('xml');
foreach ($clients as $client) {
if($client->id==$id) $client->email = $newemail;
$xml->startElement('client');
$xml->writeElement('id', $client->id);
$xml->writeElement('identifiant', $client->identifiant);
$xml->writeElement('mdp', $client->mdp);
$xml->writeElement('nom', $client->nom);
$xml->writeElement('prenom', $client->prenom);
$xml->writeElement('rue', $client->rue);
$xml->writeElement('ville', $client->ville);
$xml->writeElement('cp', $client->cp);
$xml->writeElement('tel', $client->tel);
$xml->writeElement('email', $client->email);
$xml->writeElement('date', $client->date);
$xml->endElement();
}
$xml->endElement();
$xml->endDocument();
$xml->flush();
unset($xml);
$end = microtime(true);
$duree = number_format(($end - $start)*1000,3);
echo "duree:$duree ms<br>";
?>
Durée moyenne de plusieurs tests: 19.317 ms
Mesure du temps d'exécution MySQL table innoDB, mysqli procédural
<?php
$id = '56823b1fa645';
$newemail = 'truc@machin.fr';
$start = microtime(true);
$servername = "";
$username = "";
$database = "";
$password = "";
$sql = "UPDATE clients_innodb SET email='$newemail' WHERE id='$id';";
$mysqli = mysqli_connect($servername, $username, $password, $database) or die('Error connecting to MySQL server.');
mysqli_query($mysqli, $sql) or die("<b>$sql</b><br/>".mysqli_error($mysqli));
mysqli_close($mysqli);
$end = microtime(true);
$duree = number_format(($end - $start)*1000,3);
echo "duree:$duree ms<br>";
?>
Durée moyenne de plusieurs tests: 3.633 ms
Mesure du temps d'exécution MySQL table innoDB, mysqli objet
<?php
$id = '56823b1fa645';
$newemail = 'truc@machin.fr';
$start = microtime(true);
$servername = "";
$username = "";
$database = "";
$password = "";
$sql = "UPDATE clients_innodb SET email='$newemail' WHERE id='$id';";
$mysqli = new mysqli($servername, $username, $database, $password);
if ($mysqli->connect_errno) {
echo "Failed to connect to MySQL: ".$mysqli->connect_error;
die();
}
$mysqli->query($sql);
if ($mysqli->errno) {
printf("Could not update table: %s<br />", $mysqli->error);
}
$mysqli->close();
$end = microtime(true);
$duree = number_format(($end - $start)*1000,3);
echo "duree:$duree ms<br>";
?>
Durée moyenne de plusieurs tests: 3.727 ms
Mesure du temps d'exécution MySQL table innoDB, Objets PDO
<?php
$id = '56823b1fa645';
$newemail = 'truc@machin.fr';
$start = microtime(true);
$servername = "";
$username = "";
$database = "";
$password = "";
$sql = "UPDATE clients_innodb SET email='$newemail' WHERE id='$id';";
try {
$pdo = new PDO("mysql:host=$servername;dbname=$database", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt= $pdo->prepare($sql);
$stmt->execute();
$pdo = null;
}
catch(PDOException $e) {
echo $e->getMessage();
}
$end = microtime(true);
$duree = number_format(($end - $start)*1000,3);
echo "duree:$duree ms<br>";
?>
Durée moyenne de plusieurs tests: 3.728 ms
Récapitulatif des mesures
Pour faire en PHP un update d'une valeur sur le 280e enregistrement d'une table qui en a 311.
| Code | Durée (ms) | Taille du fichier (Ko) | |
| CSV fgetcsv() | 20.659 | 42.9 | Prend en charge un séparateur dans une valeur |
| CSV explode() | 2.213 | 42.9 | Ne prend pas en charge un séparateur dans une valeur |
| CSV explode()/implode() | 1.488 | 42.9 | Ne prend pas en charge un séparateur dans une valeur |
| CSV Parser1 PHP | 5.091 | 42.9 | Ne prend pas en charge un séparateur dans une valeur |
| CSV Parser2 PHP | 7.234 | 42.9 | Prend en charge un séparateur dans une valeur |
| JSON | 5.608 | 74.5 | |
| JSON associatif | 5.352 | 74.5 | |
| XML | 15.395 | 98.6 | |
| XML XmlWriter | 19.317 | 98.6 | |
| MySQL innodb, mysqli | 3.633 | 54.6 | Ajouter la latence réseau ping pong du serveur web au serveur de données |
| MySQL innodb, mysqli obj | 3.727 | 54.6 | Ajouter la latence réseau ping pong du serveur web au serveur de données |
| MySQL innodb, PDO | 3.728 | 54.6 | Ajouter la latence réseau ping pong du serveur web au serveur de données |
"Conclusion, en perf c'est le plus petit qui gagne.
S'il faut prendre en compte un séparateur dans une valeur, je partirai sur du JSON avec tableau associatif.
Sinon CSV explode() + implode();
Voilà, le CSV est presque 3X plus rapide que le SQL... Et je ne vous dis pas le temps passé à installer un OS, MySQL et PHPMyAdmin, créer la base de données, l'utilisateur, lui assigner des droits, créer la structure de la table, la remplir. Sans parler du prix d'un ordinateur comme serveur de données, avec deux disques, et un 2e ordinateur pour la redondance car si le serveur de données tombe, l'application aussi. Le NOSQL c'est pour les applications web 2.0 temps réel. Demandez-leur si Amazon fonctionne sous Prestashop...
Si vous voulez tester voici le zip (107.9Ko) contenant les données CSV, JSON, XML, MySQL.
À vous de voir quelle méthode vous préférez." - B.M.
COMMENTAIRES