ข้อควรพิจารณาเกี่ยวกับคีย์หลักที่ไม่ใช่จำนวนเต็ม


16

บริบท

ฉันออกแบบฐานข้อมูล (บน PostgreSQL 9.6) ซึ่งจะเก็บข้อมูลจากแอปพลิเคชันแบบกระจาย เนื่องจากลักษณะการกระจายของแอปพลิเคชันฉันไม่สามารถใช้จำนวนเต็มเพิ่มโดยอัตโนมัติ ( SERIAL) เป็นคีย์หลักของฉันเนื่องจากสภาพการแข่งขันที่เป็นไปได้

วิธีแก้ปัญหาตามธรรมชาติคือการใช้ UUID หรือตัวระบุที่ไม่ซ้ำกันทั่วโลก Postgres มาพร้อมกับประเภทในตัวUUIDซึ่งเป็นขนาดที่พอดี

ปัญหาที่ฉันมีกับ UUID เกี่ยวข้องกับการดีบัก: มันเป็นสตริงที่ไม่เป็นมิตรกับมนุษย์ ตัวระบุไม่ff53e96d-5fd7-4450-bc99-111b91875ec5บอกอะไรฉันในขณะACC-f8kJd9xKCdที่ไม่รับประกันว่าจะไม่ซ้ำใคร แต่บอกว่าฉันกำลังจัดการกับACCวัตถุ

จากมุมมองการเขียนโปรแกรมเป็นเรื่องปกติที่จะดีบักเคียวรีแอปพลิเคชันที่เกี่ยวข้องกับวัตถุต่าง ๆ สมมติว่าโปรแกรมเมอร์ค้นหาACCวัตถุ (บัญชี) ที่ORDตาราง (ลำดับ) อย่างไม่ถูกต้อง ด้วยตัวระบุที่มนุษย์สามารถอ่านได้โปรแกรมเมอร์จะระบุปัญหาได้ทันทีในขณะที่ใช้ UUID เขาจะใช้เวลาในการพิจารณาว่ามีอะไรผิดปกติ

ฉันไม่ต้องการเอกลักษณ์ "รับประกัน" ของ UUIDs; ฉันไม่ต้องการห้องพักบางส่วนสำหรับการสร้างปุ่มโดยไม่ให้เกิดความขัดแย้ง แต่ UUID เป็น overkill นอกจากนี้สถานการณ์กรณีที่เลวร้ายที่สุดมันจะไม่ใช่จุดจบของโลกหากเกิดการชน (ฐานข้อมูลปฏิเสธและแอปพลิเคชันสามารถกู้คืนได้) ดังนั้นการพิจารณาถึงความไม่เหมาะสมตัวระบุขนาดเล็ก แต่เป็นมิตรกับมนุษย์จะเป็นทางออกที่ดีสำหรับกรณีการใช้งานของฉัน

การระบุวัตถุแอปพลิเคชัน

ตัวระบุที่ฉันใช้มีรูปแบบต่อไปนี้: {domain}-{string}ซึ่ง{domain}จะถูกแทนที่ด้วยโดเมนวัตถุ (บัญชีคำสั่งผลิตภัณฑ์) และ{string}เป็นสตริงที่สร้างแบบสุ่ม ในบางกรณีอาจทำให้การแทรก a {sub-domain}ก่อนสตริงสุ่ม ลองมองข้ามความยาวของ{domain}และ{string}เพื่อจุดประสงค์ในการรับประกันเอกลักษณ์

รูปแบบอาจมีขนาดคงที่หากช่วยในการจัดทำดัชนี / สอบถามประสิทธิภาพการทำงาน

ปัญหา

