데이터베이스에서 레코드를 버전 관리하는 방법


176

데이터베이스에 레코드가 있고 관리자 및 일반 사용자 모두 업데이트를 수행 할 수 있다고 가정 해 봅시다.

누구든지이 테이블의 모든 변경 사항을 버전 제어하는 ​​방법에 대한 좋은 접근 방식 / 아키텍처를 제안하여 레코드를 이전 개정으로 롤백 할 수 있습니다.

답변:


164

당신이 있다고 가정 해 봅시다 FOO관리자와 사용자가 업데이트 할 수 테이블이 합니다. 대부분의 경우 FOO 테이블에 대해 쿼리를 작성할 수 있습니다. 행복한 날들.

그런 다음 FOO_HISTORY테이블을 만듭니다 . 여기에는 FOO테이블 의 모든 열이 있습니다 . 기본 키는 FOO와 RevisionNumber 열을 더한 것과 같습니다. 에서 외래 키 FOO_HISTORYFOO있습니다. UserId 및 RevisionDate와 같이 개정과 관련된 열을 추가 할 수도 있습니다. 모든 *_HISTORY테이블에서 (즉, Oracle 시퀀스 또는 이와 동등한) 계속해서 수정 번호를 채 웁니다 . 1 초에 한 번만 변경되는 것에 의존하지 마십시오 (즉 RevisionDate기본 키에 넣지 마십시오 ).

이제 업데이트 할 때마다 업데이트 FOO를 수행하기 직전에 이전 값을FOO_HISTORY . 프로그래머가 실수로이 단계를 놓칠 수 없도록 설계의 일부 기본 수준에서이 작업을 수행합니다.

행을 삭제하려면 FOO몇 가지 선택 사항이 있습니다. 캐스케이드 및 모든 히스토리를 삭제하거나 삭제 된 것으로 플래그 FOO를 지정 하여 논리적 삭제를 수행하십시오 .

이 솔루션은 현재 값에 관심이 많고 때때로 이력에만 관심이있는 경우에 좋습니다. 항상 기록이 필요한 경우 효과적인 시작 및 종료 날짜를 입력하고 모든 레코드를 FOO자체적으로 보관할 수 있습니다. 그런 다음 모든 쿼리는 해당 날짜를 확인해야합니다.


1
데이터 액세스 계층이 직접 지원하지 않는 경우 데이터베이스 트리거로 감사 테이블 업데이트를 수행 할 수 있습니다. 또한 시스템 데이터 사전의 내부 검사를 사용하는 트리거를 만들기 위해 코드 생성기를 구축하는 것은 어렵지 않습니다.
ConcernedOfTunbridgeWells

44
이전 데이터가 아닌 데이터 를 실제로 삽입하는 것이 좋습니다 . 그래서 기록 테이블에 모든 데이터가 있습니다. 리던던트 데이터를 저장하지만 히스토리 데이터가 필요할 때 두 테이블에서 검색을 처리하는 데 필요한 특별한 경우를 제거합니다.
Nerdfest

6
개인적으로 나는 아무것도 삭제하지 않는 것이 좋으며 (이것은 특정 하우스 키핑 활동을 지연시킵니다) 삽입 / 업데이트 / 삭제 여부를 지정하는 "액션 유형"열을 갖는 것이 좋습니다. 삭제의 경우 행을 정상적으로 복사하지만 조치 유형 열에 "delete"를 넣으십시오.
Neil Barnwell

3
@Hydrargyrum 현재 값을 보유한 테이블은 히스토리 테이블의보기보다 성능이 우수합니다. 현재 값을 참조하는 외래 키를 정의 할 수도 있습니다.
WW.

2
There is a foreign key from FOO_HISTORY to FOO': 나쁜 생각, 기록을 변경하지 않고 foo에서 레코드를 삭제하고 싶습니다. 히스토리 테이블은 정상적인 사용시 삽입 전용이어야합니다.
Jasen

46

누군가가 질문 / 답변을 편집 할 때 StackOverflow 가하는 것처럼 데이터베이스 레코드의 내용을 버전 화하려고한다고 생각합니다. 개정 시작을 사용하는 일부 데이터베이스 모델을 살펴 보는 것이 좋습니다 .

가장 좋은 예는 Wikipedia 엔진 인 MediaWiki입니다. 여기 에서 데이터베이스 다이어그램 , 특히 개정 테이블을 비교하십시오. .

사용중인 기술에 따라 좋은 diff / merge 알고리즘을 찾아야합니다.

.NET 용인 경우이 질문을 확인하십시오 .


30

BI 세계에서는 버전을 지정할 테이블에 startDate 및 endDate를 추가하여이 작업을 수행 할 수 있습니다. 첫 번째 레코드를 테이블에 삽입하면 startDate가 채워지지만 endDate는 널입니다. 두 번째 레코드를 삽입하면 첫 번째 레코드의 endDate도 두 번째 레코드의 startDate로 업데이트합니다.

