ชนิดข้อมูลที่เหมาะสมที่สุดสำหรับเขตข้อมูล MD5 คืออะไร


35

เรากำลังออกแบบระบบที่รู้กันว่าอ่านยาก (ตามคำสั่งของการอ่านหมื่นครั้งต่อนาที)

  • มีตารางnamesที่ทำหน้าที่จัดเรียงรีจิสทรีกลาง แต่ละแถวมีtextเขตข้อมูลrepresentationและไม่ซ้ำกันkeyซึ่งเป็นแฮช MD5 ของสิ่งrepresentationนั้น 1ตารางนี้มีระเบียนหลายสิบล้านระเบียนและคาดว่าจะเติบโตเป็นพันล้านตลอดอายุการใช้งานแอปพลิเคชัน
  • มีตารางอื่น ๆ อีกหลายสิบตาราง (ของสคีมาที่แตกต่างกันอย่างมากและจำนวนเรคคอร์ด) ที่อ้างอิงถึงnamesตาราง ระเบียนใดก็ตามที่ระบุในตารางใดตารางหนึ่งเหล่านี้รับประกันว่าจะมี a name_keyซึ่งเป็น foreign key ไปยังnamesตาราง

1: อนึ่งตามที่คุณคาดไว้ระเบียนในตารางนี้จะไม่เปลี่ยนรูปเมื่อมีการเขียน

สำหรับตารางใดก็ตามที่ไม่ใช่namesตารางแบบสอบถามที่พบบ่อยที่สุดจะเป็นไปตามรูปแบบนี้:

SELECT list, of, fields 
FROM table 
WHERE name_key IN (md5a, md5b, md5c...);

ฉันต้องการปรับให้เหมาะสมสำหรับการอ่าน ฉันสงสัยว่าจุดแรกของฉันควรจะลดขนาดของดัชนี (แม้ว่าฉันจะไม่ได้รับการพิสูจน์ว่าผิด)

คำถาม:
อะไรคือ / ชนิดข้อมูลที่ดีที่สุดสำหรับkeyและname_keyคอลัมน์คืออะไร?
มีเหตุผลที่จะใช้hex(32)มากกว่าbit(128)? BTREEหรือGIN?

คำตอบ:


41

ชนิดข้อมูลuuidเป็นที่ที่ดีที่สุดที่เหมาะสำหรับงาน มีขนาด 16 ไบต์เท่านั้นเมื่อเทียบกับ RAM ขนาด 37 ไบต์สำหรับการเป็นตัวแทนvarcharหรือ text(หรือ 33 ไบต์บนดิสก์ แต่จำนวนคี่จะต้องใช้การขยายในหลาย ๆ กรณีเพื่อให้ได้40ไบต์อย่างมีประสิทธิภาพ) และuuidชนิดมีข้อดีมากกว่า

ตัวอย่าง:

SELECT md5('Store hash for long string, maybe for index?')::uuid AS md5_hash

รายละเอียดและคำอธิบายเพิ่มเติม:

คุณอาจพิจารณาฟังก์ชั่นการแฮช (อื่น ๆ ) ที่ถูกกว่าหากคุณไม่ต้องการส่วนประกอบการเข้ารหัสของ md5 แต่ฉันจะใช้ md5 สำหรับกรณีการใช้งานของคุณ (ส่วนใหญ่เป็นแบบอ่านอย่างเดียว)

คำเตือน : สำหรับกรณีของคุณ ( immutable once writtenบริการ) การทำงานขึ้นอยู่กับ (หลอกธรรมชาติ) PKดี แต่สิ่งเดียวกันจะเป็นความเจ็บปวดที่textเป็นไปได้ในการอัปเดต คิดว่าการแก้ไขการพิมพ์ผิด: PK และดัชนีขึ้นอยู่ทั้งหมดคอลัมน์ FK ในdozens of other tablesและการอ้างอิงอื่น ๆ จะต้องเปลี่ยนเช่นกัน ตารางและดัชนีขยายตัวปัญหาการล็อคการอัปเดตช้าการอ้างอิงที่สูญหาย ...

