Cách kiểm soát phiên bản bản ghi trong cơ sở dữ liệu


176

Hãy nói rằng tôi có một bản ghi trong cơ sở dữ liệu và cả quản trị viên và người dùng bình thường đều có thể cập nhật.

Bất cứ ai cũng có thể đề xuất một cách tiếp cận / kiến ​​trúc tốt làm thế nào để kiểm soát phiên bản trong mọi thay đổi trong bảng này để có thể khôi phục một bản ghi cho bản sửa đổi trước đó.

Câu trả lời:


164

Giả sử bạn có một FOObảng mà quản trị viên và người dùng có thể cập nhật. Hầu hết thời gian bạn có thể viết các truy vấn đối với bảng FOO. Những ngày hạnh phúc.

Sau đó, tôi sẽ tạo một FOO_HISTORYbảng. Điều này có tất cả các cột của FOObảng. Khóa chính giống như FOO cộng với cột RevisionNumber. Có một khóa ngoại từ FOO_HISTORYđến FOO. Bạn cũng có thể thêm các cột liên quan đến sửa đổi, chẳng hạn như UserId và RevisionDate. Dân số RevisionNumbers theo kiểu ngày càng tăng trên tất cả các *_HISTORYbảng (nghĩa là từ một chuỗi Oracle hoặc tương đương). Không dựa vào đó chỉ có một thay đổi trong một giây (tức là không đưa RevisionDatevào khóa chính).

Bây giờ, mỗi khi bạn cập nhật FOO, ngay trước khi bạn thực hiện cập nhật, bạn sẽ chèn các giá trị cũ vào FOO_HISTORY. Bạn làm điều này ở một số cấp độ cơ bản trong thiết kế của mình để các lập trình viên không thể vô tình bỏ lỡ bước này.

Nếu bạn muốn xóa một hàng từ FOObạn có một số lựa chọn. Xếp tầng và xóa tất cả lịch sử hoặc thực hiện xóa logic bằng cách gắn cờ FOOlà đã xóa.

Giải pháp này là tốt khi bạn chủ yếu quan tâm đến các giá trị hiện tại và chỉ thỉnh thoảng trong lịch sử. Nếu bạn luôn cần lịch sử thì bạn có thể đặt ngày bắt đầu và ngày kết thúc hiệu quả và giữ tất cả các hồ sơ trong FOOchính nó. Mỗi truy vấn sau đó cần kiểm tra những ngày đó.


1
Bạn có thể thực hiện cập nhật bảng kiểm toán với các kích hoạt cơ sở dữ liệu nếu lớp truy cập dữ liệu của bạn không hỗ trợ trực tiếp. Ngoài ra, không khó để xây dựng một trình tạo mã để tạo ra các trình kích hoạt sử dụng nội quan từ từ điển dữ liệu hệ thống.
Mối quan tâmOfTunbridgeWells

44
Tôi woyuld khuyên bạn thực sự nên chèn dữ liệu mới , không phải dữ liệu trước đó, vì vậy bảng lịch sử có tất cả dữ liệu. Mặc dù nó lưu trữ dữ liệu dư thừa, nó loại bỏ các trường hợp đặc biệt cần thiết để xử lý tìm kiếm trong cả hai bảng khi cần dữ liệu lịch sử.
Nerdfest

6
Cá nhân tôi khuyên bạn không nên xóa bất cứ điều gì (trì hoãn điều này với một hoạt động vệ sinh cụ thể) và có một cột "loại hành động" để chỉ định xem nó có được chèn / cập nhật / xóa hay không. Để xóa, bạn sao chép hàng như bình thường, nhưng đặt "xóa" trong cột loại hành động.
Neil Barnwell

3
@Hydrargyrum Một bảng chứa các giá trị hiện tại sẽ hoạt động tốt hơn so với chế độ xem của bảng lịch sử. Bạn cũng có thể muốn xác định khóa ngoại tham chiếu các giá trị hiện tại.
Thế chiến.

2
There is a foreign key from FOO_HISTORY to FOO': ý tưởng tồi, tôi muốn xóa hồ sơ từ foo mà không thay đổi lịch sử. bảng lịch sử nên được chèn chỉ trong sử dụng bình thường.
Jasen 12/2/2015

