Tamsayı olmayan birincil anahtar hususları


16

bağlam

Dağıtılmış bir uygulamadan veri depolayacak bir veritabanı (PostgreSQL 9.6'da) tasarlıyorum. Uygulamanın dağıtılmış doğası SERIALnedeniyle, potansiyel artış koşulları nedeniyle otomatik artış tamsayılarını ( ) birincil anahtarım olarak kullanamıyorum.

Doğal çözüm, bir UUID veya global olarak benzersiz bir tanımlayıcı kullanmaktır. Postgres birlikte bir dahili UUIDtip mükemmel bir seçimdir.

UUID ile ilgili sorunum hata ayıklama ile ilgili: insan dostu olmayan bir dize. Tanımlayıcı ff53e96d-5fd7-4450-bc99-111b91875ec5bana hiçbir şey söylemese ACC-f8kJd9xKCdde, benzersiz olduğu garanti edilmese de, bir ACCnesneyle uğraştığımı söylüyor .

Programlama açısından bakıldığında, birkaç farklı nesne ile ilgili uygulama sorgularında hata ayıklamak yaygındır. Programcının (sipariş) tablosunda yanlış bir ACC(hesap) nesnesi aradığını varsayalım ORD. İnsan tarafından okunabilen bir tanımlayıcıyla, programcı sorunu anında tanımlar, UUID'leri kullanırken neyin yanlış olduğunu anlamak için biraz zaman harcayacaktır.

UUID'lerin "garantili" benzersizliğine ihtiyacım yok; Ben do çakışma olmadan anahtarları oluşturmak için biraz yer gerekir, ancak UUID overkill. Ayrıca, en kötü durum senaryosu, bir çarpışma olursa dünyanın sonu olmazdı (veritabanı onu reddeder ve uygulama kurtarabilir). Dolayısıyla, değiş tokuşlar göz önüne alındığında, daha küçük ama insan dostu bir tanımlayıcı kullanım durumum için ideal çözüm olacaktır.

Uygulama nesnelerini belirleme

Geldiğim tanımlayıcı şu biçime sahip: {domain}-{string}burada {domain}nesne etki alanı (hesap, sipariş, ürün) ile değiştirilir ve {string}rastgele oluşturulmuş bir dizedir. Bazı durumlarda, {sub-domain}rastgele dizeden önce a eklemek bile mantıklı olabilir . Eşsizliği garanti etmek amacıyla {domain}ve uzunluğunu göz ardı edelim {string}.

Biçimlendirme / sorgulama performansına yardımcı oluyorsa biçim sabit bir boyuta sahip olabilir.

Sorun

Bilerek:

  • Bir biçime sahip birincil anahtarlara sahip olmak istiyorum ACC-f8kJd9xKCd.
  • Bu birincil anahtarlar birkaç tablonun parçası olacaktır.
  • Tüm bu anahtarlar 6NF veritabanında birçok birleşme / ilişki üzerinde kullanılacaktır.
  • Çoğu tablo orta ila büyük-ish boyutuna sahip olacaktır (ortalama ~ 1M satır; en büyük olanlar ~ 100M satır).

Performansla ilgili olarak, bu anahtarı depolamanın en iyi yolu nedir?

Aşağıda dört olası çözüm bulunmaktadır, ancak veritabanlarıyla ilgili çok az deneyimim olduğu için hangisinin (hangisinin) en iyisi olduğundan emin değilim.

Dikkate alınan çözümler

1. dize olarak depola ( VARCHAR)

(Postgres CHAR(n)ve arasında hiçbir fark yaratmaz VARCHAR(n), bu yüzden görmezden geliyorum CHAR).

Bazı araştırmalardan sonra VARCHAR, özellikle birleştirme işlemlerinde dize karşılaştırmasının kullanmaktan daha yavaş olduğunu öğrendim INTEGER. Bu mantıklı, ama bu ölçekte endişelenmem gereken bir şey mi?

2. İkili ( bytea) olarak depola

Postgres'in aksine, MySQL'in yerel bir UUIDtürü yoktur. Bir UUID'nin BINARY36 baytlık VARCHARbir alan yerine 16 baytlık bir alan kullanarak nasıl saklanacağını açıklayan birkaç yazı vardır . Bu gönderiler bana anahtarı ikili ( byteaPostgres'te) olarak saklama fikrini verdi .

Bu boyut tasarrufu sağlar, ancak performansla daha fazla ilgileniyorum. İkili veya dize olanlar: karşılaştırma daha hızlı bir açıklama bulmak için biraz şans vardı. İkili karşılaştırmanın daha hızlı olduğuna inanıyorum. Eğer öyleyse bytea, VARCHARprogramcı şimdi her zaman verileri kodlamak / kodunu çözmek zorunda olsa bile , muhtemelen daha iyidir .

