
Relational은 테이블에서 PK-FK의 관계가 있다는 뜻이다. 데이터를 테이블 형태로 표현하는 방식이다.
예를 들어, 차와 관련해서 구매자와 차종이 있다고 했을 때 왼쪽 그림처럼 하나의 행렬(테이블)에 다른 행렬을 넣을 수가 없다. 그래서 2개의 행렬을 따로 놓고 관계를 연결하는 것이다. (이때 FK는 소나타)
RDBMS는 데이터의 중복을 최소화하고 구조 변경이 쉽고 SQL 언어를 사용하여 데이터를 쉽게 조작할 수 있는 장점이 있다. 또한 ACID 원칙을 준수하여 데이터의 무결성과 일관성을 보장한다. 그래서 RDBMS는 현재 가장 널리 사용되는 DBMS로서 우리가 익히 잘아는 MySQL, Oracle, SQLite 등이 있다.

테이블 형태의 관계형 모델이 아닌 여러가지의 모델로 데이터를 표현하는 방식이다. 예를 들어, 문서(Document)나 키-값(Key-Value) 쌍이나 그래프(Graph) 등으로 데이터를 나타낼 수 있다. 그래서 NOSQL은 어느 한가지 형태의 데이터베이스를 지칭하지 않고, RDBMS의 테이블 형태가 아닌 형태를 띈 DB를 총칭한다고 보면 된다.
{
"Id": 1,
"Name": "Aaron",
"Car": {
"Name": "소나타",
"Eng": "X기통",
"Size": "준중형"
}
}
RDBMS보다 확장성과 가용성이 높고 유연한 스키마를 가지는 장점이 있지만, 데이터의 일관성이 항상 보장되지 않고 기존 SQL과 호환되지 않는 단점이 있다.
NOSQL은 대용량 데이터나 분산 처리에 매우 빠르게 대응이 가능하기 때문에 페이스북, 스냅챗과 같은 SNS에서 쓰인다.
데이터베이스에서 데이터에 대한 하나의 논리적인 작업 단위를 말한다. (한꺼번에 수행되어야 할 일련의 연산 모음)
논리적인 이유로 여러 SQL문들을 단일 작업으로 묵어서 나누어질 수 없도록 만든 것이다.
트랜잭션 내 SQL문들은 모두 성공하는 경우 commit되고, 하나라도 실패하는 경우에는 전체가 롤백된다.
트랜잭션 내 SQL문 중에 일부만 성공해서 DB에 반영되는 일은 일어나지 않는다.
예를 들어 은행 어플에서 계좌이체를 할 때 은행 어플에서는 내부적으로 여러 단계의 업무를 겨쳐야 한다.
이 업무로는 [계좌 번호 확인][이름 확인] [송신자 계죄 금액 감소][수신자 계좌 금액 증가] 등이 있을 것이다.
이런 단계들을 데이터를 처리하는 논리적 행위인 트랜잭션이라고 한다.
그러면 이 작업들이 하나의 작업으로 이루어져서는 안된다. 반드시 여러 개의 트랜잭션이 완료되어야 계좌이체라는 하나의 작업이 완료된다.
데이터베이스 상태를 바꾸는 일종의 작업 단위이다.
INSERT, DELETE, UPDATE 등의 SQL 명령문으로 데이터 상태를 바꿀 때마다 내부적으로, 자동적으로 Commit을 실행하여 변경된 내역을 데이터베이스에 반영한다.


