Konteks
Saya sedang merancang basis data (pada PostgreSQL 9.6) yang akan menyimpan data dari aplikasi terdistribusi. Karena sifat aplikasi terdistribusi, saya tidak dapat menggunakan bilangan bulat kenaikan-otomatis ( SERIAL
) sebagai kunci utama saya karena kondisi ras yang potensial.
Solusi alami adalah dengan menggunakan UUID, atau pengidentifikasi unik secara global. Postgres dilengkapi dengan built-in UUID
jenis , yang cocok.
Masalah yang saya miliki dengan UUID terkait dengan debugging: ini adalah string yang tidak ramah terhadap manusia. Pengenal tidak ff53e96d-5fd7-4450-bc99-111b91875ec5
memberi tahu saya apa pun, sedangkan ACC-f8kJd9xKCd
, meskipun tidak dijamin unik, memberi tahu saya bahwa saya sedang berurusan dengan suatu ACC
objek.
Dari perspektif pemrograman, biasanya untuk men-debug permintaan aplikasi yang berhubungan dengan beberapa objek yang berbeda. Misalkan programmer salah mencari objek ACC
(akun) di ORD
tabel (urutan). Dengan pengenal yang bisa dibaca manusia, programmer langsung mengidentifikasi masalah, saat menggunakan UUID dia akan menghabiskan waktu mencari tahu apa yang salah.
Saya tidak membutuhkan keunikan UUID yang "dijamin"; Saya memang membutuhkan ruang untuk membuat kunci tanpa konflik, tetapi UUID berlebihan. Juga, skenario terburuk, itu tidak akan menjadi akhir dunia jika terjadi tabrakan (database menolaknya dan aplikasi dapat pulih). Jadi, dipertimbangkan pertukaran, pengenal yang lebih kecil namun ramah manusia akan menjadi solusi ideal untuk kasus penggunaan saya.
Mengidentifikasi objek aplikasi
Identifier yang saya buat memiliki format berikut:, di {domain}-{string}
mana {domain}
diganti dengan domain objek (akun, pesanan, produk) dan {string}
merupakan string yang dibuat secara acak. Dalam beberapa kasus, bahkan mungkin masuk akal untuk memasukkan {sub-domain}
sebelum string acak. Mari kita mengabaikan panjangnya {domain}
dan {string}
untuk tujuan menjamin keunikan.
Format dapat memiliki ukuran tetap jika membantu kinerja pengindeksan / pencarian.
Masalah
Mengetahui bahwa:
- Saya ingin memiliki kunci utama dengan format seperti
ACC-f8kJd9xKCd
. - Kunci primer ini akan menjadi bagian dari beberapa tabel.
- Semua kunci ini akan digunakan pada beberapa sambungan / hubungan, pada basis data 6NF.
- Sebagian besar tabel memiliki ukuran sedang hingga besar (rata-rata ~ 1M baris; yang terbesar dengan ~ 100M baris).
Mengenai kinerja, apa cara terbaik untuk menyimpan kunci ini?
Di bawah ini adalah empat solusi yang mungkin, tetapi karena saya memiliki sedikit pengalaman dengan database, saya tidak yakin yang mana (jika ada) yang terbaik.
Solusi yang dipertimbangkan
1. Simpan sebagai string ( VARCHAR
)
(Postgres tidak membuat perbedaan antara CHAR(n)
dan VARCHAR(n)
, jadi saya mengabaikan CHAR
).
Setelah beberapa penelitian, saya telah menemukan bahwa perbandingan string dengan VARCHAR
, khusus pada bergabung dengan operasi, lebih lambat daripada menggunakan INTEGER
. Ini masuk akal, tetapi apakah itu sesuatu yang harus saya khawatirkan pada skala ini?
2. Simpan sebagai biner ( bytea
)
Tidak seperti Postgres, MySQL tidak memiliki UUID
tipe asli . Ada beberapa pos yang menjelaskan cara menyimpan UUID menggunakan bidang 16-byte BINARY
, bukan 36-byte VARCHAR
. Posting ini memberi saya ide untuk menyimpan kunci sebagai biner ( bytea
di Postgres).
Ini menghemat ukuran, tapi saya lebih mementingkan kinerja. Saya kurang beruntung menemukan penjelasan perbandingan mana yang lebih cepat: biner atau string. Saya percaya perbandingan biner lebih cepat. Jika ya, maka bytea
mungkin lebih baik daripada VARCHAR
, meskipun programmer sekarang harus menyandikan / mendekode data setiap saat.
Saya mungkin salah, tapi saya pikir keduanya bytea
dan VARCHAR
akan membandingkan (kesetaraan) byte demi byte (atau karakter demi karakter). Apakah ada cara untuk "melewatkan" perbandingan ini selangkah demi selangkah dan hanya membandingkan "semuanya"? (Saya rasa tidak, tetapi tidak perlu biaya pengecekan).
Saya pikir menyimpan sebagai bytea
solusi terbaik, tapi saya ingin tahu apakah ada alternatif lain yang saya abaikan. Juga, kekhawatiran yang sama yang saya ungkapkan pada solusi 1 benar: apakah biaya overhead pada perbandingan cukup yang harus saya khawatirkan?
Solusi "Kreatif"
Saya datang dengan dua solusi yang sangat "kreatif" yang dapat bekerja, saya hanya tidak yakin pada tingkat mana (yaitu jika saya akan mengalami kesulitan meningkatkan mereka ke lebih dari beberapa ribu baris dalam sebuah tabel).
3. Simpan sebagai UUID
tetapi dengan "label" yang melekat padanya
Alasan utama untuk tidak menggunakan UUID adalah agar programmer dapat lebih baik men-debug aplikasi. Tetapi bagaimana jika kita dapat menggunakan keduanya: database menyimpan semua kunci sebagai UUID
s saja, tetapi membungkus objek sebelum / setelah query dibuat.
Sebagai contoh, programmer meminta ACC-{UUID}
, database mengabaikan ACC-
bagian itu, mengambil hasilnya, dan mengembalikan semuanya sebagai {domain}-{UUID}
.
Mungkin ini bisa dilakukan dengan peretasan dengan prosedur atau fungsi tersimpan, tetapi beberapa pertanyaan muncul di benak:
- Apakah ini (menghapus / menambahkan domain pada setiap permintaan) overhead yang besar?
- Apakah ini mungkin?
Saya tidak pernah menggunakan prosedur atau fungsi tersimpan sebelumnya, jadi saya tidak yakin apakah ini mungkin. Adakah yang bisa menjelaskan? Jika saya dapat menambahkan lapisan transparan antara programmer dan data yang tersimpan, sepertinya solusi yang sempurna.
4. Simpan (favorit saya) sebagai IPv6 cidr
Ya, Anda membacanya dengan benar. Ternyata format alamat IPv6 menyelesaikan masalah saya dengan sempurna .
- Saya dapat menambahkan domain dan sub-domain pada beberapa oktet pertama, dan menggunakan yang tersisa sebagai string acak.
- The kemungkinan tabrakan yang OK. (Saya tidak akan menggunakan 2 ^ 128, tetapi masih OK.)
- Perbandingan kesetaraan (semoga) dioptimalkan, jadi saya mungkin mendapatkan kinerja yang lebih baik daripada hanya menggunakan
bytea
. - Saya benar-benar dapat melakukan beberapa perbandingan yang menarik, seperti
contains
, tergantung pada bagaimana domain dan hierarki mereka diwakili.
Misalnya, saya menggunakan kode 0000
untuk mewakili domain "produk". Kunci 0000:0db8:85a3:0000:0000:8a2e:0370:7334
akan mewakili produk 0db8:85a3:0000:0000:8a2e:0370:7334
.
Pertanyaan utama di sini adalah: dibandingkan dengan bytea
, apakah ada kelebihan atau kekurangan utama dalam menggunakan cidr
tipe data?
varchar
salah satu masalah lainnya. Saya tidak tahu tentang domain pg, yang bagus untuk dipelajari. Saya melihat domain yang digunakan untuk memvalidasi jika kueri yang diberikan menggunakan objek yang benar, tetapi masih bergantung pada memiliki indeks non-integer. Tidak yakin apakah ada cara "aman" untuk menggunakan di serial
sini (tanpa satu langkah kunci).
varchar
. Pertimbangkan untuk menjadikannya FK
integer
tipe dan tambahkan tabel pencarian untuknya. Dengan begitu Anda dapat memiliki keterbacaan manusia dan Anda akan melindungi komposit Anda PK
dari menyisipkan / memperbarui anomali (menempatkan domain yang tidak ada).
text
lebih disukai daripada varchar
. Lihatlah depesz.com/2010/03/02/charx-vs-varcharx-vs-varchar-vs-text dan postgresql.org/docs/current/static/datatype-character.html
ACC-f8kJd9xKCd
. ”← Tampaknya itu adalah pekerjaan untuk komposit PRIMARY KEY tua yang baik .