Thiết kế cơ sở dữ liệu: làm thế nào để xử lý vấn đề lưu trữ trên mạng của Google?


18

Tôi khá chắc chắn rất nhiều ứng dụng, ứng dụng quan trọng, ngân hàng và cứ thế làm điều này hàng ngày.

Ý tưởng đằng sau tất cả đó là:

  • tất cả các hàng phải có lịch sử
  • tất cả các liên kết phải được gắn kết
  • thật dễ dàng để thực hiện các yêu cầu để có được các cột "hiện tại"
  • khách hàng đã mua những thứ lỗi thời vẫn nên xem những gì họ đã mua mặc dù sản phẩm này không còn là một phần của danh mục nữa

và như thế.

Đây là những gì tôi muốn làm và tôi sẽ giải thích những vấn đề tôi gặp phải.

Tất cả các bảng của tôi sẽ có các cột đó:

  • id
  • id_origin
  • date of creation
  • start date of validity
  • start end of validity

Và đây là những ý tưởng cho các hoạt động CRUD:

  • tạo = chèn hàng mới với id_origin= id, date of creation= now, start date of validity= now, end date of validity= null (= có nghĩa là bản ghi hoạt động hiện tại)
  • cập nhật =
    • đọc = đọc tất cả các bản ghi với end date of validity== null
    • cập nhật bản ghi "hiện tại" end date of validity= null với end date of validity= ngay bây giờ
    • tạo một cái mới với các giá trị mới và end date of validity= null (= có nghĩa là bản ghi hoạt động hiện tại)
  • xóa = cập nhật bản ghi "hiện tại" end date of validity= null với end date of validity= ngay bây giờ

Vì vậy, đây là vấn đề của tôi: với các hiệp hội nhiều-nhiều. Hãy lấy một ví dụ với các giá trị:

  • Bảng A (id = 1, id_origin = 1, start = now, end = null)
  • Bảng A_B (start = now, end = null, id_A = 1, id_B = 48)
  • Bảng B (id = 48, id_origin = 48, start = now, end = null)

Bây giờ tôi muốn cập nhật bảng A, bản ghi id = 1

  • Tôi đánh dấu bản ghi id = 1 bằng end = now
  • Tôi chèn một giá trị mới vào bảng A và ... chết tiệt, tôi đã mất mối quan hệ của mình A_B trừ khi tôi nhân đôi mối quan hệ này ... điều này cũng sẽ kết thúc với một bảng:

  • Bảng A (id = 1, id_origin = 1, start = now, end = now + 8mn)

  • Bảng A (id = 2, id_origin = 1, start = now + 8mn, end = null)
  • Bảng A_B (start = now, end = null, id_A = 1, id_B = 48)
  • Bảng A_B (start = now, end = null, id_A = 2, id_B = 48)
  • Bảng B (id = 48, id_origin = 48, start = now, end = null)

Và ... tôi cũng có một vấn đề khác: mối quan hệ A_B: tôi sẽ đánh dấu (id_A = 1, id_B = 48) có lỗi thời hay không (A - id = 1 đã lỗi thời, nhưng không phải B - 48)?

Làm thế nào để đối phó với điều này?

Tôi phải thiết kế nó ở quy mô lớn: sản phẩm, đối tác, v.v.

Kinh nghiệm của bạn về điều này là gì? Làm thế nào bạn sẽ làm (làm thế nào bạn đã làm)?

-- Biên tập

Tôi đã tìm thấy bài viết rất thú vị này , nhưng nó không giải quyết đúng đắn về "lỗi thời" (= những gì tôi đang hỏi thực sự)


Làm thế nào về việc sao chép dữ liệu của bản ghi cập nhật trước khi nó được cập nhật sang một bản ghi mới với một id mới giữ danh sách lịch sử được liên kết với trường id_hist_prev. Vì vậy, id của hồ sơ hiện tại không bao giờ được thay đổi

Thay vì phát minh lại bánh xe, bạn đã xem xét sử dụng, ví dụ, Lưu trữ dữ liệu Flashback trên Oracle?
Jack Douglas

Câu trả lời:


4