หากtextสามารถเปลี่ยนแปลงการทำงานปกติตัวแทน PKก็จะเป็นทางเลือกที่ดีกว่า ผมขอแนะนำให้bigserialคอลัมน์ (ช่วง-9223372036854775808 to +9223372036854775807- ที่เก้า Quintillion 223 quadrillion 372000000000000 สามสิบหกบางสิ่งบางอย่างพันล้าน ) billions of rowsค่าแตกต่างกันสำหรับ อาจเป็นความคิดที่ดีในกรณีใด ๆ : 8แทน16ไบต์สำหรับคอลัมน์ FK และดัชนีหลายสิบรายการ!) หรือUUID แบบสุ่มสำหรับความสำคัญมากกว่าหรือระบบกระจาย คุณสามารถจัดเก็บ md5 ที่กล่าวไว้ (เช่นuuid) เพิ่มเติมเพื่อค้นหาแถวในตารางหลักจากข้อความต้นฉบับได้อย่างรวดเร็ว ที่เกี่ยวข้อง:

สำหรับคำถามของคุณ:


หากต้องการระบุความคิดเห็นของ @ Daniel : หากคุณต้องการตัวแทนที่ไม่มีเครื่องหมายขีดคั่นให้ลบเครื่องหมายขีดคั่นเพื่อแสดง:

SELECT replace('90b7525e-84f6-4850-c2ef-b407fae3f271', '-', '')

แต่ฉันจะไม่รบกวน การเป็นตัวแทนเริ่มต้นนั้นใช้ได้ และปัญหาไม่ใช่การแสดงที่นี่จริง ๆ

หากฝ่ายอื่นควรมีแนวทางที่แตกต่างกันและโยนสายอักขระโดยไม่มีเครื่องหมายขีดคั่นในการผสมนั่นก็ไม่ใช่ปัญหาเช่นกัน Postgres ยอมรับหลายการแสดงข้อความที่เหมาะสมเป็น input uuidสำหรับ เอกสารประกอบ :

PostgreSQL ยังยอมรับรูปแบบทางเลือกต่อไปนี้สำหรับการป้อนข้อมูล: ใช้ตัวเลขตัวพิมพ์ใหญ่, รูปแบบมาตรฐานที่ล้อมรอบด้วยเครื่องหมายวงเล็บ, ไม่ใส่เครื่องหมายยัติภังค์บางส่วนหรือทั้งหมด, เพิ่มเครื่องหมายขีดคั่นหลังจากกลุ่มสี่หลักใด ๆ ตัวอย่างคือ:

A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11
{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}
a0eebc999c0b4ef8bb6d6bb9bd380a11
a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11
{a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11}

ยิ่งไปกว่านั้นmd5()ฟังก์ชั่นกลับtextมาคุณจะใช้decode()ในการแปลงbyteaและการแสดงเริ่มต้นของที่ :

SELECT decode(md5('Store hash for long string, maybe for index?'), 'hex')

\220\267R^\204\366HP\302\357\264\007\372\343\362q

คุณจะต้องencode()รับข้อความที่เป็นต้นฉบับอีกครั้ง:

SELECT encode(my_md5_as_bytea, 'hex');

ยิ่งไปกว่านั้นค่าที่เก็บไว้byteaจะมีขนาด 20 ไบต์ใน RAM (และ 17 ไบต์บนดิสก์, 24 กับการเติมเต็ม ) เนื่องจากโอเวอร์เฮดภายในvarlenaซึ่งไม่เอื้ออำนวยต่อขนาดและประสิทธิภาพของดัชนีอย่างง่าย

ทุกอย่างทำงานได้ดีuuidที่นี่


1
สิ่งนี้ถูกกฎหมายสำหรับ "uuid" หรือไม่ โปรดขอโทษด้วยถ้าฉันเชื่องช้าเกินไป แต่ฉันคิดว่าสิ่งที่ฉันเห็นคือประเภทข้อมูล "uuid" มุ่งเน้นไปที่การจัดเก็บตัวเลขที่มีความยาว 16 octets ในรูปแบบไบนารี แต่คำว่า "uuid" แสดงให้เห็นถึงอัลกอริธึมการสร้าง / การแฮชเฉพาะรวมถึงการแสดงข้อความแบบเดิมใน 5 บล็อกของอักขระเลขฐานสิบหกที่คั่นด้วยเครื่องหมายขีดกลาง หากชื่อประเภทนี้แนะนำอย่างยิ่งในการสร้าง UUID / GUID มันเป็นความเข้าใจผิดเล็กน้อยสำหรับโปรแกรมเมอร์อย่างน้อยจะใช้ประเภทนี้เพื่อเก็บแฮชหรือไม่?
Andrew Wolfe