รู้ว่า:

  • ฉันต้องการมีคีย์หลักที่มีรูปแบบACC-f8kJd9xKCdดังนี้
  • คีย์หลักเหล่านี้จะเป็นส่วนหนึ่งของหลายตาราง
  • คีย์เหล่านี้ทั้งหมดจะใช้กับการเชื่อมต่อหลายครั้ง / ความสัมพันธ์บนฐานข้อมูล 6NF
  • ตารางส่วนใหญ่จะมีขนาดกลางถึงขนาดใหญ่ (เฉลี่ยประมาณ 1M แถวที่ใหญ่ที่สุดที่มีแถว ~ 100M)

เกี่ยวกับประสิทธิภาพวิธีใดดีที่สุดในการจัดเก็บคีย์นี้

ด้านล่างเป็นวิธีแก้ปัญหาที่เป็นไปได้สี่ข้อ แต่เนื่องจากฉันมีประสบการณ์น้อยในฐานข้อมูลฉันจึงไม่แน่ใจว่าจะเลือกแบบใดดีที่สุด (ถ้ามี)

ถือว่าเป็นโซลูชั่น

1. เก็บเป็นสตริง ( VARCHAR)

(Postgres ไม่สร้างความแตกต่างระหว่างCHAR(n)และVARCHAR(n)ดังนั้นฉันจึงไม่สนใจCHAR)

หลังจากการวิจัยบางอย่างที่ผมเคยพบว่ามีการเปรียบเทียบสตริงพิเศษในการเข้าร่วมการดำเนินงานจะช้ากว่าการใช้VARCHAR INTEGERมันสมเหตุสมผลแล้ว แต่เป็นสิ่งที่ฉันควรกังวลในระดับนี้หรือไม่?

2. จัดเก็บเป็นไบนารี ( bytea)

แตกต่างจาก Postgres, MySQL ไม่มีUUIDประเภทพื้นเมือง มีหลายโพสต์อธิบายวิธีการจัดเก็บ UUID ใช้ 16 ไบต์มีBINARYสนามแทนที่จะเป็น 36 ไบต์VARCHARหนึ่ง โพสต์เหล่านี้ทำให้ฉันมีความคิดในการจัดเก็บคีย์เป็นไบนารี ( byteaบน Postgres)

สิ่งนี้ช่วยประหยัดขนาด แต่ฉันเกี่ยวข้องกับประสิทธิภาพมากกว่า ฉันมีโชคเล็กน้อยในการหาคำอธิบายว่าการเปรียบเทียบแบบใดที่เร็วกว่า: ไบนารีหรือสตริง ฉันเชื่อว่าการเปรียบเทียบแบบไบนารีนั้นเร็วกว่า ถ้าเป็นเช่นนั้นbyteaก็อาจจะดีกว่าVARCHARแม้ว่าตอนนี้โปรแกรมเมอร์จะต้องเข้ารหัส / ถอดรหัสข้อมูลทุกครั้ง

ฉันอาจจะผิด แต่ฉันคิดว่าทั้งสองbyteaและVARCHARจะเปรียบเทียบ (ความเสมอภาค) byte byte (หรือตัวละครโดยตัวละคร) มีวิธี "ข้าม" การเปรียบเทียบแบบทีละขั้นตอนนี้และเพียงแค่เปรียบเทียบ "สิ่งทั้งหมด"? (ฉันไม่คิดอย่างนั้น แต่ไม่มีค่าใช้จ่ายในการตรวจสอบ)

ฉันคิดว่าการจัดเก็บตามที่byteaเป็นทางออกที่ดีที่สุด แต่ฉันสงสัยว่ามีทางเลือกอื่น ๆ ที่ฉันไม่สนใจ นอกจากนี้ข้อกังวลเดียวกับที่ฉันแสดงในโซลูชันที่ 1 ถือเป็นจริง: ค่าใช้จ่ายในการเปรียบเทียบเพียงพอที่ฉันควรกังวลหรือไม่

โซลูชัน "สร้างสรรค์"

ฉันคิดวิธีแก้ปัญหาที่ "สร้างสรรค์" สองอย่างที่สามารถใช้งานได้ฉันไม่แน่ใจในระดับที่ (เช่นถ้าฉันมีปัญหาในการปรับขนาดให้มากกว่าสองพันแถวในตาราง)

