J'utilise les UUID dans mes systèmes depuis un certain temps maintenant pour diverses raisons allant de la journalisation à la corrélation retardée. Les formats que j'ai utilisés ont changé à mesure que je devenais moins naïf:
VARCHAR(255)
VARCHAR(36)
CHAR(36)
BINARY(16)
C'est lorsque j'ai atteint le dernier BINARY(16)
que j'ai commencé à comparer les performances avec un entier à incrémentation automatique de base. Le test et les résultats sont présentés ci-dessous, mais si vous voulez juste le résumé, il indique que INT AUTOINCREMENT
et BINARY(16) RANDOM
ont des performances identiques sur des plages de données allant jusqu'à 200 000 (la base de données a été pré-remplie avant les tests).
J'étais initialement sceptique quant à l'utilisation des UUID comme clés primaires, et je le suis toujours, mais je vois un potentiel ici pour créer une base de données flexible qui peut utiliser les deux. Alors que de nombreuses personnes soulignent les avantages de l'une ou l'autre, quels sont les inconvénients annulés en utilisant les deux types de données?
PRIMARY INT
UNIQUE BINARY(16)
Le cas d'utilisation pour ce type de configuration serait la clé primaire traditionnelle pour les relations inter-tables, avec un identifiant unique utilisé pour les relations inter-systèmes.
Ce que j'essaie essentiellement de découvrir, c'est la différence d'efficacité entre les deux approches. Outre l'espace disque quadruple utilisé, qui peut être largement négligeable après l'ajout de données supplémentaires, ils me semblent être les mêmes.
Schéma:
-- phpMyAdmin SQL Dump
-- version 4.0.10deb1
-- http://www.phpmyadmin.net
--
-- Host: localhost
-- Generation Time: Sep 22, 2015 at 10:54 AM
-- Server version: 5.5.44-0ubuntu0.14.04.1
-- PHP Version: 5.5.29-1+deb.sury.org~trusty+3
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
--
-- Database: `test`
--
-- --------------------------------------------------------
--
-- Table structure for table `with_2id`
--
CREATE TABLE `with_2id` (
`guidl` bigint(20) NOT NULL,
`guidr` bigint(20) NOT NULL,
`data` varchar(255) NOT NULL,
PRIMARY KEY (`guidl`,`guidr`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- --------------------------------------------------------
--
-- Table structure for table `with_guid`
--
CREATE TABLE `with_guid` (
`guid` binary(16) NOT NULL,
`data` varchar(255) NOT NULL,
PRIMARY KEY (`guid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- --------------------------------------------------------
--
-- Table structure for table `with_id`
--
CREATE TABLE `with_id` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`data` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=197687 ;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
Insérer un repère:
function benchmark_insert(PDO $pdo, $runs)
{
$data = 'Sample Data';
$insert1 = $pdo->prepare("INSERT INTO with_id (data) VALUES (:data)");
$insert1->bindParam(':data', $data);
$insert2 = $pdo->prepare("INSERT INTO with_guid (guid, data) VALUES (:guid, :data)");
$insert2->bindParam(':guid', $guid);
$insert2->bindParam(':data', $data);
$insert3 = $pdo->prepare("INSERT INTO with_2id (guidl, guidr, data) VALUES (:guidl, :guidr, :data)");
$insert3->bindParam(':guidl', $guidl);
$insert3->bindParam(':guidr', $guidr);
$insert3->bindParam(':data', $data);
$benchmark = array();
$time = time();
for ($i = 0; $i < $runs; $i++) {
$insert1->execute();
}
$benchmark[1] = 'INC ID: ' . (time() - $time);
$time = time();
for ($i = 0; $i < $runs; $i++) {
$guid = openssl_random_pseudo_bytes(16);
$insert2->execute();
}
$benchmark[2] = 'GUID: ' . (time() - $time);
$time = time();
for ($i = 0; $i < $runs; $i++) {
$guid = openssl_random_pseudo_bytes(16);
$guidl = unpack('q', substr($guid, 0, 8))[1];
$guidr = unpack('q', substr($guid, 8, 8))[1];
$insert3->execute();
}
$benchmark[3] = 'SPLIT GUID: ' . (time() - $time);
echo 'INSERTION' . PHP_EOL;
echo '=============================' . PHP_EOL;
echo $benchmark[1] . PHP_EOL;
echo $benchmark[2] . PHP_EOL;
echo $benchmark[3] . PHP_EOL . PHP_EOL;
}
Sélectionnez une référence:
function benchmark_select(PDO $pdo, $runs) {
$select1 = $pdo->prepare("SELECT * FROM with_id WHERE id = :id");
$select1->bindParam(':id', $id);
$select2 = $pdo->prepare("SELECT * FROM with_guid WHERE guid = :guid");
$select2->bindParam(':guid', $guid);
$select3 = $pdo->prepare("SELECT * FROM with_2id WHERE guidl = :guidl AND guidr = :guidr");
$select3->bindParam(':guidl', $guidl);
$select3->bindParam(':guidr', $guidr);
$keys = array();
for ($i = 0; $i < $runs; $i++) {
$kguid = openssl_random_pseudo_bytes(16);
$kguidl = unpack('q', substr($kguid, 0, 8))[1];
$kguidr = unpack('q', substr($kguid, 8, 8))[1];
$kid = mt_rand(0, $runs);
$keys[] = array(
'guid' => $kguid,
'guidl' => $kguidl,
'guidr' => $kguidr,
'id' => $kid
);
}
$benchmark = array();
$time = time();
foreach ($keys as $key) {
$id = $key['id'];
$select1->execute();
$row = $select1->fetch(PDO::FETCH_ASSOC);
}
$benchmark[1] = 'INC ID: ' . (time() - $time);
$time = time();
foreach ($keys as $key) {
$guid = $key['guid'];
$select2->execute();
$row = $select2->fetch(PDO::FETCH_ASSOC);
}
$benchmark[2] = 'GUID: ' . (time() - $time);
$time = time();
foreach ($keys as $key) {
$guidl = $key['guidl'];
$guidr = $key['guidr'];
$select3->execute();
$row = $select3->fetch(PDO::FETCH_ASSOC);
}
$benchmark[3] = 'SPLIT GUID: ' . (time() - $time);
echo 'SELECTION' . PHP_EOL;
echo '=============================' . PHP_EOL;
echo $benchmark[1] . PHP_EOL;
echo $benchmark[2] . PHP_EOL;
echo $benchmark[3] . PHP_EOL . PHP_EOL;
}
Tests:
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
benchmark_insert($pdo, 1000);
benchmark_select($pdo, 100000);
Résultats:
INSERTION
=============================
INC ID: 3
GUID: 2
SPLIT GUID: 3
SELECTION
=============================
INC ID: 5
GUID: 5
SPLIT GUID: 6
la source
BINARY(16)
Je pense que nous convenons tous les deux que c'est le moyen le plus efficace de stocker un UUID, mais en ce qui concerne l'UNIQUE
index, dois-je simplement utiliser un index régulier? Les octets sont générés à l'aide de RNG cryptographiquement sécurisés, dois-je donc dépendre entièrement du caractère aléatoire et renoncer aux contrôles?innodb_buffer_pool_size
70% du bélier disponible.'Rick James' a dit dans la réponse acceptée: "Avoir à la fois un AUTO_INCREMENT UNIQUE et un UUID UNIQUE dans la même table est un gaspillage". Mais ce test (je l'ai fait sur ma machine) montre des faits différents.
Par exemple: avec le test (T2) je fais un tableau avec (INT AUTOINCREMENT) PRIMARY et UNIQUE BINARY (16) et un autre champ comme titre, puis j'insère plus de 1,6M de lignes avec de très bonnes performances, mais avec un autre test (T3) J'ai fait la même chose mais le résultat est lent après avoir inséré 300 000 lignes seulement.
Voici mon résultat de test:
Donc binaire (16) UNIQUE avec incrémentation automatique int_id est meilleur que binaire (16) UNIQUE sans incrémentation automatique int_id.
Je refais le même test et j'enregistre plus de détails. il s'agit du code complet et de la comparaison des résultats entre (T2) et (T3) comme expliqué ci-dessus.
(T2) créez tbl2 (mysql):
(T3) créez tbl3 (mysql):
Ceci est un code de test complet, il insère 600 000 enregistrements dans tbl2 ou tbl3 (code vb.net):
Le résultat pour (T2):
Le résultat pour (T3):
la source
innodb_buffer_pool_size
? D'où vient la "taille de la table"?COMMIT
, pas avant. Cela peut éliminer certaines autres anomalies.@rec_id
et@src_id
sont générées et appliquées à chaque ligne. L'impression de quelquesINSERT
déclarations pourrait me satisfaire.t2
tombera également d'une falaise. Cela peut même aller plus lentement quet3
; Je ne suis pas sûr. Votre point de repère est dans un «trou de beignet» où ilt3
est temporairement plus lent.