गैर-पूर्णांक प्राथमिक प्रमुख विचार


16

प्रसंग

मैं एक डेटाबेस डिजाइन कर रहा हूं (PostgreSQL 9.6 पर) जो एक वितरित एप्लिकेशन से डेटा संग्रहीत करेगा। एप्लिकेशन की वितरित प्रकृति के कारण, मैं SERIALसंभावित दौड़-स्थितियों के कारण अपनी प्राथमिक कुंजी के रूप में ऑटो-इंक्रीमेंट पूर्णांक ( ) का उपयोग नहीं कर सकता ।

प्राकृतिक समाधान एक UUID, या विश्व स्तर पर अद्वितीय पहचानकर्ता का उपयोग करना है। पोस्टग्रेज एक बिल्ट-इन UUIDप्रकार के साथ आता है , जो एक आदर्श फिट है।

UUID के साथ मुझे जो समस्या है वह डीबगिंग से संबंधित है: यह एक गैर-मानव-अनुकूल स्ट्रिंग है। पहचानकर्ता ff53e96d-5fd7-4450-bc99-111b91875ec5मुझे कुछ भी नहीं बताता है, जबकि ACC-f8kJd9xKCd, अद्वितीय होने की गारंटी नहीं है, मुझे बताता है कि मैं एक ACCवस्तु के साथ काम कर रहा हूं ।

एक प्रोग्रामिंग परिप्रेक्ष्य से, कई अलग-अलग वस्तुओं से संबंधित एप्लिकेशन क्वेरी को डीबग करना आम है। मान लीजिए कि प्रोग्रामर गलत तरीके ACCसे ORD(ऑर्डर) टेबल पर (खाता) ऑब्जेक्ट खोजता है । मानव-पढ़ने योग्य पहचानकर्ता के साथ, प्रोग्रामर तुरंत समस्या की पहचान करता है, जबकि यूयूआईडी का उपयोग करते हुए वह कुछ समय बिताएगा जो गलत था।

मुझे UUID की "गारंटीकृत" विशिष्टता की आवश्यकता नहीं है; मैं है संघर्ष के बिना कुंजी पैदा करने के लिए कुछ कमरे की जरूरत है, लेकिन UUID overkill है। इसके अलावा, सबसे खराब स्थिति, यह दुनिया का अंत नहीं होगा अगर टक्कर हुई (डेटाबेस इसे अस्वीकार कर देता है और एप्लिकेशन पुनर्प्राप्त कर सकता है)। इसलिए, व्यापार-नापसंद माना जाता है, एक छोटी लेकिन मानव-अनुकूल पहचानकर्ता मेरे उपयोग के मामले के लिए आदर्श समाधान होगा।

एप्लिकेशन ऑब्जेक्ट्स की पहचान करना

मेरे साथ आया पहचानकर्ता निम्न प्रारूप में है: {domain}-{string}जहां {domain}ऑब्जेक्ट डोमेन (खाता, आदेश, उत्पाद) के साथ बदल दिया गया है और {string}एक बेतरतीब ढंग से उत्पन्न स्ट्रिंग है। कुछ मामलों में, {sub-domain}यादृच्छिक स्ट्रिंग से पहले डालने का भी अर्थ हो सकता है । आइए विशिष्टता की गारंटी के उद्देश्य के लिए {domain}और इसकी लंबाई को अनदेखा करें {string}

यदि अनुक्रमण / क्वेरी प्रदर्शन में मदद करता है तो प्रारूप का एक निश्चित आकार हो सकता है।

समस्या

यह जानते हुए:

  • मैं एक प्रारूप के साथ प्राथमिक कुंजी रखना चाहता हूं ACC-f8kJd9xKCd
  • ये प्राथमिक कुंजियाँ कई तालिकाओं का हिस्सा होंगी।
  • इन सभी कुंजियों का उपयोग 6NF डेटाबेस पर कई जॉइन / रिलेशनशिप पर किया जाएगा।
  • अधिकांश तालिकाओं में मध्यम-से-बड़े आकार (~ 1M पंक्तियों के औसत) ~ 100M पंक्तियों के साथ सबसे बड़े वाले) होंगे।

