Od jakiegoś czasu używam UUID w moich systemach z różnych powodów, od rejestrowania po opóźnioną korelację. Formaty, których użyłem, zmieniły się, kiedy stałem się mniej naiwny z:
VARCHAR(255)
VARCHAR(36)
CHAR(36)
BINARY(16)
Kiedy dotarłem do ostatniego BINARY(16)
, zacząłem porównywać wydajność z podstawową liczbą całkowitą automatycznego przyrostu. Test i wyniki pokazano poniżej, ale jeśli chcesz tylko podsumowania, oznacza to INT AUTOINCREMENT
i BINARY(16) RANDOM
ma identyczną wydajność w zakresach danych do 200 000 (baza danych została wstępnie wypełniona przed testami).
Początkowo byłem sceptycznie nastawiony do używania identyfikatorów UUID jako kluczy podstawowych i nadal tak jest, ale widzę tutaj potencjał do stworzenia elastycznej bazy danych, która mogłaby używać obu. Podczas gdy wiele osób kładzie nacisk na zalety obu tych metod, jakie wady eliminuje się przy użyciu obu typów danych?
PRIMARY INT
UNIQUE BINARY(16)
Przypadkiem użycia dla tego typu konfiguracji byłby tradycyjny klucz podstawowy dla relacji między tabelami, z unikalnym identyfikatorem używanym dla relacji między systemami.
Zasadniczo próbuję odkryć różnicę w wydajności między tymi dwoma podejściami. Oprócz czterokrotnie użytego miejsca na dysku, które po dodaniu dodatkowych danych mogą być w znacznym stopniu nieistotne, wydaje mi się, że są takie same.
Schemat:
-- 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 */;
Wstaw test porównawczy:
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;
}
Wybierz test:
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;
}
Testy:
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
benchmark_insert($pdo, 1000);
benchmark_select($pdo, 100000);
Wyniki:
INSERTION
=============================
INC ID: 3
GUID: 2
SPLIT GUID: 3
SELECTION
=============================
INC ID: 5
GUID: 5
SPLIT GUID: 6
źródło
BINARY(16)
Myślę, że oboje zgadzamy się, że jest to najbardziej efektywny sposób przechowywania UUID, ale jeśli chodzi oUNIQUE
indeks, czy powinienem po prostu używać zwykłego indeksu? Bajty są generowane przy użyciu kryptograficznie bezpiecznych RNG, więc czy mam całkowicie polegać na losowości i zrezygnować z kontroli?innodb_buffer_pool_size
to 70% dostępnego pamięci RAM.„Rick James” powiedział w zaakceptowanej odpowiedzi: „Posiadanie UNIKALNEGO AUTO_INCREMENT i UNIKALNEGO UUIDA w tej samej tabeli jest marnotrawstwem”. Ale ten test (zrobiłem to na moim komputerze) pokazuje inne fakty.
Na przykład: za pomocą testu (T2) tworzę tabelę z (INT INTEGRACJA) PODSTAWOWYM i UNIKALNYM BINARNYM (16) i innym polem jako tytułem, a następnie wstawiam ponad 1,6 mln wierszy o bardzo dobrej wydajności, ale z innym testem (T3) Zrobiłem to samo, ale wynik jest powolny po wstawieniu tylko 300 000 wierszy.
Oto mój wynik testu:
Zatem binarne (16) UNIKALNE z automatycznym przyrostem int_id jest lepsze niż binarne (16) UNIKALNE bez automatycznego przyrostu int_id.
Ponownie wykonuję ten sam test i rejestruję więcej szczegółów. jest to pełne porównanie kodu i wyników między (T2) i (T3), jak wyjaśniono powyżej.
(T2) utwórz tbl2 (mysql):
(T3) utwórz tbl3 (mysql):
To jest pełny kod testujący, wstawia 600 000 rekordów do tbl2 lub tbl3 (kod vb.net):
Wynik dla (T2):
Wynik dla (T3):
źródło
innodb_buffer_pool_size
? Skąd wziął się „rozmiar stołu”?COMMIT
, a nie wcześniej. Może to wyeliminować niektóre inne anomalie.@rec_id
i@src_id
są generowane i stosowane do każdego wiersza. Wydrukowanie kilkuINSERT
wyciągów może mnie zadowolić.t2
również spadnie z klifu. To może nawet iść wolniej niżt3
; Nie jestem pewien. Twój test porównawczy znajduje się w „dziurze z pączkami”, gdziet3
jest chwilowo wolniejszy.