46

Tôi nghĩ rằng bạn đang tìm kiếm phiên bản nội dung của các bản ghi cơ sở dữ liệu (như StackOverflow thực hiện khi ai đó chỉnh sửa câu hỏi / câu trả lời). Một điểm khởi đầu tốt có thể đang xem xét một số mô hình cơ sở dữ liệu sử dụng theo dõi sửa đổi .

Ví dụ tốt nhất mà tôi nghĩ đến là MediaWiki, công cụ Wikipedia. So sánh sơ đồ cơ sở dữ liệu ở đây , đặc biệt là bảng sửa đổi .

Tùy thuộc vào công nghệ bạn đang sử dụng, bạn sẽ phải tìm một số thuật toán khác biệt / hợp nhất tốt.

Kiểm tra câu hỏi này nếu nó cho .NET.


30

Trong thế giới BI, bạn có thể thực hiện điều này bằng cách thêm startDate và endDate vào bảng bạn muốn phiên bản. Khi bạn chèn bản ghi đầu tiên vào bảng, startDate được điền, nhưng endDate là null. Khi bạn chèn bản ghi thứ hai, bạn cũng cập nhật endDate của bản ghi đầu tiên với startDate của bản ghi thứ hai.

Khi bạn muốn xem bản ghi hiện tại, bạn chọn bản ghi trong đó endDate là null.

Điều này đôi khi được gọi là Kích thước thay đổi từ từ loại 2 . Xem thêm TupleVersioning


Không phải bảng của tôi phát triển khá lớn bằng cách sử dụng phương pháp này?
Niels Bosma

1
Có, nhưng bạn có thể đối phó với điều đó bằng cách lập chỉ mục và / hoặc phân vùng bảng. Ngoài ra, sẽ chỉ có một số ít các bàn lớn. Hầu hết sẽ nhỏ hơn nhiều.
Mối quan tâmOfTunbridgeWells

Nếu tôi không nhầm, sự sụp đổ duy nhất ở đây là nó có giới hạn thay đổi một lần mỗi giây không?
pimbrouwers

@pimbrouwers có, cuối cùng nó phụ thuộc vào độ chính xác của các trường và chức năng cư trú của chúng.
Dave Neeley

9

Nâng cấp lên SQL 2008.

Hãy thử sử dụng Theo dõi thay đổi SQL, trong SQL 2008. Thay vì dấu thời gian và hack cột mộ, bạn có thể sử dụng tính năng mới này để theo dõi các thay đổi trên dữ liệu trong cơ sở dữ liệu của mình.

Theo dõi thay đổi MSDN SQL 2008


7

Chỉ muốn thêm rằng một giải pháp tốt cho vấn đề này là sử dụng cơ sở dữ liệu tạm thời . Nhiều nhà cung cấp cơ sở dữ liệu cung cấp tính năng này hoặc ra khỏi hộp hoặc thông qua một phần mở rộng. Tôi đã sử dụng thành công phần mở rộng bảng tạm thời với PostgreSQL nhưng những người khác cũng có nó. Bất cứ khi nào bạn cập nhật một bản ghi trong cơ sở dữ liệu, cơ sở dữ liệu cũng giữ phiên bản trước của bản ghi đó.


6

Hai lựa chọn:

  1. Có bảng lịch sử - chèn dữ liệu cũ vào bảng lịch sử này bất cứ khi nào bản gốc được cập nhật.
  2. Bảng kiểm toán - lưu trữ các giá trị trước và sau - chỉ cho các cột được sửa đổi trong bảng kiểm toán cùng với các thông tin khác như ai đã cập nhật và khi nào.

5

Bạn có thể thực hiện kiểm toán trên bảng SQL thông qua các trình kích hoạt SQL. Từ một kích hoạt, bạn có thể truy cập 2 bảng đặc biệt ( được chèn và xóa ). Các bảng này chứa các hàng chính xác được chèn hoặc xóa mỗi khi bảng được cập nhật. Trong SQL kích hoạt, bạn có thể lấy các hàng đã sửa đổi này và chèn chúng vào bảng kiểm toán. Cách tiếp cận này có nghĩa là kiểm toán của bạn là minh bạch cho lập trình viên; không đòi hỏi nỗ lực từ họ hoặc bất kỳ kiến ​​thức thực hiện.