3. เก็บเป็นUUIDแต่มี "ฉลาก" ติดอยู่

เหตุผลหลักที่จะไม่ใช้ UUID คือเพื่อให้โปรแกรมเมอร์สามารถดีบักแอปพลิเคชันได้ดีขึ้น แต่จะเกิดอะไรขึ้นถ้าเราสามารถใช้ทั้งคู่: ฐานข้อมูลจะเก็บคีย์ทั้งหมดเป็นUUIDเพียง s เท่านั้น แต่มันจะล้อมวัตถุก่อน / หลังแบบสอบถาม

ยกตัวอย่างเช่นโปรแกรมเมอร์ขอACC-{UUID}ฐานข้อมูลละเว้นส่วนเรียกผลและกลับมาทั้งหมดของพวกเขาเป็นACC-{domain}-{UUID}

อาจจะเป็นไปได้ด้วยการแฮกเกอร์ที่มีขั้นตอนหรือฟังก์ชั่นการจัดเก็บไว้ แต่คำถามบางข้อมาถึง:

  • นี่คือ (ลบ / เพิ่มโดเมนที่แต่ละแบบสอบถาม) เป็นค่าใช้จ่ายมากหรือไม่
  • เป็นไปได้ไหม

ฉันไม่เคยใช้ขั้นตอนหรือฟังก์ชั่นที่เก็บไว้มาก่อนดังนั้นฉันไม่แน่ใจว่าสิ่งนี้จะเป็นไปได้หรือไม่ ใครบางคนสามารถส่องแสงบ้างไหม? ถ้าฉันสามารถเพิ่มเลเยอร์โปร่งใสระหว่างโปรแกรมเมอร์และข้อมูลที่เก็บไว้ดูเหมือนว่าเป็นโซลูชั่นที่สมบูรณ์แบบ

4. (My Favorite) Store เป็น IPv6 cidr

ใช่คุณอ่านถูกต้อง แต่กลับกลายเป็นว่ารูปแบบที่อยู่ IPv6 แก้ปัญหาของฉันได้อย่างสมบูรณ์แบบ

  • ฉันสามารถเพิ่มโดเมนและโดเมนย่อยที่สองสาม octets แรกและใช้ที่เหลือเป็นสตริงแบบสุ่ม
  • อัตราต่อรองการปะทะกันก็โอเค (ฉันจะไม่ใช้ 2 ^ 128 แต่ก็ยังใช้ได้)
  • การเปรียบเทียบความเท่าเทียมกันได้รับการปรับให้เหมาะสม (หวังว่า) ดังนั้นฉันจึงอาจได้รับประสิทธิภาพที่ดีกว่าการใช้เพียงอย่างbyteaเดียว
  • ฉันสามารถทำการเปรียบเทียบที่น่าสนใจเช่นcontainsขึ้นอยู่กับว่าโดเมนและลำดับชั้นถูกแสดงอย่างไร

ตัวอย่างเช่นสมมติว่าฉันใช้รหัส0000เพื่อแทนโดเมน "ผลิตภัณฑ์" ที่สำคัญจะเป็นตัวแทนของสินค้า0000:0db8:85a3:0000:0000:8a2e:0370:73340db8:85a3:0000:0000:8a2e:0370:7334

คำถามหลักที่นี่คือ: เมื่อเทียบกับbyteaมีข้อได้เปรียบหลักหรือข้อเสียในการใช้cidrชนิดข้อมูลใด ๆ


5
มีโหนดแบบกระจายจำนวนเท่าใดที่เป็นไปได้ คุณรู้หมายเลขของพวกเขา (และชื่อ) ล่วงหน้าหรือไม่? คุณคิดว่า PK (หลายคอลัมน์) คอมโพสิตหรือไม่? โดเมน (ขึ้นอยู่กับคำถามแรกของฉัน) บวกกับคอลัมน์อนุกรมธรรมดาอาจเล็กที่สุดง่ายที่สุดและเร็วที่สุด ...
Erwin Brandstetter

