[TIL - 0829] 트랜잭션, RDBMS SELECT와 SELECT JOIN 속도 차이

트랜잭션과 스프링

트랜잭션

  • 격리성을 보장하면서도 성능을 가져가야함
    • 동시에 실행되는 트랜잭션은 서로 영향을 미치면 안됨(상황에 따라 어느 수준까지 영향을 미치지 않을 것인가 정해야함 - Lock 정책) - 예를 들어 T1이 SELECT한 데이터를 T2가 UPDATE를 했을 때 T1이 다시 SELECT 하면 다른 값이 읽히는 그런 영향(Repeatable Read 불가 - 각 락 정책에 따라 발생할 수 있는 문제가 있음)
    • 데이터 정합성 수준에 따라 Lock 정책을 정하면 됨 : 기본적으로 데이터베이스마다 설정되어있는 정책이 있음
mysql> show variables like 'tx_isolation';
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+

Read Committed is the default isolation level in PostgreSQL
  • 트랜잭션 동시 처리 격리 수준 : 1로 가까울수록 영향을 미치게되고(성능은 더 좋아짐 - Lock이 그만큼 약해지니깐), 4로 가까울수록 직렬 처리, 데이터베이스마다 지원 레벨이 달라서 알아봐야함
    1. Read Uncommitted : 커밋되지않은 데이터를 읽을 수 있고 그로인해 처음 읽었던 값과 다를 수도 있고 추가도 할 수 있음(Dirty Read, Non-Repeatable Read, Phantom Read 가능)
    2. Read Committed : 처음에 선택한 데이터를 다른 트랜잭션에서 수정 후 커밋하면 이후 달라진 데이터를 읽게됨, 추가도 가능(Non-Repeatable Read, Phantom Read 가능)
    3. Repeatable Read : 데이터 수정은 불가하나 추가될 수 있음(Phantom Read 가능)
    4. Serializable : 직렬 처리

스프링과 트랜잭션

  • 스프링과 트랜잭션 : @Transactional - AOP(경계설정 코드를 삽입하지않고도 트랜잭션 처리가능)
    • spring-data-jpa에서 제공하는 SimpleJpaRepository는 기본적으로 @Transactional이 설정되어있음
    • 메소드마다 걸면 됨 : readOnly 설정을 할 경우 flush를 하지않는 것으로 설정(데이터베이스 동기화 - 읽기만 하니깐) 그럴 경우 변경 감지 또한 하지않기때문에 성능 상에 이점을 가져온다고 함
@Transactional(readOnly = true)
Optional<Item> findByName(String name);

SELECT VS JOIN 성능 차이 눈으로 확인

  • SELECT만으로 문제를 해결하면 JOIN 보다 빠르다라고 생각만 해봤지 실제로 얼마나 차이가 나는지 보고싶었음
    • 환경 세팅하기 : 더미 데이터를 bulk insert함 - 두가지 테이블을 생성함(테이블 두개로 나뉘어져있을 때와 하나의 테이블에 관련 데이터가 다 들어가있을 때)
    • 더미데이터 생성기 만들어서 데이터 생성 : csv(comma seperated variables) 파일 bulk insert, 생성하면서 한 것은 .java .class로 컴파일 후 실행시키기(매번 IDE가 해주니깐 개념적으로만 알고 거의 해본적이 없었음)
> LOAD DATA LOCAL INFILE '파일경로 + 파일명' INTO TABLE 테이블명 FIELDS TERMINATED BY ',';
  • 테이블 생성
    1. Car / Person
    2. CarInfo(Car + Person)
 (1)
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int(11)     | NO   | PRI | NULL    |       |
| name  | varchar(16) | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+

+---------------+-------------+------+-----+---------+-------+
| Field         | Type        | Null | Key | Default | Extra |
+---------------+-------------+------+-----+---------+-------+
| id            | int(11)     | NO   | PRI | NULL    |       |
| name          | varchar(16) | YES  |     | NULL    |       |
| fk_person_car | int(11)     | YES  |     | NULL    |       |
+---------------+-------------+------+-----+---------+-------+

(2)
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int(11)     | NO   | PRI | NULL    |       |
| name  | varchar(16) | YES  |     | NULL    |       |
| pname | varchar(16) | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
  • 하나의 테이블에 몰았을 때와 테이블을 나눠서 검색할 때 성능 차이 눈으로 보기
  • 더미 데이터를 만들 때 무조건 fk를 가지고 있게 했음 : 333,333개 데이터가 다 출력되도록 - 두가지 상황(SELECT와 JOIN으로 데이터 출력하기)
  • 적게는 약 3배 검색 속도 차이를 볼 수 있었음 : 데이터베이스 서버 로컬에서만 검색했을 때의 차이, 조건 없이 단순히 SELECT와 JOIN으로 찾아오기만
> SELECT * FROM CARINFO;
333333 rows in set (0.17 sec) # SELECT 결과

> SELECT c.id, c.name, p.name FROM CAR AS c INNER JOIN PERSON AS p ON c.id = p.fk_person_car;
333333 rows in set (0.56 sec) # SELECT JOIN 결과
  • 더 알아봐야할 것
    • 지금은 CarInfo에 이름 밖에 없지만 만약 다른 컬럼들이 더 관리된다면? ORM으로 객체 쪽 코드 복잡도를 낮출 수 있지 않을까
    • 전체 컬럼을 검색했을 때와 인덱스 걸린 컬럼을 검색했을 때에 대한 차이, 검색(SELECT)할 때 최적화할 수 있는 방법 : 쿼리 플랜 보는 연습해보기