He estado usando UUID en mis sistemas desde hace un tiempo por una variedad de razones que van desde el registro hasta la correlación retrasada. Los formatos que utilicé cambiaron a medida que me volví menos ingenuo de:
VARCHAR(255)
VARCHAR(36)
CHAR(36)
BINARY(16)
Fue cuando llegué al último BINARY(16)
cuando comencé a comparar el rendimiento con un entero de incremento automático básico. La prueba y los resultados se muestran a continuación, pero si solo desea el resumen, lo indica INT AUTOINCREMENT
y BINARY(16) RANDOM
tiene un rendimiento idéntico en rangos de datos de hasta 200,000 (la base de datos se rellenó previamente antes de las pruebas).
Inicialmente era escéptico sobre el uso de UUID como claves principales, y de hecho todavía lo soy, sin embargo, veo potencial aquí para crear una base de datos flexible que pueda usar ambos. Mientras que muchas personas enfatizan las ventajas de cualquiera de ellos, ¿cuáles son las desventajas canceladas al usar ambos tipos de datos?
PRIMARY INT
UNIQUE BINARY(16)
El caso de uso para este tipo de configuración sería la clave primaria tradicional para las relaciones entre tablas, con un identificador único utilizado para las relaciones entre sistemas.
Lo que esencialmente intento descubrir es la diferencia de eficiencia entre los dos enfoques. Además del cuádruple espacio en disco utilizado, que puede ser en gran medida insignificante después de agregar datos adicionales, me parecen iguales.
Esquema:
-- 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 */;
Insertar punto de referencia:
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;
}
Seleccionar punto de referencia:
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;
}
Pruebas:
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
benchmark_insert($pdo, 1000);
benchmark_select($pdo, 100000);
Resultados:
INSERTION
=============================
INC ID: 3
GUID: 2
SPLIT GUID: 3
SELECTION
=============================
INC ID: 5
GUID: 5
SPLIT GUID: 6
BINARY(16)
Creo que ambos estamos de acuerdo en que es la forma más eficiente de almacenar un UUID, pero con respecto alUNIQUE
índice, ¿debería simplemente usar un índice regular? Los bytes se generan utilizando RNG criptográficamente seguros, por lo que ¿dependeré completamente de la aleatoriedad y renunciaré a las verificaciones?innodb_buffer_pool_size
es el 70% de la memoria RAM disponible.'Rick James' dijo en respuesta aceptada: "Tener un AUTO_INCREMENTO ÚNICO y un UUID ÚNICO en la misma tabla es un desperdicio". Pero esta prueba (la hice en mi máquina) muestra hechos diferentes.
Por ejemplo: con la prueba (T2) hago una tabla con (INT AUTOINCREMENT) PRIMARY and UNIQUE BINARY (16) y otro campo como título, luego inserto más de 1.6M filas con muy buen rendimiento, pero con otra prueba (T3) Hice lo mismo pero el resultado es lento después de insertar solo 300,000 filas.
Este es el resultado de mi prueba:
Por lo tanto, binary (16) UNIQUE con incremento automático int_id es mejor que binary (16) UNIQUE sin incremento automático int_id.
Hago la misma prueba nuevamente y grabo más detalles. Este es el código completo y la comparación de resultados entre (T2) y (T3) como se explicó anteriormente.
(T2) crear tbl2 (mysql):
(T3) crear tbl3 (mysql):
Este es un código de prueba completo, está insertando 600,000 registros en tbl2 o tbl3 (código vb.net):
El resultado para (T2):
El resultado para (T3):
fuente
innodb_buffer_pool_size
? ¿De dónde vino el "tamaño de la mesa"?COMMIT
, no antes. Esto puede eliminar algunas otras anomalías.@rec_id
y@src_id
están siendo generados y aplicados a cada fila. Imprimir un par deINSERT
declaraciones podría satisfacerme.t2
también caerá por un precipicio. Incluso puede ir más lento quet3
; No estoy seguro. Su punto de referencia está en un "agujero de rosquilla" dondet3
es temporalmente más lento