Phần thưởng bổ sung của phương pháp này là việc kiểm toán sẽ diễn ra bất kể hoạt động sql diễn ra thông qua DLL truy cập dữ liệu của bạn hay thông qua truy vấn SQL thủ công; (vì việc kiểm toán được thực hiện trên chính máy chủ).


3

Bạn không nói cơ sở dữ liệu nào và tôi không thấy nó trong các thẻ bài. Nếu đó là cho Oracle, tôi có thể đề xuất cách tiếp cận được tích hợp trong Trình thiết kế: sử dụng bảng nhật ký . Nếu đó là cho bất kỳ cơ sở dữ liệu nào khác, thì về cơ bản, tôi cũng đề xuất một cách tương tự ...

Cách thức hoạt động, trong trường hợp bạn muốn sao chép nó trong một DB khác, hoặc có thể nếu bạn chỉ muốn hiểu nó, thì đối với một bảng cũng có một bảng bóng được tạo, chỉ là một bảng cơ sở dữ liệu bình thường, với cùng thông số trường , cộng với một số trường bổ sung: như hành động được thực hiện lần cuối (chuỗi, giá trị tiêu biểu "INS" để chèn, "Cập nhật" để cập nhật và "DEL" để xóa), datetime khi hành động diễn ra và id người dùng đã thực hiện nó

Thông qua các kích hoạt, mọi hành động cho bất kỳ hàng nào trong bảng sẽ chèn một hàng mới trong bảng nhật ký với các giá trị mới, hành động nào được thực hiện, khi nào và bởi người dùng nào. Bạn không bao giờ xóa bất kỳ hàng nào (ít nhất là không trong vài tháng qua). Có, nó sẽ phát triển lớn, dễ dàng hàng triệu hàng, nhưng bạn có thể dễ dàng theo dõi giá trị cho bất kỳ bản ghi nào tại bất kỳ thời điểm nào kể từ khi nhật ký bắt đầu hoặc các hàng tạp chí cũ bị thanh trừng lần cuối và ai là người thay đổi cuối cùng.

Trong Oracle mọi thứ bạn cần được tạo tự động dưới dạng mã SQL, tất cả những gì bạn phải làm là biên dịch / chạy nó; và nó đi kèm với một ứng dụng CRUD cơ bản (thực tế chỉ là "R") để kiểm tra nó.


2

Tôi cũng đang làm điều tương tự. Tôi đang làm một cơ sở dữ liệu cho các kế hoạch bài học. Những kế hoạch này cần linh hoạt phiên bản thay đổi nguyên tử. Nói cách khác, mỗi thay đổi, dù nhỏ đến đâu, đối với các kế hoạch bài học cần phải được cho phép nhưng phiên bản cũ cũng cần được giữ nguyên. Bằng cách đó, người tạo bài học có thể chỉnh sửa kế hoạch bài học trong khi học sinh đang sử dụng chúng.

Cách nó hoạt động là một khi học sinh đã làm một bài học, kết quả của họ được đính kèm với phiên bản mà họ đã hoàn thành. Nếu một thay đổi được thực hiện, kết quả của họ sẽ luôn hướng đến phiên bản của họ.

Theo cách này, nếu tiêu chí bài học bị xóa hoặc di chuyển, kết quả của chúng sẽ không thay đổi.

Cách tôi hiện đang làm điều này là bằng cách xử lý tất cả dữ liệu trong một bảng. Thông thường tôi sẽ chỉ có một trường id, nhưng với hệ thống này, tôi đang sử dụng id và sub_id. Sub_id luôn ở lại với hàng, thông qua các bản cập nhật và xóa. Id được tăng tự động. Phần mềm kế hoạch bài học sẽ liên kết đến sub_id mới nhất. Kết quả học sinh sẽ liên kết đến id. Tôi cũng đã bao gồm dấu thời gian để theo dõi khi có thay đổi, nhưng không cần thiết phải xử lý phiên bản.

