Kiểu dữ liệu tối ưu cho trường MD5 là gì?


35

Chúng tôi đang thiết kế một hệ thống được biết là nặng về đọc (theo thứ tự hàng chục ngàn lượt đọc mỗi phút).

  • Có một bảng namesphục vụ như một loại đăng ký trung tâm. Mỗi hàng có một texttrường representationvà duy nhất keylà hàm băm MD5 representation. 1 Bảng này hiện có hàng chục triệu bản ghi và dự kiến ​​sẽ tăng lên hàng tỷ trong suốt vòng đời của ứng dụng.
  • Có hàng tá các bảng khác (gồm các lược đồ và số lượng bản ghi rất khác nhau) làm tham chiếu đến namesbảng. Bất kỳ bản ghi đã cho nào trong một trong các bảng này đều được đảm bảo có một name_key, có chức năng là khóa ngoại đối với namesbảng.

1: Ngẫu nhiên, như bạn có thể mong đợi, các bản ghi trong bảng này là bất biến một khi được viết.

Đối với bất kỳ bảng đã cho nào ngoài namesbảng, truy vấn phổ biến nhất sẽ theo mẫu này:

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

Tôi muốn tối ưu hóa cho hiệu suất đọc. Tôi nghi ngờ rằng điểm dừng đầu tiên của tôi nên là giảm thiểu kích thước của các chỉ số (mặc dù tôi không ngại bị chứng minh là sai ở đó).

Câu hỏi:
Các loại dữ liệu tối ưu cho các cột keyvà là name_keygì?
Có một lý do để sử dụng hex(32)hơn bit(128)? BTREEhay GIN?

Câu trả lời:


41

Các kiểu dữ liệu uuidhoàn toàn phù hợp với nhiệm vụ. Nó chỉ chiếm 16 byte so với 37 byte trong RAM cho varcharhoặc textđại diện. (Hoặc 33 byte trên đĩa, nhưng số lẻ sẽ yêu cầu đệm trong nhiều trường hợp để tạo ra nó 40 byte hiệu quả.) Và uuidloại này có một số lợi thế hơn.

Thí dụ:

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

Chi tiết và giải thích thêm:

Bạn có thể xem xét các hàm băm khác (rẻ hơn) nếu bạn không cần thành phần mật mã của md5, nhưng tôi sẽ sử dụng md5 cho trường hợp sử dụng của bạn (chủ yếu là chỉ đọc).

Một lời cảnh báo : Đối với trường hợp của bạn ( immutable once written) một PK phụ thuộc chức năng (giả tự nhiên) là tốt. Nhưng điều tương tự sẽ là một nỗi đau nơi cập nhật textlà có thể. Hãy nghĩ đến việc sửa lỗi chính tả: PK và tất cả các chỉ mục phụ thuộc, các cột FK dozens of other tablesvà các tham chiếu khác cũng sẽ phải thay đổi. Sự phình to của bảng và chỉ mục, sự cố khóa, cập nhật chậm, tài liệu tham khảo bị mất, ...

Nếu textcó thể thay đổi trong hoạt động bình thường, PK thay thế sẽ là lựa chọn tốt hơn. Tôi đề nghị một bigserialcột (phạm vi -9223372036854775808 to +9223372036854775807- đó là chín triệu hai trăm hai mươi ba triệu ba trăm bảy mươi hai nghìn tỷ ba mươi sáu tỷ tỷ ) giá trị khác biệt cho billions of rows. Có thể là một ý tưởng tốt trong mọi trường hợp: 8 thay vì 16 byte cho hàng chục cột và chỉ mục FK!). Hoặc một UUID ngẫu nhiên cho các hồng y hoặc hệ thống phân tán lớn hơn nhiều . Bạn luôn có thể lưu trữ md5 (as uuid) nói thêm để tìm các hàng trong bảng chính từ văn bản gốc một cách nhanh chóng. Liên quan:

Đối với truy vấn của bạn :


Để giải quyết nhận xét của @ Daniel : Nếu bạn thích một đại diện không có dấu gạch ngang, hãy xóa dấu gạch nối để hiển thị:

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

Nhưng tôi sẽ không làm phiền. Các đại diện mặc định là tốt. Và vấn đề thực sự không phải là đại diện ở đây.

Nếu các bên khác nên có một cách tiếp cận khác và ném các chuỗi không có dấu gạch nối vào hỗn hợp, thì đó cũng không phải là vấn đề. Postgres chấp nhận một số biểu diễn văn bản hợp lý làm đầu vào cho a uuid. Tài liệu :

PostgreSQL cũng chấp nhận các hình thức thay thế sau đây cho đầu vào: sử dụng chữ số viết hoa, định dạng chuẩn được bao quanh bởi dấu ngoặc, bỏ qua một số hoặc tất cả dấu gạch nối, thêm dấu gạch nối sau bất kỳ nhóm bốn chữ số nào. Ví dụ là:

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