Tôi không rõ nếu các yêu cầu này dành cho mục đích kiểm toán hoặc chỉ là tài liệu tham khảo lịch sử đơn giản như với CRM và giỏ hàng.

Dù bằng cách nào, hãy xem xét có một bảng chính và bảng chính cho từng khu vực chính, nơi điều này là bắt buộc. "Chính" sẽ chỉ có các mục hiện tại / hoạt động trong khi "main_archive" sẽ có một bản sao của tất cả mọi thứ đi vào chính. Chèn / cập nhật vào main_archive có thể là một kích hoạt từ chèn / cập nhật vào chính. Xóa đối với main_archive sau đó có thể chạy trong một khoảng thời gian dài hơn, nếu có.

Đối với các vấn đề liên quan như Cust X đã mua Sản phẩm Y, cách dễ nhất để giải quyết mối quan tâm tham chiếu của bạn về mã xác thực -> sản phẩm_archive là không bao giờ xóa các mục khỏi sản phẩm_archive. Nói chung, churn nên thấp hơn nhiều trong bảng đó vì vậy kích thước không nên quá đáng lo ngại.

HTH.


2
Câu trả lời tuyệt vời, nhưng tôi muốn nói thêm rằng một lợi ích khác của việc có bảng lưu trữ là chúng có xu hướng không được chuẩn hóa, làm cho báo cáo về dữ liệu đó hiệu quả hơn nhiều. Xem xét nhu cầu báo cáo của ứng dụng của bạn với phương pháp này là tốt.
maple_shaft

1
Trong hầu hết các cơ sở dữ liệu tôi thiết kế tất cả các bảng 'chính' đều có tiền tố giống như tên sản phẩm LP_và mỗi bảng quan trọng đều có một tương đương LH_, với các trình kích hoạt chèn các hàng lịch sử vào chèn, cập nhật, xóa. Nó không hoạt động cho tất cả các trường hợp nhưng nó đã là một mô hình vững chắc cho những việc tôi làm.

Tôi đồng ý - nếu phần lớn các truy vấn dành cho các hàng "hiện tại", bạn có thể sẽ có được lợi thế hoàn hảo bằng cách phân vùng hiện tại từ lịch sử trong hai bảng. Một quan điểm có thể liên kết họ lại với nhau, như một sự thuận tiện. Bằng cách này, các trang dữ liệu với các hàng hiện tại được kết hợp với nhau và có thể ở trong bộ đệm tốt hơn và bạn không phải liên tục đủ điều kiện truy vấn cho dữ liệu hiện tại với logic ngày.
onupdatecascade

1
@onupdatecascade: Lưu ý rằng (ít nhất là trong một số RDBMS), bạn có thể đặt các chỉ mục trên UNIONchế độ xem đó, cho phép bạn thực hiện những điều tuyệt vời như thực thi một ràng buộc duy nhất đối với cả hồ sơ hiện tại và lịch sử.
Jon của tất cả các giao dịch

5 năm sau, tôi đã thực hiện hàng tấn thứ và tất cả thời gian tôi lấy lại cho bạn ý tưởng của bạn. Điều duy nhất tôi thay đổi là trên các bảng lịch sử, tôi có một cột " id" và " id_ref". id_reflà một tài liệu tham khảo cho ý tưởng thực tế của bảng. Ví dụ: personperson_h. trong person_hTôi có " id" và " id_ref" có id_refliên quan đến ' person.id' vì vậy tôi có thể có nhiều hàng có cùng person.id(= khi một hàng personđược sửa đổi) và tất idcả các bảng của tôi đều tự động.
Olivier Pons

2

Điều này có một số chồng chéo với lập trình chức năng; Cụ thể là khái niệm bất biến.

Bạn có một bảng được gọi PRODUCTvà một bảng khác được gọi PRODUCTVERSIONhoặc tương tự. Khi bạn thay đổi một sản phẩm bạn không thực hiện cập nhật, bạn chỉ cần chèn một PRODUCTVERSIONhàng mới . Để có bản mới nhất, bạn có thể lập chỉ mục bảng theo số phiên bản (desc), dấu thời gian (desc) hoặc bạn có thể có cờ ( LatestVersion).