Yanlış olabilirim, ama her ikisini de düşünüyorum byteave VARCHAR(eşitlik) bayt bayt (veya karakter karakter) karşılaştırır. Bu adım adım karşılaştırmayı "atlamanın" ve basitçe "her şeyi" karşılaştırmanın bir yolu var mı? (Ben öyle düşünmüyorum, ama kontrol etmenin maliyeti yok).

Depolamayı byteaen iyi çözüm olarak düşünüyorum , ama görmezden geldiğim başka alternatifler olup olmadığını merak ediyorum. Ayrıca, çözüm 1'de ifade ettiğim aynı endişe de geçerli: karşılaştırmalardaki yük endişe etmem yeterli mi?

"Yaratıcı çözümler

Çalışabilecek iki çok "yaratıcı" çözüm buldum, sadece ne ölçüde emin değilim (yani bir tablodaki birkaç binden fazla satıra ölçeklendirme konusunda sorun yaşarsam).

3. UUID"Etiketi" yapıştırılmış olarak ancak depolayın

UUID'lerin kullanılmamasının ana nedeni, programcıların uygulamada daha iyi hata ayıklaması yapabilmesidir. Ancak her ikisini de kullanabilirsek: veritabanı tüm anahtarları UUIDyalnızca s olarak depolar , ancak sorgular yapılmadan önce / sonra nesneyi sarar.

Örneğin, programcı ister ACC-{UUID}, veritabanı ACC-parçayı yok sayar , sonuçları getirir ve tümünü olarak döndürür {domain}-{UUID}.

Belki de bu, saklı yordamlar veya işlevlere sahip bazı bilgisayar korsanları ile mümkün olabilir, ancak bazı sorular akla geliyor:

  • Bu (her sorguda alan adının kaldırılması / eklenmesi) önemli bir ek yük mü?
  • Bu mümkün mü?

Daha önce saklı yordamları veya işlevleri hiç kullanmadım, bu yüzden bunun mümkün olup olmadığından emin değilim. Birisi biraz ışık tutabilir mi? Programcı ve saklanan veriler arasına şeffaf bir katman ekleyebilirsem, mükemmel bir çözüm gibi görünüyor.

4. (Favori) IPv6 olarak depola cidr

Evet, doğru okudunuz. IPv6 adres formatının sorunumu mükemmel bir şekilde çözdüğü ortaya çıktı .

  • İlk birkaç sekizlide etki alanları ve alt etki alanları ekleyebilir ve kalanları rastgele dize olarak kullanabilirim.
  • Çarpışma oran Tamam. (Ben olsa 2 ^ 128 kullanarak olmazdı, ama yine de sorun yok.)
  • Eşitlik karşılaştırmaları (umarım) optimize edilmiştir, bu yüzden sadece kullanmaktan daha iyi performans elde edebilirim bytea.
  • containsEtki alanlarının ve hiyerarşilerinin nasıl temsil edildiğine bağlı olarak bazı ilginç karşılaştırmalar yapabilirim .

Örneğin, 0000"ürünler" alanını temsil etmek için kod kullandığımı varsayalım . Anahtar 0000:0db8:85a3:0000:0000:8a2e:0370:7334ürünü temsil eder 0db8:85a3:0000:0000:8a2e:0370:7334.

Buradaki ana soru şudur: veri türünü byteakullanmanın herhangi bir temel avantajı veya dezavantajı var cidrmı?


5
Kaç tane dağıtılmış düğüm mümkün? Numaralarını (ve isimlerini) önceden biliyor musunuz? Kompozit (çok sütunlu) PK'ları düşündünüz mü? Bir alan (ilk soruma bağlı olarak), ayrıca düz bir seri sütun en küçük, en basit ve en hızlı olabilir ...
Erwin Brandstetter

@Phil teşekkürler! @ErwinBrandstetter Uygulama ile ilgili olarak, yüke göre otomatik olarak ölçeklendirilecek şekilde tasarlandığından, çok az bilgi var. PK olarak (domain, UUID) kullanmayı düşündüm, ancak bu "domain" i tekrarlayacaktır, domain hala varcharbirçok problem arasında olacaktır . PG'nin alanlarını bilmiyordum, bu da öğrenmesi harika. Belirli bir sorgu doğru nesneyi kullanıyorsa doğrulamak için kullanılan etki alanları görüyorum, ancak yine de tamsayı olmayan bir dizin olması güveniyor. serialBurada "güvenli" bir yol olup olmadığından emin değilsiniz (tek bir kilit adımı olmadan).
Renato Siqueira Massaro