Một điều tôi có thể thay đổi, một khi tôi đã thử nó, tôi có thể sử dụng ý tưởng null endDate đã đề cập trước đó. Trong hệ thống của tôi, để tìm phiên bản mới nhất, tôi sẽ phải tìm max (id). Hệ thống khác chỉ tìm endDate = null. Không chắc chắn nếu các lợi ích ngoài đường có trường ngày khác.

Theo quan điểm của tôi.


2

Trong khi @WW. Câu trả lời là một câu trả lời hay theo cách khác là tạo một cột phiên bản và giữ tất cả các phiên bản của bạn trong cùng một bảng.

Đối với một cách tiếp cận bảng bạn:

  • Sử dụng cờ để chỉ báo Ala Word mới nhất
  • HOẶC làm một điều khó chịu lớn hơn phiên bản outer join.

Một ví dụ SQL của outer joinphương thức sử dụng các số sửa đổi là:

SELECT tc.*
FROM text_content tc
LEFT OUTER JOIN text_content mc ON tc.path = mc.path
AND mc.revision > tc.revision
WHERE mc.revision is NULL 
AND tc.path = '/stuff' -- path in this case is our natural id.

Tin xấu là ở trên đòi hỏi một outer joinvà tham gia bên ngoài có thể chậm. Tin tốt là việc tạo các mục mới rẻ hơn về mặt lý thuyết vì bạn có thể thực hiện nó trong một thao tác ghi với các giao dịch ngoài (giả sử cơ sở dữ liệu của bạn là nguyên tử).

Một ví dụ thực hiện sửa đổi mới cho '/stuff'có thể là:

INSERT INTO text_content (id, path, data, revision, revision_comment, enabled, create_time, update_time)
(
SELECT
(md5(random()::text)) -- {id}
, tc.path
, 'NEW' -- {data}
, (tc.revision + 1)
, 'UPDATE' -- {comment}
, 't' -- {enabled}
, tc.create_time
, now() 
FROM text_content tc
LEFT OUTER JOIN text_content mc ON tc.path = mc.path
AND mc.revision > tc.revision
WHERE mc.revision is NULL 
AND tc.path = '/stuff' -- {path}
)

Chúng tôi chèn bằng cách sử dụng dữ liệu cũ. Điều này đặc biệt hữu ích nếu nói rằng bạn chỉ muốn cập nhật một cột và tránh việc khóa và giao dịch lạc quan.

Cách tiếp cận cờ và cách tiếp cận bảng lịch sử yêu cầu hai hàng được chèn / cập nhật.

Ưu điểm khác với outer joincách tiếp cận số sửa đổi là bạn luôn có thể cấu trúc lại cách tiếp cận nhiều bảng sau với các trình kích hoạt vì trình kích hoạt của bạn về cơ bản phải làm một cái gì đó như trên.


2

Alok đề nghị Audit tableở trên, tôi muốn giải thích nó trong bài viết của tôi.

Tôi đã áp dụng thiết kế bảng đơn giản, ít lược đồ này trong dự án của tôi.

Lược đồ:

  • id - TĂNG TỐC TỰ ĐỘNG INTEGER
  • tên người dùng - CHUINGI
  • tablename - CHUINGI
  • giá trị cũ - văn bản / JSON
  • giá trị mới - văn bản / JSON
  • createdon - DATETIME

Bảng này có thể giữ các bản ghi lịch sử cho tất cả các bảng trong cùng một nơi, với lịch sử đối tượng hoàn chỉnh trong một bản ghi. Bảng này có thể được sử dụng bằng cách kích hoạt / móc trong đó dữ liệu thay đổi, lưu trữ ảnh chụp nhanh giá trị cũ và mới của hàng đích.

Ưu điểm với thiết kế này:

  • Số lượng bảng ít hơn để quản lý để quản lý lịch sử.
  • Lưu trữ ảnh chụp đầy đủ của từng hàng trạng thái cũ và mới.
  • Dễ dàng tìm kiếm trên mỗi bảng.
  • Có thể tạo phân vùng theo bảng.
  • Có thể xác định chính sách lưu giữ dữ liệu trên mỗi bảng.

Nhược điểm với thiết kế này:

  • Kích thước dữ liệu có thể lớn, nếu hệ thống có thay đổi thường xuyên.
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.