완벽하게 수행되거나 완벽하게 수행되지 않아야 한다.
논리적으로 쪼개질 수 없는 작업 단위이기 때문에 내부의 SQL문들이 모두 성공하거나 중간에 어떤 하나의 SQL문이라도 실패하면, 지금까지 작업을 모두 취소해 이전 상태로 롤백해야 한다.
데이터 저장 시 모든 제약 조건들을 만족하며 데이터 조작 발생 직후, 조회 시 최신값을 볼 수 있어야 한다.
데이터 베이스는 트랜잭션의 작업 처리 결과가 항상 일관성 있는 상태여야 한다.
트랜잭션이 진행되는 동안 DB가 변경되더라도 업데이트된 DB로 트랜잭션이 진행되는 것이 아니라, 처음에 트랜잭션을 진행하기 위해 참조한 DB로 진행된다.
값이 같아야한다는 생각은 버리고 트랜잭션이 실행을 성공적으로 완료되었을 때, 이전의 상태와 결과의 상태가 항상 안정된 상태로 있어야 한다는 것이다.
은행 어플에서 내 계좌에 10,000원 있다고 가정해보자.
송금을 받을 사람의 계좌 잔고가 20,000이라고 했을 때 내가 5,000원을 계좌이체 업무를 완료해도 내 계좌와 송금 받을 사람의 계좌 합은 30,000원으로 동일하다.
만약에, constraints, trigger 등을 통해서 DB에 정의된 규칙을 트랜잭션이 위반했다면 롤백해야 한다.
트랜잭션이 DB에 정의된 규칙을 위반했는지는 DBMS가 커밋 전에 확인하고 알려준다.
그렇다고 DBMS를 맹신하기보다는, 애플리케이션 관점에서 트랜잭션이 consistent하게 동작하는지는 개발자도 챙겨야 한다.
동시에 다수 쿼리가 발생 시 모든 커리에 해당하는 Transaction은 독립되어야 한다.
여러 트랜잭션이 동시에 실행될 때에도 혼자 실행되는 것처럼 동작하게 만들어야 한다. 즉, 동시에 실행되는 여러 트랜잭션은 서로 영향을 주지 않고 독립적으로 실행되는 것처럼 보여야 한다.
DBMS는 여러 종류의 Isolation Level을 제공하고, 개발자는 이중에서 어떤 레벨로 트랜잭션을 동작시킬지 설정할 수 있다.
데이터가 지속적으로 존재하며 로그를 통해 보완해야 한다.
DB 전원이 갑자기 종료되는 문제가 발생해도 커밋된 결과는 계속 DB에 남아있어야 한다. 트랜잭션 조작을 하드 디스크에 로그로 기록하고 시스템에 이상이 발생하면 그 로그를 사용해 이상 발생 전까지 복원하는 것으로 지속성을 실현하고 있다.
트랜잭션이 성공적으로 완료됬을 경우, 결과는 영구적으로 반영되어야 한다.
여러 트랜잭션이 동시에 처리될 때, 특정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터를 볼 수 있게 허용할지 여부를 결정하는 것이다.
한 개의 트랜잭션이 다른 트랜잭셔닝 변경한 데이터에 대한 접근할 수 있는 강도를 의미.
격리 수준이 높은 순서대로 SERIALIZABLE, REPEATABLE READ, READ COMMITTED, READ UNCOMMITED가 존재한다.
READ UNCOMMITTED는 부정합 문제가 지나치게 발생하고, SERIALIZABLE은 동시성이 상당히 떨어지므로 READ COMMITTED 또는 REPEATABLE READ를 사용하면 된다. 참고로 오라클에서는 READ COMMITTED를 기본으로 사용하며, MySQL에서는 REPEATABLE READ를 기본으로 사용한다.
격리 수준은 트랜잭션의 ACID 특성을 보장하기 위해 사용된다.
격리 수준이 낮으면 많은 트랜잭션의 처리가 빨라지지만 데이터의 무결성에 문제가 발생할 수 있다.
대신 높으면 트랜잭션 처리 성능이 저하될 수 있지만 데이터 무결성의 문제를 해결할 수 있다.
그래서 데이터와 데이터를 처리하는 트랜잭션의 특징에 따라 격리 수준을 다르게 설정하여 최적의 성능과 데이터의 무결성 문제를 해결해야 한다.
다른 트랜잭션이 커밋한 데이터를 읽을 수 있는 것을 의미.
즉 한 트랜잭션에서 같은 쿼리로 2번 이상 조회했을 때 그 결과가 상이한 상황.
데이터의 수정/삭제가 발생했을 경우 발생.
다른 트랜잭션이 커밋한 데이터가 있더라도 자신의 트랜잭션에서 읽었던 내용만 사용하는 것을 의미.
즉 한 트랜잭션에서 같은 쿼리로 2번 이상 조회했을 때 없던 결과가 조회되는 상황.
데이터의 삽입이 발생했을 경우 발생.
트랜잭션 연산에서의 COMMIT
오류없이 성공적으로 작업을 마치게 된다면 DB에 반영.
트랜잭션 내용이 커밋이나 롤백과 상관없이 다른 트랜잭션에서 조회가 가능.
정합성의 문제가 많은 격리 수준이기 때문에 RDBMS 표준에서는 격리수준으로 인정하지 않는다.
SELECT가 실행되는 동안 해당 데이터에 Shared Lock이 걸리지 않는다.
그래서 Dirty Read가 발생.