प्रदर्शन के संबंध में, इस कुंजी को संग्रहीत करने का सबसे अच्छा तरीका क्या है?

नीचे चार संभव समाधान दिए गए हैं, लेकिन चूंकि मुझे डेटाबेस के साथ बहुत कम अनुभव है, इसलिए मैं अनिश्चित हूं (यदि कोई हो) सबसे अच्छा है।

माना समाधान

1. स्ट्रिंग के रूप में स्टोर ( VARCHAR)

(पोस्टग्रैज के बीच कोई अंतर नहीं है CHAR(n)और VARCHAR(n)इसलिए मैं अनदेखा कर रहा हूं CHAR)।

कुछ शोध के बाद, मुझे पता चला है कि स्ट्रिंग की तुलना VARCHAR, विशेष रूप से जुड़ने के संचालन पर, उपयोग करने की तुलना में धीमी है INTEGER। यह समझ में आता है, लेकिन क्या यह कुछ है जो मुझे इस पैमाने पर चिंता करनी चाहिए?

2. बाइनरी के रूप में स्टोर करें ( bytea)

Postgres के विपरीत, MySQL में एक देशी UUIDप्रकार नहीं है । BINARY36-बाइट के बजाय 16-बाइट फ़ील्ड का उपयोग करके UUID को संग्रहीत करने का तरीका बताने वाले कई पोस्ट हैं VARCHAR। इन पोस्टों ने मुझे कुंजी को बाइनरी ( byteaपोस्टग्रेज पर) के रूप में संग्रहीत करने का विचार दिया ।

यह आकार बचाता है, लेकिन मैं प्रदर्शन से अधिक चिंतित हूं। मेरे पास बहुत कम भाग्य था जो एक स्पष्टीकरण खोज रहा था जिस पर तुलना तेज है: द्विआधारी या स्ट्रिंग वाले। मेरा मानना ​​है कि बाइनरी तुलना तेजी से होती है। यदि वे हैं, तो byteaशायद इससे बेहतर है VARCHAR, भले ही प्रोग्रामर को अब हर बार डेटा को एनकोड / डिकोड करना पड़े ।

मैं गलत हो सकता है, लेकिन मैं दोनों लगता है byteaऔर VARCHARबाइट द्वारा (या चरित्र चरित्र द्वारा) की तुलना करेंगे (समानता) बाइट। क्या इस चरण-दर-चरण तुलना को "स्किप" करने का एक तरीका है और बस "पूरी बात" की तुलना करें? (मुझे ऐसा नहीं लगता, लेकिन यह जाँच की लागत नहीं है)।

मुझे लगता है कि भंडारण byteaकरना सबसे अच्छा उपाय है, लेकिन मुझे आश्चर्य है कि क्या कोई अन्य विकल्प है जिसे मैं अनदेखा कर रहा हूं। इसके अलावा, एक ही चिंता जो मैंने समाधान 1 पर व्यक्त की है, वह सच है: तुलनाओं पर ओवरहेड पर्याप्त है जिसके बारे में मुझे चिंता करनी चाहिए?

"सृजनात्मक समाधान

मैं दो बहुत ही "रचनात्मक" समाधानों के साथ आया, जो काम कर सकते हैं, मैं सिर्फ इस हद तक अनिश्चित हूं (यानी अगर मुझे उन्हें एक तालिका में कुछ हज़ार से अधिक पंक्तियों को स्केल करने में परेशानी होगी)।

3. स्टोर करें UUIDलेकिन इसके साथ एक "लेबल" संलग्न करें

यूयूआईडी का उपयोग नहीं करने का मुख्य कारण यह है कि प्रोग्रामर आवेदन को बेहतर तरीके से डिबग कर सकें। लेकिन क्या होगा अगर हम दोनों का उपयोग कर सकते हैं: डेटाबेस सभी कुंजियों को UUIDकेवल s के रूप में संग्रहीत करता है , लेकिन यह क्वेरी के पहले / बाद में ऑब्जेक्ट को लपेटता है।