Bây giờ nếu bạn có một cái gì đó tham chiếu một sản phẩm, bạn có thể quyết định nó trỏ đến bảng nào. Nó trỏ đến PRODUCTthực thể (luôn đề cập đến sản phẩm này) hay PRODUCTVERSIONthực thể (chỉ đề cập đến phiên bản này của sản phẩm)?

Nó trở nên phức tạp. Nếu bạn có hình ảnh của sản phẩm thì sao? Họ phải trỏ đến bảng phiên bản, vì chúng có thể được thay đổi, nhưng trong nhiều trường hợp, họ sẽ không và bạn không muốn sao chép dữ liệu một cách không cần thiết. Điều đó có nghĩa là bạn cần một PICTUREbảng và PRODUCTVERSIONPICTUREmối quan hệ nhiều-nhiều.


1

Tôi đã triển khai tất cả nội dung từ đây với 4 trường nằm trên tất cả các bảng của tôi:

  • Tôi
  • ngày tháng
  • date_valids_start
  • date_valids_end

Mỗi lần sửa đổi một bản ghi, tôi sao chép nó, đánh dấu bản ghi trùng lặp là "cũ" = date_validity_end=NOW()và bản ghi hiện tại là bản ghi tốt date_validity_start=NOW()date_validity_end=NULL.

Bí quyết là về nhiều mối quan hệ nhiều đến một và nhiều: nó hoạt động mà không cần chạm vào chúng! Đó là tất cả về các truy vấn phức tạp hơn: để truy vấn một bản ghi trong một ngày chính xác (= không phải bây giờ), tôi có cho mỗi lần tham gia và cho bảng chính, để thêm các ràng buộc đó:

WHERE (
  (date_validity_start<=:dateparam AND date_validity_end IS NULL)
  OR
  (date_validity_start<=:dateparam AND date_validity_start>=:dateparam)
)

Vì vậy, với các sản phẩm và thuộc tính (nhiều đến nhiều mối quan hệ):

SELECT p.*,a.*

FROM products p

JOIN products_attributes pa
ON pa.id_product = p.id
AND (
  (pa.date_validity_start<=:dateparam AND pa.date_validity_end IS NULL)
  OR
  (pa.date_validity_start<=:dateparam AND pa.date_validity_start>=:dateparam)
)

JOIN attributes a
ON a.id = pa.id_attribute
AND (
  (a.date_validity_start<=:dateparam AND a.date_validity_end IS NULL)
  OR
  (a.date_validity_start<=:dateparam AND a.date_validity_start>=:dateparam)
)

WHERE (
  (p.date_validity_start<=:dateparam AND p.date_validity_end IS NULL)
  OR
  (p.date_validity_start<=:dateparam AND p.date_validity_start>=:dateparam)
)

0

Còn cái này thì sao? Nó có vẻ đơn giản và khá hiệu quả cho những gì tôi đã làm trong quá khứ. Trong bảng "lịch sử" của bạn, sử dụng PK khác. Vì vậy, trường "CustomerID" của bạn là PK trong bảng Khách hàng của bạn, nhưng trong bảng "lịch sử", PK của bạn là "NewCustomerID". "CustomerID" trở thành một trường chỉ đọc khác. Điều đó khiến "CustomerID" không thay đổi trong Lịch sử và tất cả các mối quan hệ của bạn vẫn được giữ nguyên.


Ý tưởng rất hay Những gì tôi đã làm rất giống nhau: Tôi sao chép bản ghi và đánh dấu bản mới là "lỗi thời" để bản ghi hiện tại vẫn như cũ. Lưu ý Tôi muốn tạo một trình kích hoạt trên mỗi bảng nhưng mysql cấm sửa đổi bảng khi bạn vào trình kích hoạt của bảng này. PostGRESQL làm điều này. Máy chủ SQL làm điều này. Oracle làm điều này. Nói tóm lại, MySQL vẫn còn một chặng đường rất dài và lần tới tôi sẽ suy nghĩ kỹ khi chọn máy chủ cơ sở dữ liệu của mình.
Olivier Pons
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.