현재 레코드를 보려면 endDate가 널인 레코드를 선택하십시오.

이를 유형 2 느리게 변화하는 차원 이라고도합니다 . TupleVersioning 도 참조하십시오


이 접근법을 사용하여 테이블이 크게 커지지 않습니까?
닐스 보스 마

1
예. 그러나 테이블을 인덱싱 및 / 또는 파티셔닝하여이를 처리 할 수 ​​있습니다. 또한 소수의 큰 테이블 만 있습니다. 대부분은 훨씬 작습니다.
ConcernedOfTunbridgeWells

내가 실수하지 않은 경우 여기에서 유일한 몰락은 변경 사항을 초당 한 번으로 제한한다는 것입니다.
pimbrouwers

@pimbrouwers 예, 궁극적으로 필드의 정밀도와 필드를 채우는 기능에 달려 있습니다.
Dave Neeley

9

SQL 2008로 업그레이드하십시오.

SQL 2008에서 SQL 변경 추적을 사용해보십시오. 타임 스탬프 및 삭제 표시 열 해킹 대신이 새로운 기능을 사용하여 데이터베이스의 데이터 변경 내용을 추적 할 수 있습니다.

MSDN SQL 2008 변경 내용 추적


7

이 문제에 대한 좋은 해결책 중 하나는 임시 데이터베이스 를 사용하는 것 입니다. 많은 데이터베이스 공급 업체가이 기능을 기본 제공 또는 확장을 통해 제공합니다. PostgreSQL과 함께 임시 테이블 확장을 성공적으로 사용 했지만 다른 확장도 있습니다. 데이터베이스에서 레코드를 업데이트 할 때마다 데이터베이스는 해당 레코드의 이전 버전도 보유합니다.


6

두 가지 옵션 :

  1. 히스토리 테이블 보유-원본이 업데이트 될 때마다 이전 데이터를이 히스토리 테이블에 삽입하십시오.
  2. 감사 테이블-수정 전과 후 값을 감사 테이블의 수정 된 열에 대해서만 업데이트 대상 및시기와 같은 기타 정보와 함께 저장합니다.

5

SQL 트리거를 통해 SQL 테이블에 대한 감사를 수행 할 수 있습니다. 트리거에서 2 개의 특수 테이블 ( 삽입 및 삭제)에 액세스 할 수 있습니다. )에 . 이 테이블에는 테이블이 업데이트 될 때마다 삽입되거나 삭제 된 정확한 행이 포함됩니다. 트리거 SQL에서 이러한 수정 된 행을 가져 와서 감사 테이블에 삽입 할 수 있습니다. 이 접근 방식은 감사가 프로그래머에게 투명하다는 것을 의미합니다. 그들의 노력이나 구현 지식이 필요하지 않습니다.

이 접근 방식의 추가 이점은 데이터 액세스 DLL 또는 수동 SQL 쿼리를 통해 SQL 작업이 수행되었는지 여부에 관계없이 감사가 발생한다는 것입니다. 감사는 서버 자체에서 수행됩니다.


3

어떤 데이터베이스를 말하지 않고 게시 태그에 표시되지 않습니다. Oracle의 경우 Designer에 기본 제공되는 접근 방식 : 저널 테이블 사용을 권장 할 수 있습니다 . 다른 데이터베이스를위한 것이라면 기본적으로 같은 방법을 권장합니다 ...

다른 DB에서 복제하려는 경우 또는 이해하기 쉽도록 테이블의 경우 동일한 필드 사양을 가진 일반 데이터베이스 테이블과 섀도우 테이블이 생성되는 것이 작동 방식 , 마지막으로 수행 한 작업 (문자열, 삽입의 경우 "INS", 업데이트의 경우 "UPD", 삭제의 경우 "DEL"), 작업이 발생한 날짜 시간 및 수행 한 사용자 ID와 같은 추가 필드 그것.

트리거를 통해 테이블의 행에 대한 모든 조치는 저널 테이블에 새 값, 수행 된 조치,시기 및 사용자에 의해 새 행을 삽입합니다. 적어도 지난 몇 개월 동안은 행을 삭제하지 마십시오. 예, 수백만 행이 쉽게 커지지 만 어느 시점 에서나 모든 레코드 의 값을 쉽게 추적 할 수 있습니다. 저널링이 시작되거나 이전 저널 행이 마지막으로 삭제 된 마지막으로 변경했는지 누가 .

Oracle에서는 필요한 모든 것이 SQL 코드로 자동 생성되므로 컴파일 / 실행 만하면됩니다. 기본 CRUD 응용 프로그램 (실제로는 "R")과 함께 검사합니다.


