데이터 베이스 원리
- 데이터 베이스를 물류 창고에 비유. 3가지 고려해야 할 사항이 존재한다. (하지만 항상 3가지는 상충된다.)
- 많이 (병렬처리)
- 빠르게 (응답 속도)
- 정확하게 (커밋한 데이터를 지킨다)
- 데이터베이스(물류창고), ------------ 쓰레드(입구지킴이?) ---------클라(고객) 이라고 생각하자.
- 질문1. 디스크 I/O는 얼마나 느리길래 자꾸 빠르게 빠르게를 말하는가?
- 서버를 생각해보자 서버에서는 CPU에서 RAM을 왔다갔다하기가 속도 저하가 일어나서 캐쉬(레지스터, L1, L2)를 사용한는데, 여기서 말하는 디스크 IO하드는 RAM보다 더 먼 곳에 위치한다 그래서 DB를 이용할 때도 캐쉬를 사용하는데 여기서 말하는 '캐쉬'는 'RAM'이다.
- 질문2. 안전하게는 얼마나 안전하게인가?
- CPU나 메모리가 박살나도 안전해야한다. RAM은 휘발성이라서 데이터가 날아간다, 그래서 데이터를 디스크에 저장해야한다.
- 질문3 읽고 쓰고 둘다 어떻게 빠르게 하냐?
- READ는 데이터를 읽어와야하는데 하드까지 가기에 시간이 걸려서 중간에 메모리 캐쉬를 두고 만약 그곳에 데이터가 존재하지 않는다면 하드에서 데이터를 가지고온다. 가지고 올 경우 데이터를 해당 데이터만 가지고 오는 것이 아니라, 주변에 데이터까지 가지고 온다.
- 참고로 캐시에서 데이터를 정리할 때 보통 LRU(Least Recently Used) 최근에 사용빈도가 적은 것을 정리한다
락
- 락? 서버에서 말하는 락인가? 데이터베이스에도 락이 동일하게 존재한다.
- 서버에서는 READ - LOCK (서로 공유 가능 shared Lock), Write-Lock(서로 상호배타적), 서로 경합이 write가 많이 없을 경우 read - writer lock을 쓰는 것이 더 좋다..
- DB에서 LOCK을 거는 경우를 생각해보자. DB의 락 종류는 아래와 같다.
- shared (select)
- Exclusive (insert, update, delete와 같은 write문..)
- Update(update는 shared와 Exclusive의 중간이다..)
- 문제 1. db에서 그럼 lock을 걸때 막 걸면 되지않나 왜 범위를 신경 쓰는가?
- lock을 거는 범위에 따라서 성능이 좌지우지된다.(Row, page, table, database가 존재)
- Row락을 거는 것으로 갈 수록 병렬 처리하기가 좋다, 그렇지 자원 소모가 크다 왜 ?
- 병렬처리가 좋은 이유는 갈 수 있는 범위가 넓기 때문이다, 자원 소모가 큰 이유는 한번 row lock을 걸때 하나씩 거는 것이 아니라, 수천개의 row를 거는데 그럴빠엔 하나의 page에 lock을 거는 것이 자원소모가 덜하다..
트랜젝션
- 트랜잭션은 원자성이다. ALL OR NOTHING ...이러한 것들을 관리하기 위해서 log를 통해서 관리한다.
- ex : 로그를 봤는데 A에게서 물건을 없어졌으나, B에게서는 물건은 생기고 골드는 사라졌다 (A와 B가 물건을 거래하는 상황) 이때 A의 로그를 보면서 ROLLBACK을 시켜야한다.
- begin transaction으로 가다가 중간 부분이 실패한다면 rollback!!! 그렇지만 모두 성공하면 실행으로 옮겨야해서 commit.!!
- 여러 읽기 / 쓰기를 논리적으로 한번에 묶는다. 트랜잭션 범위는 커넥션 기준이다. 트랜잭션 전파는 여러 메서드 호출이 한 트랜잭션에 묶이도록 하기 위해 필요하다,
- 트랜잭션과 외부 연동은 주의해야한다. 외부 API호출이 완료된 후 rollback을 하게된다면, api호출은 rolback이 되지않는다.
- 글로벌 트랜잭션은 두 개 이상의 자원(db, 메시징큐)을 한 트랜잭션으로 처리하는 것이다. 예를 들어 insert와 메시지큐에 데이터를 넣는 것을 하나로 묶을 수 있다. 자원에 대한 처리는 쉽지만, 성능이 안좋다.
트랜젝션 정의
- 원자성(Atomicity)
- 트랜젝션의 연산은 데이터베이스에 모두 반영되던지 아니면 전혀 반영되지 않아야한다.
- 트랜젝션 내 모든 명령이 완벽히 수행되지 않고 어느 하나라도 오류가 발생하면 트랜젝션 전부가 취소되어야한다.
- 일관성(consistence)
- 트랜젝션이 실행을 완료하면, 언제나 일관성 있는 데이터베이스 상태로 변화해야한다..
- 시스템이 가지고 있는 고정 요소는 트랜젝션 수행 전과 수행 후가 동일해야한다..
- Isolation(독립성, 격리성)
- 둘 이상의 트랜잭션이 동시에 병행 실행되는 경우 어느 하나의 트랜잭션 실행중에 다른 트랜잭션의 연산이 끼어들 수 없다.
- 수행중인 트랜젝션은 완전히 완료될때까지 다른 트랜잭션에서 수행결과를 참조할 수 없다.
- Durablility(영속성, 지속성)
- 성공적으로 완료된 트랜젝션의 결과는 시스템이 고장나더라도 영구적으로 반영되어야한다.
트랜잭션 격리 수준
Read Committed
Repeatable Read
Serializable
여러 클라이언트 같은 데이터에 접근할 때 문제가 발생하는데 그것을 race condition이라고한다.
순서대로 실행하면 되지만 성능이 저하된다 그래서 다양한 격리 수준을 지원한다.
Read Committed
커밋되지 않은 데이터를 읽기를 dirty read라고한다
A라는 사람은 데이터를 insert하고 commit을 하지 않은 상태에서 B라는 사람이 조회를 하면 데이터가 깨지는 현상이 발생한다 dirty read..
위의 그림에서 A가 inser into를 할 때 stock은 2개 이상황에서 commit이 되기전에 B가 데이터를 조회하면 stock이 2개, cnt는 1개가 조회된다 아직 A가 commit을 하지 않아서 이렇게 데이터 깨지게된다..
커밋되지 않은 데이터를 덮어 쓰기 dirty write
- dirty read, dirty write를 해결하기위해서 read committed로 커밋된 데이터만 읽기, 커밋된 값과 트랜잭션 진행 중인 값을 따로 보관
- 커밋된 데이터만 덮어쓸 수 있도록한다. , 행 단위 잠금을 사용하여 같은 데이터를 수정한 트랜잭션이 끝날 때까지 대기를한다.
Repeatable Read
- 트랜잭션이 진행 중인 동안 데이터가 변경되어서 같은 데이터를 읽게한다.(특정 버전에 해당하는 데이터만 읽음)
변경 유실에 대한 몇가지 처리 방법
원자적 연산을 처리하면 동시 수정 요청에 대해 db가 순차 처리를 한다..
명시적으로 잠금
- 조회할 때 수정할 행을 미리 잠금해서 select조차 못하게 막는다.
-CAS
- 수정할 때 값이 같은지 비교해서 같은 값일 경우에만 수정을한다. 이것을 통해 변경이 유실되는 것을 방지
Serializable
- 이름 그대로 순차적으로 할 수 있나 그렇게 하면 늦어진다. 그래서 인덱스 잠금이나 조건 기반 잠금을 사용한다.