उदाहरण के लिए, प्रोग्रामर पूछता है ACC-{UUID}, डेटाबेस ACC-भाग की उपेक्षा करता है , परिणाम प्राप्त करता है, और उन सभी को वापस लौटाता है {domain}-{UUID}

हो सकता है कि यह कुछ हैकरी के साथ संग्रहीत प्रक्रियाओं या कार्यों के साथ संभव होगा, लेकिन कुछ सवाल दिमाग में आते हैं:

  • क्या यह (प्रत्येक क्वेरी पर डोमेन को हटाना / जोड़ना) एक पर्याप्त ओवरहेड है?
  • क्या यह भी संभव है?

मैंने पहले कभी संग्रहीत प्रक्रियाओं या कार्यों का उपयोग नहीं किया है, इसलिए मुझे यकीन नहीं है कि क्या यह संभव है। क्या कोई प्रकाश को बहा सकता है? अगर मैं प्रोग्रामर और संग्रहीत डेटा के बीच एक पारदर्शी परत जोड़ सकता हूं, तो यह एक सटीक समाधान लगता है।

4. IPv6 के रूप में (मेरा पसंदीदा) स्टोर cidr

हाँ, आप इसे पढ़ें। यह पता चला है कि IPv6 पता प्रारूप मेरी समस्या को पूरी तरह हल करता है

  • मैं पहले कुछ ऑक्टेट्स में डोमेन और उप-डोमेन जोड़ सकता हूं, और शेष लोगों को यादृच्छिक स्ट्रिंग के रूप में उपयोग कर सकता हूं।
  • टक्कर बाधाओं ठीक हैं। (हालांकि मैं 2 ^ 128 का उपयोग नहीं करूंगा, लेकिन यह अभी भी ठीक है।)
  • समानता तुलना (उम्मीद है) अनुकूलित है, इसलिए मुझे बस उपयोग करने से बेहतर प्रदर्शन मिल सकता है bytea
  • मैं वास्तव में कुछ दिलचस्प तुलना कर सकता हूं, जैसे contains, डोमेन और उनके पदानुक्रम का प्रतिनिधित्व कैसे किया जाता है, इस पर निर्भर करता है।

उदाहरण के लिए, मान लें कि मैं 0000डोमेन "उत्पादों" का प्रतिनिधित्व करने के लिए कोड का उपयोग करता हूं । कुंजी 0000:0db8:85a3:0000:0000:8a2e:0370:7334उत्पाद का प्रतिनिधित्व करेगी 0db8:85a3:0000:0000:8a2e:0370:7334

यहां मुख्य प्रश्न यह है: क्या डेटा प्रकार byteaका उपयोग cidrकरने पर कोई मुख्य लाभ या नुकसान है ?


5
कितने वितरित नोड संभव हैं? क्या आप समय से पहले उनकी संख्या (और नाम) जानते हैं? क्या आप समग्र (बहुरंगी) पीके पर विचार करते थे? एक डोमेन (मेरे पहले प्रश्न पर निर्भर करता है), प्लस एक सादा सीरियल कॉलम सबसे छोटा, सबसे सरल और सबसे तेज़ हो सकता है ...
Erwin Brandstetter

@ फील थैंक्स! @ErwinBrandstetter एप्लिकेशन के बारे में, यह लोड के अनुसार ऑटो-स्केल के लिए डिज़ाइन किया जा रहा है, इसलिए समय से पहले बहुत कम जानकारी है। मैंने पीके के रूप में (डोमेन, यूयूआईडी) का उपयोग करने के बारे में सोचा है, लेकिन यह "डोमेन" को सभी जगह दोहराएगा, डोमेन अभी भी varcharकई अन्य समस्याओं के बीच होगा । मुझे pg के डोमेन के बारे में नहीं पता था, जिसके बारे में सीखना बहुत अच्छा है। मुझे लगता है कि यदि किसी दिए गए क्वेरी का उपयोग सही ऑब्जेक्ट का उपयोग करने के लिए किया जा रहा है तो मान्य करने के लिए डोमेन का उपयोग किया जाता है, लेकिन यह अभी भी एक गैर-पूर्णांक सूचकांक होने पर निर्भर करेगा। यकीन नहीं होता कि serialयहां (एक लॉक स्टेप के बिना) उपयोग करने का एक "सुरक्षित" तरीका है ।
रेनैटो सिकीरा मस्सारो