2

나는 또한 같은 일을하고 있습니다. 수업 계획을위한 데이터베이스를 만들고 있습니다. 이러한 계획에는 원자 변경 버전 관리 유연성이 필요합니다. 다시 말해, 수업 계획에 대한 각 변경 사항은 아무리 작아도 허용되어야하지만 이전 버전도 그대로 유지해야합니다. 이렇게하면 수업 제작자는 수업 계획을 사용하는 동안 수업 계획을 편집 할 수 있습니다.

그것이 작동하는 방식은 일단 학생이 수업을 마치면 그 결과가 완성 된 버전에 첨부되는 것입니다. 변경하면 결과는 항상 버전을 가리 킵니다.

이런 방식으로 수업 조건을 삭제하거나 이동해도 결과는 변경되지 않습니다.

현재이 작업을 수행하는 방법은 하나의 테이블에서 모든 데이터를 처리하는 것입니다. 일반적으로 하나의 id 필드 만 가지고 있지만이 시스템에서는 id와 sub_id를 사용하고 있습니다. sub_id는 항상 업데이트 및 삭제를 통해 행과 함께 유지됩니다. ID는 자동 증분됩니다. 수업 계획 소프트웨어는 최신 sub_id에 연결됩니다. 학생 결과가 ID에 연결됩니다. 또한 변경 사항이 발생한 시점을 추적하기위한 타임 스탬프도 포함 ​​시켰지만 버전 관리를 처리 할 필요는 없습니다.

테스트 한 후에 변경할 수있는 한 가지는 앞서 언급 한 endDate null 아이디어를 사용할 수 있다는 것입니다. 내 시스템에서 최신 버전을 찾으려면 max (id)를 찾아야합니다. 다른 시스템은 endDate = null을 찾습니다. 혜택에 다른 날짜 필드가 있는지 확실하지 않습니다.

내 두 센트


2

@WW 동안. 또 다른 방법은 버전 열을 만들고 모든 버전을 동일한 테이블에 유지하는 것입니다.

하나의 테이블 접근 방식은 다음 중 하나입니다.

  • 플래그를 사용하여 최신 알라 워드 프레스 표시
  • 또는 version보다 큰 작업을 수행하십시오 outer join.

outer join개정 번호를 사용 하는 메소드의 SQL 예 는 다음과 같습니다.

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.

나쁜 소식은 위와 outer join외부 조인이 필요하다는 점 입니다. 좋은 소식은 트랜잭션없이 한 번의 쓰기 작업 (데이터베이스가 원자 적이 라고 가정 )으로 새 항목을 만드는 것이 이론적으로 더 저렴하다는 것 입니다.

에 대한 새로운 개정의 예 '/stuff'는 다음과 같습니다.

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}
)

이전 데이터를 사용하여 삽입합니다. 이것은 하나의 열만 업데이트하고 낙관적 잠금 및 / 또는 트랜잭션을 피하려는 경우 특히 유용합니다.

플래그 접근 및 히스토리 테이블 접근은 두 개의 행을 삽입 / 업데이트해야합니다.

outer join개정 번호 접근 방식 의 또 다른 장점 은 트리거가 본질적으로 위와 같은 작업을 수행해야하기 때문에 나중에 트리거를 사용하여 다중 테이블 접근 방식으로 언제든지 리팩토링 할 수 있다는 것입니다.


2

Alok는 Audit table위에서 제안 했으며 내 게시물에 설명하고 싶습니다.

프로젝트에서이 스키마가없는 단일 테이블 디자인을 채택했습니다.

개요:

  • id-정수 자동 증가
  • 사용자 이름-STRING
  • 테이블 이름-STRING
  • 이전 값-TEXT / JSON
  • newvalue-TEXT / JSON
  • createdon-DATETIME

이 테이블은 각 테이블에 대한 히스토리 레코드를 한 번에 한 레코드에 보유 할 수 있으며 완전한 오브젝트 히스토리는 한 레코드에 보유 할 수 있습니다. 이 테이블은 데이터가 변경되는 트리거 / 후크를 사용하여 채워질 수 있으며 대상 행의 이전 및 새 값 스냅 샷을 저장합니다.

이 디자인의 장점 :

  • 히스토리 관리를 위해 관리 할 테이블 수가 적습니다.
  • 각 행의 이전 및 새 상태에 대한 전체 스냅 샷을 저장합니다.
  • 각 테이블을 쉽게 검색 할 수 있습니다.
  • 테이블별로 파티션을 만들 수 있습니다.
  • 테이블 당 데이터 보존 정책을 정의 할 수 있습니다.

이 디자인과 단점 :

  • 시스템이 자주 변경되는 경우 데이터 크기가 클 수 있습니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.