Hơn nữa, md5()hàm trả về text, bạn sẽ sử dụng decode()để chuyển đổi thànhbytea và đại diện mặc định của đó là:

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

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

Bạn sẽ phải encode()một lần nữa để có được đại diện văn bản gốc:

SELECT encode(my_md5_as_bytea, 'hex');

Trên hết, các giá trị được lưu trữ byteasẽ chiếm 20 byte trong RAM (và 17 byte trên đĩa, 24 với phần đệm ) do chi phí nội bộvarlena , đặc biệt bất lợi cho kích thước và hiệu suất của các chỉ mục đơn giản.

Tất cả mọi thứ hoạt động có lợi cho một uuidở đây.


1
Điều này có hợp pháp cho "uuid" không? Xin thứ lỗi cho tôi nếu tôi quá tầm phào, nhưng tôi nghĩ rằng những gì tôi đang thấy là kiểu dữ liệu "uuid" được định hướng để lưu trữ các số có độ dài 16 octet ở định dạng nhị phân. Nhưng thuật ngữ "uuid" gợi ý một thuật toán tạo / băm cụ thể cũng như biểu diễn văn bản thông thường trong 5 khối ký tự thập lục phân tách biệt. Nếu tên loại này gợi ý mạnh mẽ đến việc tạo UUID / GUID, thì ít nhất nó có gây hiểu lầm cho các lập trình viên hay không, sử dụng loại này để lưu trữ hàm băm?
Andrew Wolfe

2
@AndrewWolfe: Hoàn toàn hợp pháp, IMO. Đừng mang tên đó đi . Đó là một thực thể 16 byte với một tập hợp thuận tiện các kiểu phôi được cung cấp và logic đầu vào / đầu ra. Trường hợp trong tầm tay thậm chí thực sự đòi hỏi một "định danh duy nhất". Bạn cũng có thể lưu trữ tất cả các loại dữ liệu ký tự trong textcác cột - ngay cả khi đó hoàn toàn không phải là "văn bản".
Erwin Brandstetter

Điều gì xảy ra nếu hàm băm MD5 được chuyển đổi sang cơ sở 64, bạn sẽ lưu trữ nó như thế nào
PirateApp

2
@PirateApp, giải mã nó trước : SELECT encode(decode('tZmffOd5Tbh8yXaVlZfRJQ==', 'base64'), 'hex')::uuid;.
nyov

1
@nyov: uuidlà loại 16 byte không thể lưu trữ kết quả của bất kỳ thuật toán SHA nào tạo ra từ 160 đến 512 bit. Không có loại tương tự phù hợp với phân phối chuẩn của Postgres. Bạn có thể tạo một ... Không thành công, mặc định byteagiống như pg_crypto .
Erwin Brandstetter

2

Tôi sẽ lưu trữ MD5 trong một texthoặc varcharcột. Không có sự khác biệt về hiệu suất giữa các loại dữ liệu ký tự khác nhau. Bạn có thể muốn hạn chế độ dài của các giá trị md5 bằng cách sử dụng varchar(xxx)để đảm bảo giá trị md5 không bao giờ vượt quá một độ dài nhất định.

Danh sách IN lớn thường không thực sự nhanh, tốt hơn là làm điều gì đó như thế này:

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

Một tùy chọn khác đôi khi được cho là nhanh hơn là sử dụng một mảng:

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

Vì bạn chỉ đang so sánh về sự bình đẳng, một chỉ số BTree thông thường sẽ ổn. Cả hai truy vấn sẽ có thể sử dụng một chỉ mục như vậy (đặc biệt nếu chỉ chọn một phần nhỏ của các hàng.


Bất kỳ lý do cụ thể không sử dụng bit (128) hoặc hex (32)? Các giá trị được đảm bảo phù hợp gọn gàng trong một lĩnh vực như vậy và tôi muốn bảo vệ khỏi các giá trị xấu được chỉ định.
bobocopy

3
@bobocopy: không có kiểu dữ liệu "hex" trong Postgres. Tôi chưa bao giờ sử dụng bitloại này vì vậy tôi không thể nhận xét về điều đó. Với số lượng hàng dự kiến ​​của bạn, đề xuất của Erwin có vẻ tốt hơn vì tiết kiệm không gian bạn có được khi lưu trữ này dưới dạng UUID
a_horse_with_no_name

-1

Một tùy chọn khác là sử dụng 4 cột INTEGER hoặc 2 BIGINT.


2
Tất nhiên, về kích thước lưu trữ, một trong hai tùy chọn sẽ phù hợp, nhưng nó sẽ tiện lợi như thế nào khi làm việc với? Có lẽ bạn có thể mở rộng câu trả lời của mình để đưa ra một ví dụ hoặc giải thích điều đó.
Andriy M
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.