1
डोमेन जरूरी होने की जरूरत नहीं है varchar। इसे एक FK integerप्रकार बनाने पर विचार करें और इसके लिए एक लुकअप तालिका जोड़ें। इस तरह से आपके पास मानव पठनीयता दोनों हो सकती है और आप अपने समग्र PKको सम्मिलित / अद्यतन विसंगतियों (गैर-मौजूद डोमेन डाल) से बचाएंगे।
यमित

1
textअधिक बेहतर है varchar। पर देखो depesz.com/2010/03/02/charx-vs-varcharx-vs-varchar-vs-text और postgresql.org/docs/current/static/datatype-character.html
pietrop

1
मैं चाहता हूँ कि जैसे प्रारूप के साथ प्राथमिक कुंजी हो ACC-f8kJd9xKCd“PR यह अच्छे पुराने मिश्रित प्राथमिक कुंजी के लिए एक नौकरी प्रतीत होता है ।
एमडीसीसीएल

जवाबों:


5

का उपयोग करते हुए ltree

अगर IPV6 काम करता है, तो बढ़िया है। यह "एसीसी" का समर्थन नहीं करता है। ltreeकर देता है।

एक लेबल पथ शून्य या अधिक लेबल का एक क्रम है जो डॉट्स द्वारा अलग किया जाता है, उदाहरण के लिए L1.L2.L3, एक विशेष नोड के लिए एक श्रेणीबद्ध पेड़ की जड़ से एक पथ का प्रतिनिधित्व करता है। लेबल पथ की लंबाई 65kB से कम होनी चाहिए, लेकिन इसे 2kB के अंतर्गत रखना बेहतर होता है। व्यवहार में यह कोई बड़ी सीमा नहीं है; उदाहरण के लिए, DMOZ कैटलॉग ( http://www.dmoz.org ) में सबसे लंबा लेबल पथ लगभग 240 बाइट्स है।

आप इसे इस तरह उपयोग करेंगे,

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

हम नमूना डेटा बनाते हैं।

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

और वियोला ।।

                                                          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)

अधिक जानकारी और ऑपरेटरों के लिए डॉक्स देखें

यदि आप उत्पाद आईडी बना रहे हैं, तो मैं लेट्री करूंगा। यदि आपको उन्हें बनाने के लिए कुछ चाहिए, तो मैं यूयूआईडी का उपयोग करूंगा।


1

बस बाइट के साथ प्रदर्शन की तुलना के बारे में। नेटवर्क की तुलना 3 चरणों में की जाती है: पहले नेटवर्क भाग के सामान्य बिट्स पर, फिर नेटवर्क भाग की लंबाई पर, और फिर पूरे अनमास्क पते पर। देखें: network_cmp_internal

इसलिए यह थोड़ा धीमा होना चाहिए, इसके बाद बाइटा जो स्टैम्प्ट से मेमकम्प तक जाती है। मैंने एक मेज पर एक साधारण परीक्षण चलाया है जिसमें 10 मिलियन पंक्तियाँ एक ही की तलाश में हैं:

  • संख्यात्मक आईडी (पूर्णांक) का उपयोग करते हुए यह मुझे 1000ms में ले गया।
  • cidr का उपयोग करके इसे 1300ms लिया गया।
  • बायटिया के उपयोग से इसे 1250 मी।

मैं यह नहीं कह सकता कि बाईट और सिडर के बीच बहुत अंतर है (हालांकि अंतर लगातार बना हुआ है) बस अतिरिक्त if बयान - अनुमान है कि 10 मीटर ट्यूपल के लिए बहुत बुरा नहीं है।

आशा है कि यह मदद करता है - यह सुनना अच्छा लगेगा कि आपने क्या चुनना चुना।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.