Estou usando UUIDs em meus sistemas há um tempo por vários motivos, desde o log até a correlação atrasada. Os formatos que usei mudaram à medida que me tornei menos ingênuo de:
VARCHAR(255)
VARCHAR(36)
CHAR(36)
BINARY(16)
Foi quando cheguei ao final BINARY(16)
que comecei a comparar o desempenho com o número inteiro básico de auto incremento. O teste e os resultados são mostrados abaixo, mas se você deseja apenas o resumo, isso indica que o desempenho é idêntico INT AUTOINCREMENT
e os BINARY(16) RANDOM
dados variam de até 200.000 (o banco de dados foi preenchido previamente antes dos testes).
Inicialmente, fiquei cético em relação ao uso de UUIDs como chaves primárias e, de fato, ainda o sou, porém vejo potencial aqui para criar um banco de dados flexível que possa usar ambos. Enquanto muitas pessoas enfatizam as vantagens de qualquer uma delas, quais são as desvantagens canceladas usando os dois tipos de dados?
PRIMARY INT
UNIQUE BINARY(16)
O caso de uso desse tipo de configuração seria a chave primária tradicional para relacionamentos entre tabelas, com um identificador exclusivo usado para relacionamentos entre sistemas.
O que estou tentando essencialmente descobrir é a diferença de eficiência entre as duas abordagens. Além do espaço em disco quádruplo usado, que pode ser amplamente desprezível após a adição de dados adicionais, eles me parecem iguais.
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 */;
Inserir referência:
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;
}
Selecionar referência:
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;
}
Testes:
$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
fonte
BINARY(16)
Acho que nós dois concordamos que é a maneira mais eficiente de armazenar um UUID, mas com relação aoUNIQUE
índice, devo simplesmente usar um índice regular? Os bytes são gerados usando RNGs criptograficamente seguros, então devo depender inteiramente da aleatoriedade e renunciar às verificações?innodb_buffer_pool_size
é 70% da memória RAM disponível.'Rick James' disse em resposta aceita: "Ter um AUTO_INCREMENT ÚNICO e um UUID ÚNICO na mesma tabela é um desperdício". Mas este teste (eu fiz na minha máquina) mostra fatos diferentes.
Por exemplo: com o teste (T2), faço a tabela com (INT AUTOINCREMENT) PRIMARY e UNIQUE BINARY (16) e outro campo como título, depois insiro mais de 1,6 milhão de linhas com desempenho muito bom, mas com outro teste (T3) Fiz o mesmo, mas o resultado é lento depois de inserir apenas 300.000 linhas.
Este é o meu resultado de teste:
Portanto, binário (16) UNIQUE com incremento automático int_id é melhor que binário (16) UNIQUE sem incremento automático int_id.
Faço o mesmo teste novamente e registro mais detalhes. essa é uma comparação completa de código e resultados entre (T2) e (T3), conforme explicado acima.
(T2) crie tbl2 (mysql):
(T3) crie tbl3 (mysql):
Este é um código de teste completo, está inserindo 600.000 registros no tbl2 ou tbl3 (código vb.net):
O resultado para (T2):
O resultado para (T3):
fonte
innodb_buffer_pool_size
? De onde veio o "tamanho da mesa"?COMMIT
, e não antes. Isso pode eliminar algumas outras anomalias.@rec_id
e@src_id
estão sendo gerados e aplicados a cada linha. Imprimir algumasINSERT
declarações pode me satisfazer.t2
também cairá de um penhasco. Ele pode até mesmo ir mais lento do quet3
; Não tenho certeza. Sua referência está em um "buraco de rosca", ondet3
é temporariamente mais lento.