1
Etki alanının mutlaka bir olması gerekmez varchar. Bunu bir FK integertür yapmayı düşünün ve bunun için bir arama tablosu ekleyin. Bu şekilde hem insan tarafından okunabilir olabilirsiniz hem de kompozitinizi PKekleme / güncelleme anormalliklerinden (var olmayan bir alan adı koyarak) koruyabilirsiniz .
yemet


1
Bir biçime sahip birincil anahtarlara sahip olmak istiyorum ACC-f8kJd9xKCd. ”← Bu eski iyi kompozit PRIMARY KEY için bir iş gibi görünüyor .
MDCCL

Yanıtlar:


5

kullanma ltree

IPV6 çalışıyorsa harika. "ACC" yi desteklemiyor. ltreeyapar.

Bir etiket yolu, hiyerarşik bir ağacın kökünden belirli bir düğüme giden bir yolu temsil eden, noktalarla ayrılmış sıfır veya daha fazla etiket dizisidir, örneğin L1.L2.L3. Bir etiket yolunun uzunluğu 65kB'den az olmalıdır, ancak 2kB'nin altında tutmak tercih edilir. Uygulamada bu büyük bir sınırlama değildir; örneğin, DMOZ kataloğundaki ( http://www.dmoz.org ) en uzun etiket yolu yaklaşık 240 bayttır.

Bunu böyle kullanırdın,

CREATE EXTENSION ltree;
SELECT replace('ACC-f8kJd9xKCd', '-', '.')::ltree;

Örnek veriler yaratıyoruz.

SELECT x, (
  CASE WHEN x%7=0 THEN 'ACC'
    WHEN x%3=0 THEN 'XYZ'
    ELSE 'COM'
  END ||'.'|| md5(x::text)
  )::ltree
FROM generate_series(1,10000) AS t(x);

CREATE INDEX ON foo USING GIST (ltree);
ANALYZE foo;


  x  |                ltree                 
-----+--------------------------------------
   1 | COM.c4ca4238a0b923820dcc509a6f75849b
   2 | COM.c81e728d9d4c2f636f067f89cc14862c
   3 | XYZ.eccbc87e4b5ce2fe28308fd9f2a7baf3
   4 | COM.a87ff679a2f3e71d9181a67b7542122c
   5 | COM.e4da3b7fbbce2345d7772b0674a318d5
   6 | XYZ.1679091c5a880faf6fb5e6087eb1b2dc
   7 | ACC.8f14e45fceea167a5a36dedd4bea2543
   8 | COM.c9f0f895fb98ab9159f51fd0297e236d

Ve viyola ..

                                                          QUERY PLAN                                                          
------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on foo  (cost=103.23..234.91 rows=1414 width=57) (actual time=0.422..0.908 rows=1428 loops=1)
   Recheck Cond: ('ACC'::ltree @> ltree)
   Heap Blocks: exact=114
   ->  Bitmap Index Scan on foo_ltree_idx  (cost=0.00..102.88 rows=1414 width=0) (actual time=0.389..0.389 rows=1428 loops=1)
         Index Cond: ('ACC'::ltree @> ltree)
 Planning time: 0.133 ms
 Execution time: 1.033 ms
(7 rows)

Daha fazla bilgi ve operatörler için dokümanlara bakın

Ürün kimliklerini oluşturuyorsanız, ben de bilirim. Bunları oluşturmak için bir şeye ihtiyacınız varsa, UUID kullanırdım.


1

Sadece bytea ile performans karşılaştırmasıyla ilgili. ağ karşılaştırması 3 adımda yapılır: önce ağ parçasının ortak bitlerinde, daha sonra ağ parçasının uzunluğunda ve ardından maskelenmemiş adresin tamamında. bkz. network_cmp_internal

bu yüzden memcmp için giden gider biraz daha yavaş bytea olmalıdır. Tek bir tane arayan 10 milyon sıralı bir masada basit bir test yaptım:

  • sayısal kimliği (tamsayı) kullanarak bana 1000 ms sürdü.
  • cidr kullanarak 1300ms sürdü.
  • bytea kullanarak 1250 ms sürdü.

Baytea ve cidr arasında çok fazla fark olduğunu söyleyemem (her ne kadar boşluk tutarlı kalsa da) Sadece ek ifaçıklama - sanırım bu 10m tuples için çok kötü değil.

Umarım yardımcı olur - neyi seçtiğini duymak isterdim.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.