@Phil ขอบคุณ! @ErwinBrandstetter เกี่ยวกับแอปพลิเคชันมันถูกออกแบบมาเพื่อปรับขนาดอัตโนมัติตามโหลดดังนั้นจึงมีข้อมูลน้อยมากก่อนเวลา ฉันคิดเกี่ยวกับการใช้ (domain, UUID) เป็น PK แต่จะทำซ้ำ "โดเมน" ทั่วโดเมนจะยังคงอยู่varcharในปัญหาอื่น ๆ อีกมากมาย ฉันไม่รู้เกี่ยวกับโดเมนของ pg ซึ่งเป็นการเรียนรู้ที่ดี ฉันเห็นโดเมนที่ใช้ในการตรวจสอบความถูกต้องหากแบบสอบถามที่ระบุกำลังใช้วัตถุที่ถูกต้อง แต่ก็ยังคงต้องอาศัยการมีดัชนีที่ไม่ใช่จำนวนเต็ม ไม่แน่ใจว่ามีวิธีการ "ปลอดภัย" ที่serialนี่หรือไม่ (ไม่มีขั้นตอนการล็อคหนึ่งครั้ง)
Renato Siqueira Massaro

1
varcharโดเมนที่ไม่จำเป็นต้องเป็น ลองสร้างเป็นFK integerประเภทและเพิ่มตารางการค้นหา ด้วยวิธีนี้คุณสามารถมีทั้งมนุษย์ที่สามารถอ่านได้และคุณจะปกป้องคอมโพสิตของคุณPKจากการแทรก / อัปเดตความผิดปกติ (ใส่โดเมนที่ไม่มีอยู่)
yemet


1
ฉันต้องการมีคีย์หลักที่มีรูปแบบACC-f8kJd9xKCdดังนี้ ” ←ที่ปรากฏเป็นงานเก่าที่ดีคีย์หลักคอมโพสิต
MDCCL

คำตอบ:


5

การใช้ ltree

หาก IPV6 ใช้งานได้ดีเยี่ยม ไม่รองรับ "ACC" 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)

ดูเอกสารสำหรับข้อมูลเพิ่มเติมและผู้ประกอบการ

หากคุณกำลังสร้างรหัสผลิตภัณฑ์ฉันจะ ltree หากคุณต้องการสิ่งที่จะสร้างพวกเขาฉันจะใช้ UUID


1

เพียงแค่เกี่ยวกับการเปรียบเทียบประสิทธิภาพกับ bytea การเปรียบเทียบเครือข่ายจะดำเนินการใน 3 ขั้นตอน: อันดับแรกบนบิตทั่วไปของส่วนเครือข่ายจากนั้นตามความยาวของส่วนเครือข่ายจากนั้นตามที่อยู่ที่ไม่ได้รับการเปิดเผย ดูที่: network_cmp_internal

ดังนั้นควรช้าลงเล็กน้อยจากนั้น bytea ซึ่งไปที่ straigt ไปยัง memcmp ฉันได้ทำการทดสอบอย่างง่าย ๆ บนโต๊ะที่มี 10 ล้านแถวเพื่อค้นหาแบบเดี่ยว:

  • ใช้รหัสตัวเลข (จำนวนเต็ม) เอาฉัน 1000ms
  • ใช้ cidr มันใช้เวลา 1300 มิลลิวินาที
  • ใช้ bytea ใช้เวลา 1250ms

ฉันไม่สามารถพูดได้ว่ามีความแตกต่างระหว่างไบต์และ cidr มาก (แม้ว่าช่องว่างยังคงสอดคล้องกัน) เพียงเพิ่มเติม ifคำสั่ง - เดาว่าไม่เลวร้ายเกินไปสำหรับ tuples 10m

หวังว่ามันจะช่วย - รักที่จะได้ยินสิ่งที่คุณได้เลือก

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.