Saya telah menggunakan UUID di sistem saya untuk sementara waktu sekarang karena berbagai alasan mulai dari logging hingga korelasi yang tertunda. Format yang saya gunakan berubah karena saya menjadi kurang naif dari:
VARCHAR(255)
VARCHAR(36)
CHAR(36)
BINARY(16)
Ketika saya mencapai final BINARY(16)
, saya mulai membandingkan kinerja dengan integer kenaikan-otomatis dasar. Tes dan hasil ditunjukkan di bawah ini, tetapi jika Anda hanya ingin ringkasan, ini menunjukkan bahwa INT AUTOINCREMENT
dan BINARY(16) RANDOM
memiliki kinerja yang identik pada rentang data hingga 200.000 (database telah diisi sebelumnya sebelum tes).
Awalnya saya ragu menggunakan UUID sebagai kunci utama, dan memang masih demikian, namun saya melihat potensi di sini untuk membuat database fleksibel yang bisa menggunakan keduanya. Sementara banyak orang menekankan kelebihan dari keduanya, kerugian apa yang dibatalkan dengan menggunakan kedua tipe data ini?
PRIMARY INT
UNIQUE BINARY(16)
Kasus penggunaan untuk jenis pengaturan ini akan menjadi kunci utama tradisional untuk hubungan antar-tabel, dengan pengidentifikasi unik yang digunakan untuk hubungan antar-sistem.
Apa yang saya coba temukan pada dasarnya adalah perbedaan efisiensi antara kedua pendekatan. Selain ruang disk quadruple yang digunakan, yang sebagian besar dapat diabaikan setelah data tambahan ditambahkan, mereka tampaknya sama.
Skema:
-- 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 */;
Masukkan patokan:
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;
}
Pilih patokan:
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;
}
Tes:
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
benchmark_insert($pdo, 1000);
benchmark_select($pdo, 100000);
Hasil:
INSERTION
=============================
INC ID: 3
GUID: 2
SPLIT GUID: 3
SELECTION
=============================
INC ID: 5
GUID: 5
SPLIT GUID: 6
sumber
BINARY(16)
Saya pikir kita berdua sepakat adalah cara paling efisien untuk menyimpan UUID, tetapi mengenaiUNIQUE
indeks, haruskah saya menggunakan indeks biasa? Byte dibuat menggunakan RNGs yang aman secara kriptografis, jadi haruskah saya bergantung sepenuhnya pada keacakan, dan melupakan pemeriksaan?innodb_buffer_pool_size
adalah 70% dari ram yang tersedia.'Rick James' mengatakan dalam jawaban yang diterima: "Memiliki AUTO_INCREMENT UNIK dan UUID UNIK di tabel yang sama adalah sia-sia". Tetapi tes ini (saya melakukannya di mesin saya) menunjukkan fakta yang berbeda.
Sebagai contoh: dengan tes (T2) saya membuat tabel dengan (INT AUTOINCREMENT) PRIMARY dan UNIQUE BINARY (16) dan bidang lain sebagai judul, kemudian saya memasukkan lebih dari 1,6M baris dengan kinerja sangat baik, tetapi dengan pengujian lain (T3) Saya melakukan hal yang sama tetapi hasilnya lambat setelah memasukkan 300.000 baris saja.
Ini adalah hasil pengujian saya:
Jadi biner (16) UNIQUE dengan penambahan otomatis int_id lebih baik daripada biner (16) UNIQUE tanpa penambahan otomatis int_id.
Saya melakukan tes yang sama lagi dan mencatat lebih banyak detail. ini adalah kode lengkap dan hasil perbandingan antara (T2) dan (T3) seperti yang dijelaskan di atas.
(T2) buat tbl2 (mysql):
(T3) buat tbl3 (mysql):
Ini adalah kode pengujian lengkap, ini memasukkan 600.000 catatan ke tbl2 atau tbl3 (kode vb.net):
Hasil untuk (T2):
Hasil untuk (T3):
sumber
innodb_buffer_pool_size
? Dari mana "ukuran tabel" berasal?COMMIT
, bukan sebelumnya. Ini dapat menghilangkan beberapa anomali lainnya.@rec_id
dan@src_id
sedang dihasilkan serta diterapkan pada setiap baris. Mencetak beberapaINSERT
pernyataan mungkin memuaskan saya.t2
juga akan jatuh dari tebing. Ini mungkin bahkan pergi lebih lambat darit3
; Saya tidak yakin. Benchmark Anda berada di "lubang donat"t3
yang sementara waktu lebih lambat.