2
@AndrewWolfe: ชอบธรรมอย่างแท้จริง IMO ไม่ได้รับการดำเนินการไปโดยชื่อ มันเป็นเอนทิตีขนาด 16 ไบต์พร้อมชุดคาสต์ประเภทที่กำหนดและตรรกะอินพุต / เอาท์พุตที่สะดวกสบาย กรณีที่ใช้งานจริงต้องใช้ "ตัวระบุที่ไม่ซ้ำกัน" คุณสามารถจัดเก็บข้อมูลอักขระทุกชนิดในtextคอลัมน์ได้เช่นกันแม้ว่าจะไม่ใช่ "ข้อความ" ก็ตาม
Erwin Brandstetter

จะเกิดอะไรขึ้นถ้าแฮช MD5 ถูกแปลงเป็นเบส 64 คุณจะเก็บมันอย่างไร
PirateApp

2
@PirateApp SELECT encode(decode('tZmffOd5Tbh8yXaVlZfRJQ==', 'base64'), 'hex')::uuid;ถอดรหัสมันเป็นครั้งแรก:
nyov

1
@nyov: uuidเป็นชนิด 16- ไบต์ที่ไม่สามารถเก็บผลลัพธ์ของอัลกอริทึม SHA ใด ๆ ที่ผลิตระหว่าง 160 ถึง 512 บิต ไม่มีประเภทที่คล้ายกันที่เหมาะกับการแจกจ่ายมาตรฐานของ Postgres คุณสามารถสร้างหนึ่ง ... ล้มเหลวที่เริ่มต้นbyteaเช่น - pg_cryptoทำ
Erwin Brandstetter

2

ฉันจะเก็บ MD5 ไว้ในคอลัมน์textหรือ varcharไม่มีความแตกต่างของประสิทธิภาพระหว่างชนิดข้อมูลอักขระต่างๆ คุณอาจต้องการจำกัดความยาวของค่า md5 โดยใช้varchar(xxx)เพื่อให้แน่ใจว่าค่า md5 ไม่เกินความยาวที่แน่นอน

รายการขนาดใหญ่ในปกติไม่เร็วจริง ๆ ควรทำดังนี้:

with md5vals (md5) as (
  values ('one'), ('two'), ('three')
)
select t.*
from the_table t
  join md5vals m on t.name_key  = m.md5;

ตัวเลือกอื่นที่บางครั้งกล่าวว่าเร็วขึ้นคือการใช้อาร์เรย์:

select t.*
from the_table t
where name_key = ANY (array['one', 'two', 'three']);

ในขณะที่คุณกำลังเปรียบเทียบเพื่อความเท่าเทียมกันดัชนี BTree ปกติน่าจะดี แบบสอบถามทั้งสองควรจะสามารถใช้ประโยชน์จากดัชนีดังกล่าวได้ (โดยเฉพาะถ้ามีการเลือกเพียงส่วนน้อยของแถว


มีเหตุผลใดที่จะไม่ใช้บิต (128) หรือฐานสิบหก (32)? รับประกันความคุ้มค่าเพื่อให้พอดีกับเขตข้อมูลดังกล่าวอย่างเป็นระเบียบและฉันต้องการปกป้องจากค่าที่ไม่ดีที่ได้รับมอบหมาย
bobocopy

3
@bococopy: ไม่มีประเภทข้อมูล "hex" ใน Postgres ฉันไม่เคยใช้bitประเภทนี้เลยดังนั้นฉันจึงไม่สามารถแสดงความคิดเห็นได้ เมื่อพิจารณาจากจำนวนแถวที่คาดไว้ข้อเสนอแนะของ Erwin น่าจะดีกว่าเนื่องจากการประหยัดพื้นที่ที่คุณได้รับด้วยการจัดเก็บเป็น UUID
a_horse_with_no_name

-1

ตัวเลือกอื่นคือใช้คอลัมน์ 4 INTEGER หรือ 2 BIGINT


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