Bối cảnh
Tôi đang thiết kế một cơ sở dữ liệu (trên PostgreQuery 9.6) sẽ lưu trữ dữ liệu từ một ứng dụng phân tán. Do tính chất phân tán của ứng dụng, tôi không thể sử dụng số nguyên tăng tự động ( SERIAL
) làm khóa chính vì điều kiện chủng tộc tiềm năng.
Giải pháp tự nhiên là sử dụng UUID hoặc mã định danh duy nhất toàn cầu. Postgres đi kèm với một built-in UUID
loại , đó là hoàn toàn phù hợp.
Vấn đề tôi gặp phải với UUID liên quan đến gỡ lỗi: đó là một chuỗi không thân thiện với con người. Mã định danh không ff53e96d-5fd7-4450-bc99-111b91875ec5
cho tôi biết gì, trong khi ACC-f8kJd9xKCd
, trong khi không được đảm bảo là duy nhất, cho tôi biết tôi đang xử lý một ACC
đối tượng.
Từ góc độ lập trình, thông thường để gỡ lỗi các truy vấn ứng dụng liên quan đến một số đối tượng khác nhau. Giả sử lập trình viên tìm kiếm sai ACC
đối tượng (tài khoản) tại bảng ORD
(thứ tự). Với một định danh có thể đọc được bằng con người, lập trình viên ngay lập tức xác định vấn đề, trong khi sử dụng UUID, anh ta sẽ dành thời gian để tìm ra điều gì sai.
Tôi không cần sự duy nhất "được bảo đảm" của UUID; Tôi làm cần một số phòng để tạo ra các phím mà không xung đột, nhưng UUID là quá mức cần thiết. Ngoài ra, trường hợp xấu nhất, đó sẽ không phải là ngày tận thế nếu xảy ra va chạm (cơ sở dữ liệu từ chối và ứng dụng có thể phục hồi). Vì vậy, đánh đổi cân nhắc, một định danh nhỏ hơn nhưng thân thiện với con người sẽ là giải pháp lý tưởng cho trường hợp sử dụng của tôi.
Xác định đối tượng ứng dụng
Mã định danh tôi đã đưa ra có định dạng sau : {domain}-{string}
, nơi {domain}
được thay thế bằng miền đối tượng (tài khoản, đơn hàng, sản phẩm) và {string}
là một chuỗi được tạo ngẫu nhiên. Trong một số trường hợp, thậm chí có thể có ý nghĩa khi chèn a {sub-domain}
trước chuỗi ngẫu nhiên. Chúng ta hãy bỏ qua chiều dài {domain}
và {string}
cho mục đích đảm bảo tính độc đáo.
Định dạng có thể có kích thước cố định nếu nó giúp hiệu suất lập chỉ mục / truy vấn.
Vấn đề
Biết rằng:
- Tôi muốn có các khóa chính với định dạng như
ACC-f8kJd9xKCd
. - Các khóa chính này sẽ là một phần của một số bảng.
- Tất cả các khóa này sẽ được sử dụng trên một số liên kết / mối quan hệ, trên cơ sở dữ liệu 6NF.
- Hầu hết các bảng sẽ có kích thước trung bình đến lớn (trung bình ~ 1M hàng; các bảng lớn nhất với ~ 100M hàng).
Về hiệu suất, cách tốt nhất để lưu trữ khóa này là gì?
Dưới đây là bốn giải pháp khả thi, nhưng vì tôi có ít kinh nghiệm với cơ sở dữ liệu nên tôi không chắc chắn (nếu có) là tốt nhất.
Giải pháp được coi là
1. Lưu trữ dưới dạng chuỗi ( VARCHAR
)
(Postgres không có sự khác biệt giữa CHAR(n)
và VARCHAR(n)
, vì vậy tôi bỏ qua CHAR
).
Sau một số nghiên cứu, tôi đã phát hiện ra rằng so sánh chuỗi với VARCHAR
, đặc biệt là về các hoạt động tham gia, chậm hơn so với sử dụng INTEGER
. Điều này có ý nghĩa, nhưng nó có phải là điều mà tôi nên lo lắng ở quy mô này?
2. Lưu trữ dưới dạng nhị phân ( bytea
)
Không giống như Postgres, MySQL không có UUID
kiểu bản địa . Có một số bài viết giải thích cách lưu trữ UUID bằng trường 16 byte BINARY
, thay vì 36 byte VARCHAR
. Những bài đăng này đã cho tôi ý tưởng lưu trữ khóa dưới dạng nhị phân ( bytea
trên Postgres).
Điều này giúp tiết kiệm kích thước, nhưng tôi quan tâm nhiều hơn đến hiệu suất. Tôi đã có một chút may mắn khi tìm thấy một lời giải thích về việc so sánh nhanh hơn: nhị phân hoặc chuỗi. Tôi tin rằng so sánh nhị phân là nhanh hơn. Nếu có, thì bytea
có lẽ tốt hơn VARCHAR
, mặc dù lập trình viên bây giờ phải mã hóa / giải mã dữ liệu mỗi lần.
Tôi có thể sai, nhưng tôi nghĩ cả hai bytea
và VARCHAR
sẽ so sánh (bằng) byte theo byte (hoặc ký tự theo ký tự). Có cách nào để "bỏ qua" so sánh từng bước này và chỉ đơn giản là so sánh "toàn bộ"? (Tôi không nghĩ vậy, nhưng nó không kiểm tra chi phí).
Tôi nghĩ lưu trữ bytea
là giải pháp tốt nhất, nhưng tôi tự hỏi liệu có bất kỳ giải pháp thay thế nào khác mà tôi bỏ qua không. Ngoài ra, mối quan tâm tương tự mà tôi thể hiện ở giải pháp 1 là đúng: liệu chi phí so sánh có đủ để tôi lo lắng không?
Giải pháp "Sáng tạo"
Tôi đã đưa ra hai giải pháp rất "sáng tạo" có thể hoạt động, tôi chỉ không chắc chắn ở mức độ nào (nghĩa là nếu tôi gặp khó khăn khi nhân rộng chúng lên hơn một vài nghìn hàng trong một bảng).
3. Lưu trữ dưới dạng UUID
nhưng có "nhãn" đính kèm
Lý do chính để không sử dụng UUID là để các lập trình viên có thể gỡ lỗi ứng dụng tốt hơn. Nhưng điều gì sẽ xảy ra nếu chúng ta có thể sử dụng cả hai: cơ sở dữ liệu chỉ lưu trữ tất cả các khóa dưới dạng UUID
s, nhưng nó bao bọc đối tượng trước / sau khi truy vấn được thực hiện.
Ví dụ, lập trình viên yêu cầu ACC-{UUID}
, cơ sở dữ liệu bỏ qua ACC-
phần đó, tìm nạp kết quả và trả về tất cả chúng dưới dạng {domain}-{UUID}
.
Có thể điều này có thể xảy ra với một số tin tặc với các thủ tục hoặc chức năng được lưu trữ, nhưng một số câu hỏi xuất hiện trong đầu:
- Đây có phải (loại bỏ / thêm tên miền tại mỗi truy vấn) một chi phí đáng kể?
- Điều này thậm chí có thể?
Tôi chưa bao giờ sử dụng các thủ tục hoặc chức năng được lưu trữ trước đây, vì vậy tôi không chắc liệu điều này có khả thi hay không. Ai đó có thể làm sáng tỏ? Nếu tôi có thể thêm một lớp trong suốt giữa lập trình viên và dữ liệu được lưu trữ, thì đó có vẻ là một giải pháp hoàn hảo.
4. (Yêu thích của tôi) Lưu trữ dưới dạng IPv6 cidr
Có, bạn đọc nó đúng. Hóa ra định dạng địa chỉ IPv6 giải quyết vấn đề của tôi một cách hoàn hảo .
- Tôi có thể thêm tên miền và tên miền phụ ở một vài octet đầu tiên và sử dụng các tên miền còn lại làm chuỗi ngẫu nhiên.
- Các tỷ lệ cược va chạm là OK. (Tôi sẽ không sử dụng 2 ^ 128, nhưng vẫn ổn.)
- So sánh bình đẳng (hy vọng) được tối ưu hóa, vì vậy tôi có thể có hiệu suất tốt hơn so với việc sử dụng đơn giản
bytea
. - Tôi thực sự có thể thực hiện một số so sánh thú vị, như
contains
, tùy thuộc vào cách các miền và hệ thống phân cấp của chúng được thể hiện.
Ví dụ: giả sử tôi sử dụng mã 0000
để thể hiện tên miền "sản phẩm". Key 0000:0db8:85a3:0000:0000:8a2e:0370:7334
sẽ đại diện cho sản phẩm 0db8:85a3:0000:0000:8a2e:0370:7334
.
Câu hỏi chính ở đây là: so với bytea
, có bất kỳ lợi thế hay bất lợi chính nào khi sử dụng cidr
kiểu dữ liệu không?
varchar
một trong nhiều vấn đề khác. Tôi không biết về tên miền của pg, thật tuyệt khi tìm hiểu về. Tôi thấy các tên miền đang được sử dụng để xác thực nếu một truy vấn nhất định đang sử dụng đúng đối tượng, nhưng nó vẫn dựa vào việc có một chỉ mục không nguyên. Không chắc chắn nếu có một cách "an toàn" sử dụng serial
ở đây (không có một bước khóa).
varchar
. Xem xét làm cho nó một FK
integer
loại và thêm một bảng tra cứu cho nó. Bằng cách đó, bạn có thể có cả khả năng đọc của con người và bạn sẽ bảo vệ tổ hợp của mình PK
khỏi các dị thường chèn / cập nhật (đặt một miền không tồn tại).
text
là tốt hơn so với varchar
. Hãy xem depesz.com/2010/03/02/charx-vs-varcharx-vs-varchar-vs-text và postgresql.org/docs/cản/static/datatype-character.html
ACC-f8kJd9xKCd
. Có vẻ như đó là một công việc cho khóa CHÍNH HÃNG tổng hợp cũ .