1) DB에 10만원을 가지고 있는 계좌가 있다.
2) Transaction A가 읽기 요청을 수행. (DB는 10만원을 반환)
3) A가 5만원을 추가하는 Write 요청을 한다.
4) DB에는 총 15만원을 가지고 있음.
5) B가 읽기 연산을 수행.
6) READ-UNCOMMITTED는 아직 커밋되지 않은 데이터를 읽을 수 있기 때문에 15만원을 그대로 가져온다.
7) 만약 A에서 오류가 발생하여 롤백해야 한다면 B에는 15만원이 있지만 DB에는 10만원이 있게 된다.
이렇게 해서 발생하는 문제를 Dirty Read 문제라고 한다.
한 트랜잭션의 변경 내용이 커밋되어야만 다른 트랜잭션에서 조회가 가능하다.
대부분의 RDBMS에서 기본적으로 사용하는 격리수준이다.
SELECT 가 실행되는 동안 Shared Lock이 걸린다. 조회 시에는 실제 테이블 값이 아니라 Undo 영역에 백업된 레코드 값을 가져온다.
하지만 하나의 트랜잭션에서 똑같은 SELECT 쿼리를 실행했을 때는 항상 같은 결과를 가져와야 하는 REPEATABLE READ의 정합성에 어긋난다.

1) DB에 있는 10만원을 A가 읽고 쓰고 해서 15만원이 된 상태.
2) B가 DB에 있는 15만원을 읽는데 아직 커밋되지 않았기 때문에 10만원을 읽게 된다.
3) B는 10만원을 읽었는데 A가 커밋한 뒤 B가 다시 DB를 읽으면 10만원이 아닌 15만원을 읽게 된다.
즉 같은 데이터를 요청했는데 서로 다른 데이터가 나오므로 문제가 발생.
DIRTY READ 문제 해결
NON-REPEATABLE READ 발생
특정 데이터를 반복 조회 시 같은 값을 반환한다.
MYSQL DBMS에서 기본적으로 사용하고 있고, 이 격리수준에서는 NON-REPEATABLE READ 부정합이 발생하지 않는다.
트랜잭션이 완료될 때까지 Select문이 사용하는 모든 데이터에 Shared Lock이 걸린다.
트랜잭션 범위 내에서 조회한 데이터의 내용은 항상 동일하다.
트랜잭션이 시작 시점 데이터의 일관성을 보장해야 하기 때문에 트랜잭션의 실행시간이 길어질수록 계속 멀티 버전을 관리해야 하는 단점이 발생할 수 있다.

DIRTY READ 문제 해결
NON-REPEATABLE READ 문제 해결
1) B가 은행 계좌의 개수를 세는 쿼리를 날린다고 했을 때 계좌 1개가 나온다.
2) A가 새로운 계좌인 5만원을 추가하면 DB에는 2개의 계좌가 나온다.
3) B가 다시 쿼리 날리면 계좌 2개가 나온다.
상반된 결과가 나옴.
Phantom-Read 문제 발생
여러 트랜잭션이 동일한 레코드에 동시 접근할 수 없으므로, 어떠한 데이터 부정합 문제도 발생하지 않는다.
가장 엄격한 격리 수준으로 트랜잭션을 순차적으로 진행시키지만 동시 처리 성능이 매우 떨어진다.
SERIALIZABLE 수준에서는 PHANTOM READ가 발생하지 않는다.
순차적이기 때문에 다른 사용자는 그 영역에 해댱되는 데이터에 대한 수정 및 입력이 불가능하다.
(동시 처리 성능이 낮아 거의 사용 X)

결국 격리 수준은 데이터의 특징과 DBMS의 특징에 따라서 레벨을 설정하여 효율적인 격리 수준을 설정할 수 있어야 한다.
🔗 https://www.youtube.com/watch?v=taUeIi6a6hk
🔗 https://blog.kakaocdn.net/dn/cKKUqM/btsFLVrTi3H/Fg5QupovmSIKEAJJM7Ywck/img.gif




Reference
🔗 https://rnclf1005.tistory.com/29 - RDBMS에서 트랜잭션의 ACID 규칙
🔗 https://velog.io/@luda412/Isolation-Level - 격리성
🔗 https://blog.yevgnenll.me/posts/what-is-acid-about-transaction
🔗 https://engineerinsight.tistory.com/210
🔗 https://mangkyu.tistory.com/299 - 격리 수준
🔗 https://akasai.space/db/about_isolation/