728x90
반응형
728x90
반응형
반응형

환경: springboot 2.7.6, mysql 8

 

@Id로 엔티티의 primitive key를 명시할 때 꼭 wrapper 클래스로 작성해야 하는지에 대한 의문이 생겼다.

wrapper클래스의 경우 null을 허용하는데, 엔티티를 저장할 때 키 값이 없으면(null) 자동 생성해 주는 기능 때문에 0이 의도치 않게 들어가면 이슈가 있을 수 있다고 생각했다.

하여 정리해본다.

 

id의 경우 아래와 같은 케이스가 있다.

  • select 시: id는 not null이기 때문에 굳이 wrapper를 쓸 일이 없음
  • insert 시:
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long seq;
  • jpa의 @GeneratedValue(strategy = GenerationType.IDENTITY)가 선언된 id 콜롬 & 디비에서 AUTO_INCREMENT 설정된 경우
    • 해당 경우는 디비의 설정을 사용한다는 뜻으로 mysql에 설정된 seq bigint(20) NOT NULL AUTO_INCREMENT COMMENT처럼 AUTO_INCREMENT 옵션이 들어간 경우 1부터 저장하기 때문에 0은 무시
  @Id
  private long seq;
  • @GeneratedValue 가 선언되지 않은 id 콜롬 & 디비에서 AUTO_INCREMENT 설정된 경우
    • 0 자체가 null과 동일하게 auto increment를 생성하라는 의미이기 때문에 0이 저장되지 않고 다음 sequence가 저장됨, AUTO_INCREMENT 옵션이 들어간 경우 1부터 저장하기 때문에 0은 무시
  • @GeneratedValue 가 선언되지 않은 id 콜롬 & 디비에서 AUTO_INCREMENT 설정되지 않은 경우
    • 0이 저장됨

참고로 mysql 기본 설정은 AUTO_INCREMENT일 때 0은 제외한다.
sql_mode항목에 NO_AUTO_VALUE_ON_ZERO 값이 설정되어 있으면 auto increment 시 0을 추가한다.

각자 디비에 어떻게 설정되어 있는지는 아래 쿼리로 확인 가능하며

SHOW VARIABLES LIKE 'sql_mode';

혹시.. 0을 의미 있는 값으로 세팅하려거든 아래처럼 하면 된다.

SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';

 

즉, id값을 primitive로 사용할지 wrapper로 사용할지에 대한 판단은, db에서의 0에 대한 설정과 값의 null/0 여부를 파악 후 결정하는 게 좋겠다

728x90
반응형
반응형

0125024852  0125024850 0125024851

친구코드: 0125024852  0125024850 0125024851

추천번호: 0125024852  0125024850 0125024851

 

최장 6개월에 최대 6%임 소소한 돈으로 쏠쏠하게 ㄱㄱ!

인당 3개까지 가입해서 코드가 3개입니다!! 연달아 가입했더니 코드도 순서대로네요 ㅋ_ㅋ

https://obank.kbstar.com/quics?page=C020722&boardId=669&compId=b058336&articleId=121532&bbsMode=view&viewPage=1&articleClass=2&searchCondition=title&searchStr=

 

728x90
반응형
반응형

collation 이란

데이터베이스의 문자열 Datatype(CHAR, VARCHAR, TEXT 등)에는 캐릭터 셋(Character set)과 콜래이션(Collation)이라는 속성이 있다.

캐릭터 셋(Character set)은 각 문자가 컴퓨터에 저장될 때 어떻게 저장될지(encoding)에 대한 규칙의 집합이고,

콜래이션(Collation)은 특정 캐릭터 셋(Character set)에 의해

  • 데이터베이스에 저장된 값들을 비교 검색(where clause)하거나
  • 문자들을 서로 정렬(order by) 등의 작업을 위해 비교할 때
  • 그리고 인덱싱을 할 때

사용하는 규칙들의 집합을 의미한다.

예를 들어 

  • int형은 123 < 345 으로 명확히 비교할 수 있고
  • date형은 2013-01-01 < 2022-01-01로 명백하나

문자열의 경우

  • '가'와 '나' 중 어느 것이 큰지
  • 'a'와 'A' 중 어느 것이 큰지 
  • '가'와 'ㄱㅏ'는 어떻게 비교해야 하는지

혼란스럽다. 이와 관련하여 정리된 방식이 collation이라고 생각하면 된다.

 

대표적인 collation 타입

  • utf8mb4_bin
    • binary 저장 값으로 정렬; 각 문자를 byte 취급하여 byte 값을 비교(언어적인 규칙이 고려되지 않음)
    • A는 41, a는 61이기 때문에 오름차순 정렬 시 A~Z 다음 a~z가 정렬된다.
      • SELECT HEX(WEIGHT_STRING('A' collate utf8mb4_bin)) as 'A',  HEX(WEIGHT_STRING('a' collate utf8mb4_bin)) as 'a'  FROM dual;
  • utf8mb4_general_ci
    • 간단하고 빠르게 사용할 수 있는 타입
    • 모든 유니코드가 고려된 건 아니지만 일반적으로 많이 사용됨
      • 유니코드 중 Basic Multilingual Plane (BMP)를 벗어나면 정렬이 틀리게 될 수 있음
      • 하지만 중국어(C), 일본어(J), 한국어(K) 통칭 CJK는 BMP에 포함되어 있어 국내에서도 잘 쓰이는 타입
    • case insensitive로 A는 41, a도 41로 같기 때문에 A와 a가 혼용되어 정렬된다.
      • SELECT HEX(WEIGHT_STRING('A' collate utf8mb4_general_ci)) as 'A',  HEX(WEIGHT_STRING('a' collate utf8mb4_general_ci)) as 'a'  FROM dual;
  • utf8mb4_unicode_ci
    •  모든 유니코드를 고려한 정렬 규칙으로 고려하는 규칙 자체가 많아 utf8mb4_general_ci 방식보다 느림
      • 한국어, 영어, 중국어, 일본어 사용 환경에서는 utf8mb4_general_ci와 동일한 결과를 냄
      • 더 특수한 문자의 정렬 순서는 달라질 수 있음
    • case insensitive로 A는 0E33, a도 0E33으로 같기 때문에 A와 a가 혼용되어 정렬된다.
      • SELECT HEX(WEIGHT_STRING('A' collate utf8mb4_unicode_ci)) as 'A', HEX(WEIGHT_STRING('a' collate utf8mb4_unicode_ci)) as 'a'  FROM dual;

 

간단하게 collation 타입 읽는 법

ex. utf8mb4_0900_ai_ci

  • utf8mb4 : 문자 하나당 1~4byte 할당(mb4 : 4byte 지원), 바로 이어서 지역 및 언어를 나타내는 단어로 세분화되기도 함
    • utf8mb3는 3byte가 할당되는 방식으로 mysql8에서 deprecated
  • 0900 : Unicode Collation Algorithm (UCA) version 9.0 표준을 따른다는 뜻
    • mysql8 추가; 더 세분화된 정렬법 적용
  • ai : accent insensitive (이전버전에서는 악센트 구분이 안되었으며 MySQL 8.0부터 추가됨)
  • ci : case insensitive (대소문자 구분하지 않음)
 

그 외 아래와 같은 표기법이 사용될 수 있음

Suffix
Meaning
비고
_ai
Accent-insensitive
mysql8 추가
_as
Accent-sensitive
mysql8 추가
_ci
Case-insensitive
 
_cs
Case-sensitive
 
_ks
Kana-sensitive
mysql8 추가
일본어 히라가나-가타카나 같게할지 여부
_bin
Binary
 
  • ​MySQL 8.0.1 버전부터 utf8mb4_0900_ai_ci이 기본값임

 

mysql8부터 추가된 collation type의 pad attribute

  • 이전 버전에서는 PAD SPACE를 사용하던 것과 다르게 NO PAD 속성이 생김
    • 기본 값은 PAD SPACE
  • NO PAD 속성은 문자열 끝에 빈 문자열이 있는 경우에 문자열 비교 시 공백까지 포함하여 비교함(공백도 의미를 가진다는 전제)
  • 따라서 정렬 시 의도한 대로 정렬이 되지 않을 수 있으니 사용하고 있는 collation type이 어떤 pad attribute를 갖는지 확인해야 함

 

그냥 접속 시 매번 collation이 다르게 접속될 수 있음

  • jdbc 커넥터 버전에 따라 커넥션 별 collation이 달라질 수 있음
  • 워크벤치에서도 지정하지 않으면 외부 영향을 받아 달라질 수 있음
  • 따라서 커넥터 레벨에서 collation을 지정하거나 접속 시 명령어에 아래 내용 추가하여 항상 고정된 collation으로 붙을 수 있도록 하는 게 좋음
-- Set the character set and collation for the session
SET NAMES utf8mb4 COLLATE utf8mb4_general_ci;

 

collation이 다르게 접속이 될 경우 데이터 저장 시 에러 발생할 가능성 있음

문자열을 기준으로 다음 작업을 할 때 아래 에러가 발생할 수 있음:

  • 테이블을 join 하거나
  • 값을 비교, 필터링하거나
  • string 연산(concat 등)을 할 때
오류 코드: 1267Illegal mix
of collations (utf8_general_ci, IMPLICIT) and (utf8_unicode_ci, IMPLICIT) for
operation '='

이 경우 내 로컬(커넥션) 설정과 서버의 collation 설정이 다르거나 테이블, 콜롬 등에 설정된 collation이 상이하기 때문으로 collation에 대해서 확인 필요

그럴리는 없겠지만 혹시 테이블, 콜롬별 collation 설정이 다르다면 아래와 같이 join 조건에 collate 타입을 명시해야 한다.

SELECT *
FROM table1
JOIN table2 ON table1.name = table2.description COLLATE utf8_general_ci;

 

지금 내가 사용하는 디비에서 collation 값 조회하는 방법

  • mysql8
show variables where variable_name like '%collation%';

Variable_name                |Value             |
-----------------------------+------------------+
collation_connection         |utf8mb4_0900_ai_ci|
collation_database           |utf8mb4_general_ci|
collation_server             |utf8mb4_general_ci|
default_collation_for_utf8mb4|utf8mb4_0900_ai_ci|


#전체 값 확인
SHOW COLLATION WHERE Charset = 'utf8mb4';


Collation                 |Charset|Id |Default|Compiled|Sortlen|Pad_attribute|
--------------------------+-------+---+-------+--------+-------+-------------+
utf8mb4_0900_ai_ci        |utf8mb4|255|Yes    |Yes     |      0|NO PAD       |
utf8mb4_0900_as_ci        |utf8mb4|305|       |Yes     |      0|NO PAD       |
utf8mb4_0900_as_cs        |utf8mb4|278|       |Yes     |      0|NO PAD       |
utf8mb4_0900_bin          |utf8mb4|309|       |Yes     |      1|NO PAD       |
utf8mb4_bg_0900_ai_ci     |utf8mb4|318|       |Yes     |      0|NO PAD       |
...생략
  • mysql5
show variables where variable_name like '%collation%';

Variable_name       |Value             |
--------------------+------------------+
collation_connection|utf8mb4_general_ci|
collation_database  |utf8mb4_general_ci|
collation_server    |utf8mb4_general_ci|


#전체 값 확인
SHOW COLLATION WHERE Charset = 'utf8mb4';

Collation             |Charset|Id |Default|Compiled|Sortlen|
----------------------+-------+---+-------+--------+-------+
utf8mb4_general_ci    |utf8mb4| 45|Yes    |Yes     |      1|
utf8mb4_bin           |utf8mb4| 46|       |Yes     |      1|
utf8mb4_unicode_ci    |utf8mb4|224|       |Yes     |      8|
utf8mb4_icelandic_ci  |utf8mb4|225|       |Yes     |      8|

 

collation 변경 방법

1) 데이터베이스 레벨

ALTER DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

2) 테이블 레벨

ALTER TABLE my_table CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci ;

3) 콜롬 레벨

ALTER TABLE mytable MODIFY name VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci;

4) 세션 레벨

SET collation_connection = 'utf8mb4_general_ci' ;

5) 쿼리 레벨(insert문 동일)

SELECT 
    @_now := now(),
    @ver := '1.17.0' collate utf8mb4_general_ci , 
    @domain := 'my domain' collate utf8mb4_general_ci , 
    @port_ := '1234'
;

6) 서버 레벨

[mysqld]
character-set-server=latin1
collation-server=latin1_swedish_ci

 

결론

  • 같은 문자셋이라도 콜레이션에 따라 영어의 경우 대소문자의 구분, 일본어의 경우 히라가나와 가타카나의 구분, 한글 자음과 결합문자를 구분하는 방법 등이 달라짐
  • 관련해서 정렬 시 정확도와 검색 속도에 영향이 있음
  • MySql 5-> 8로 올릴 때 collation 설정 값이 정렬 등에 영향을 줄 수 있다는 것을 인지할 필요 있음
  • MySQL 8.0의 기본 collation 인 utf8 mb4_0900_ai_ci는 utf8이며 글자당 4byte까지 저장하고, 0900 버전의 UCA 규칙을 따르며 accent, 대소문자, 히라가나와 가타카나, 한글 자음과 결합문자를 구분하지 않음

관련 상세 내용은 버전별 공식 문서를 확인하자

https://dev.mysql.com/doc/refman/8.4/en/charset.html

 

MySQL :: MySQL 8.4 Reference Manual :: 12 Character Sets, Collations, Unicode

MySQL 8.4 Reference Manual  /  Character Sets, Collations, Unicode Chapter 12 Character Sets, Collations, Unicode MySQL includes character set support that enables you to store data using a variety of character sets and perform comparisons according to a

dev.mysql.com

https://dev.mysql.com/doc/refman/5.7/en/charset.html

 

MySQL :: MySQL 5.7 Reference Manual :: 10 Character Sets, Collations, Unicode

MySQL 5.7 Reference Manual  /  Character Sets, Collations, Unicode Chapter 10 Character Sets, Collations, Unicode MySQL includes character set support that enables you to store data using a variety of character sets and perform comparisons according to a

dev.mysql.com

관련 내용 테스트 한 블로그

https://blog.naver.com/sory1008/223071678680

728x90
반응형

'개발 > sql' 카테고리의 다른 글

[mysql] delete, drop, truncate  (0) 2024.10.02
[mysql] basic functions  (0) 2024.09.09
DB isolation level  (0) 2024.05.22
[mysql] merge into..?  (0) 2024.05.17
[mysql] 유저의 등수 구하기 rank under v8  (0) 2024.02.06
반응형

(DB가 아닌) 애플리케이션 단 락에 대해 알아본다.

2023.01.12 - [개발/spring] - [jpa] lock종류와 사용 시 주의사항

 

Optimistic Locking 낙관적 락

  • @Version 어노테이션 사용(엔티티 당 하나)
  • 최초 커밋만 인정하는 방법
  • entity에 바로 락을하는 것이 아니라 버전 넘버를 저장하는 방식
    • 초기 버전 값은 0
  • 저장할 때 버전 넘버가 다르거나(해당 row못찾음) 0이면 롤백하고 에러 발생
  • intIntegerlongLongshortShortjava.sql.Timestamp
    • we can also use other approaches, such as timestamps, hash value computation, or serialized checksum.
  • 버전 애트리뷰트는 엔티티를 통해 읽을 수 있지만 절대 개발자가 직접 업데이트하거나 증가시켜선 안된다
    • 벌크 연산은 버전을 무시하기 때문에 벌크 연산을 할 경우 버전 필드를 직접 증가시켜야 한다
@Version
@UpdateTimestamp
@Column(nullable = false)
private LocalDateTime updated;

 

Pessimistic Locking 비관적 락

  • DB에서 제공하는 락기능을 사용
  • entity에 접근하는 순간 락이 걸림
  • 트랜잭션끼리의 충돌이 발생한다고 가정하고 우선 락을 거는 방법
  • @Lock 어노테이션 사용
  • 락이 실행할 때 transaction이 없으면 에러 발생(transactionRequiredException)
  • 락을 바로 얻을 수 없으면 LockTImeoutException 던짐
    • timeout 설정 필요: 락을 잡고 있는 최대 시간
      • QueryHint 이용; javax.persistence.lock.timeout; 단위 ms
    • 모든 DBMS가 지원하는 건 아님
  • If the time it takes to obtain a lock exceeds the value of this property, a LockTimeoutException will be thrown, but the current transaction will not be marked for rollback.

 

@Lock(LockModeType.PESSIMISTIC_READ)
@QueryHints({@QueryHint(name = "jakarta.persistence.lock.timeout", value = "3000")})
public Optional<Customer> findById(Long customerId);

 

 

LockModeType

public enum LockModeType {
  READ,
  WRITE,
  OPTIMISTIC,
  OPTIMISTIC_FORCE_INCREMENT,
  PESSIMISTIC_READ,
  PESSIMISTIC_WRITE,
  PESSIMISTIC_FORCE_INCREMENT,
  NONE;

 

optimistic lock 관련

  • NONE
    • 락 모드를 적용하지 않아도 엔티티에 버전 애트리뷰트가 있으면 Optimistic Locking이 적용된다
    • Second Lost Update Problem을 예방할 수 있다
    • 조회한 엔티티를 수정할 때 버전을 체크하면서 버전을 증가한다(UPDATE 쿼리 사용)
    • 이때 데이터베이스의 버전 값이 현재 버전이 아니면 예외 발생
  • OPTIMISTIC
    • NONE 을 사용하면 엔티티를 수정해야 버전을 체크하지만 OPTIMISTIC을 사용하면 엔티티를 조회만 해도 버전을 체크한다
    • 쉽게 얘기하면 한 번 조회한 엔티티는 트랜잭션을 종료할 때까지 다른 트랜잭션에서 변경하지 않음을 보장한다
    • dirty read와 non repeatable read를 방지한다
    • 트랜잭션을 커밋할 때 버전 정보를 조회해서(SELECT 쿼리 사용) 현재 엔티티의 버전과 같은지 검증하고 같지 않으면 예외가 발생한다.
  • OPTIMISTIC_FORCE_INCREMENT
    • Optimistic Locking을 사용하면서 버전 정보를 강제로 증가한다
    • 논리적인 단위의 엔티티 묶음을 관리할 떄 사용한다
    • 엔티티를 수정하지 않아도 트랜잭션을 커밋할 때 UPDATE 쿼리를 사용해 버전을 강제로 증가시킨다
    • 이때 데이터베이스의 버전이 엔티티 버전과 다르다면 예외가 발생한다
    • 추가로 엔티티를 수정하면 수정 시 버전 UPDATE가 발생한다 따라서 총 2번의 버전 증가가 나타날 수 있다

persistent lock 관련

  • LockModeType.PESSIMISTIC_WRITE
    • 일반적인 옵션. 데이터베이스에 쓰기 락
    • 다른 트랜잭션에서 읽기도 쓰기도 못함. (배타적 잠금)
  • LockModeType.PESSIMISTIC_READ
    • 잘 사용하지 않음 
    • 반복 읽기만 하고 수정하지 않는 용도로 락을 걸 때 사용
    • 다른 트랜잭션에서 읽기는 가능함. (공유 잠금)
  • LockModeType.PESSINISTIC_FORCE_INCREMENT
    • Version 정보를 사용하는 비관적 락
    • 버전 정보를 강제로 증가시킴

jpa에 위와 같이 이름으로 ForUpdate 줘도 만들어지는 쿼리는 select for update로 나가게 된다.

728x90
반응형
반응형

springframework에서 제공하는 spring retry에 대해 알아보자

maven repo: https://mvnrepository.com/artifact/org.springframework.retry/spring-retry

 

spring aspect 라이브러리를 사용할 수도 있는데, 해당 라이브러리는 springboot starter에 있을 수 있기에 한번 확인하고 넣는 게 좋다.

springboot2.7.3 기준 5.3.24 버전이 들어가 있다.

 

관련 어노테이션을 사용하려면 아래처럼 enable 시켜줘야 한다.

import org.springframework.context.annotation.Configuration;
import org.springframework.retry.annotation.EnableRetry;

@Configuration
@EnableRetry
public class AppConfig {
}

실행 원리는 proxy!!

 

재시도를 해야 하는 함수에 @Retryable을 달아주고

재시도 시 실행해야하는 함수에 @Recover를 달아준다.

import org.springframework.retry.annotation.Recover;
import org.springframework.stereotype.Service;

@Service
public class MyService {

    @Retryable(value = { SomeTransientException.class }, maxAttempts = 3)
    public void performTask() throws SomeTransientException {
        // Your logic here
        System.out.println("Trying to perform task...");
        throw new SomeTransientException("Temporary failure");
    }

    @Recover
    public void recover(SomeTransientException e) {
        // Recovery logic here
        System.out.println("Recovering from failure: " + e.getMessage());
    }
}

api에러 뿐만 아니라 아래와 같이 디비 에러에도 가능. IO에러 등등 많이 커버한다.. @Retryable에서 throw 하는 에러 타입과 @Recover에서 받는 에러타입이 반드시 같아야 한다.

@Service
public interface MyService { 

    @Retryable(retryFor = SQLException.class)
    void retryServiceWithRecovery(String sql) throws SQLException; 

    @Recover
    void recover(SQLException e, String sql); 
}

Retryable 어노테이션에는 어떤 exception이 발생할 때 재시도를 할지와 최대 몇 번 실행할지를 지정해 줄 수 있다.

  • maxAttempts: 실패가 n번 나면 recover 함수를 실행한다.
    • default: 3
  • backoff: 재실행하고 n초 쉬었다가 재실행(단위: ms)
    • default:  a fixed delay of 1000ms
  • multiplier: 재시도와 재시도 사이의 시간 간극이 n배로 점점 멀어짐
    • default: 0(무시)
  • BackOffPolicy: 아래처럼 설정 값들에 따라 재시도 간격이 달라진다.
    • With no explicit settings the default is a fixed delay of 1000ms
    • Only the delay() set: the backoff is a fixed delay with that value
    • When delay() and maxDelay() are set the backoff is uniformly distributed between the two values
    • With delay(), maxDelay() and multiplier() the backoff is exponentially growing up to the maximum value
    • If, in addition, the random() flag is set then the multiplier is chosen for each delay from a uniform distribution in [1, multiplier-1]

 

위 값들은 클래스에서 상수 값으로 관리할 수 있지만 공용사용과 환경별 관리를 위헤 프로퍼티 파일로 뺄 수도 있다.

이때 해당 변수 명이 달라지니(ex. maxAttemps -> maxAtempsExpression) 반드시 공식문서를 참고해야 한다!

https://docs.spring.io/spring-retry/docs/api/current/org/springframework/retry/annotation/Retryable.html

 

공통 설정 혹은 더 복잡한 설정을 위해 java config로 설정할 수도 있다.

@Configuration
@EnableRetry
public class RetryConfig {


    @Bean
    public RetryTemplate retryTemplate(){
        RetryTemplate retryTemplate = new RetryTemplate();

        FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
        fixedBackOffPolicy.setBackOffPeriod(3000l);
        
        retryTemplate.setBackOffPolicy(fixedBackOffPolicy);

        Map<Class<? extends Throwable>, Boolean> includeExceptions = new HashMap<>();
        includeExceptions.put(CannotAcquireLockException.class, true);

        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(5, includeExceptions);
        retryTemplate.setRetryPolicy(retryPolicy);

        return retryTemplate;
    }
    
}

 

참고로 delay는 간격은 랜덤으로도 줄 수 있다.

@Retryable(
    value = {CannotAcquireLockException.class},
    maxAttempts = 4,
    backoff = @Backoff(random = true, delay = 1000, maxDelay = 5000, multiplier = 2)

 


기본설명:

https://www.baeldung.com/spring-retry

좀더 복잡하게 사용하기:

https://medium.com/@AlexanderObregon/using-springs-retryable-annotation-for-automatic-retries-c1d197bc199f

https://medium.com/@vmoulds01/springboot-retry-random-backoff-136f41a3211a


이럴 수가.. 적으면서 이거 전에 본적 있는데 싶었는데 무려 정리를 했던 적이 있던 것ㅋㅋㅋㅋ

2022.02.04 - [개발/spring] - [retry] spring-retry for auto retry

 

[retry] spring-retry for auto retry

spring-retry? exception이 난 작업에 대해 자동으로 재시도 해주는 스프링 라이브러리이다. 일시적인 connection error 등에 유용하게 사용할 수 있다. 환경: springboot2.6.2 implementation 'org.springframework.retry:spri

bangpurin.tistory.com

 

728x90
반응형
반응형

2024.05.23 - [개발/spring] - [transaction] isolation level

 

[jpa] transaction isolation level

2024.05.22 - [개발/sql] - DB isolation level DB isolation levelisolation level 이란 무엇인가?디비 동시성을 관리하고 데이터 정합성을 유지하기 위해 서로 다른 트랜젝션끼리의 관계를 정의한 것 존재 이유?언

bangpurin.tistory.com

A transaction is a group of sequential operations on a database that forms a logical unit of working with data.

Transactions are used to maintain the consistency and integrity of data in a database.

 

 It’s important to know that transactions are thread-local, so they apply to the current thread only.

 

propagation: 세션의 트랜젝션을 어떻게 이용할지; 무결성과 정합성을 유지하기 위한 방법

종류 트랜젝션 존재 시 트랜젝션 미존재 시 비고
REQUIRED 기존 트랜잭션 이용 신규 트랜잭션 생성 기본값
SUPPORTS 기존 트랜잭션 이용  트랜잭션 없이 수행  
MANDATORY 기존 트랜잭션 이용 exception 발생 꼭 이전 트랜잭션이 있어야 하는  경우
NEVER exception 발생 정상적으로 트랜잭션 없이 수행 트랜잭션 없을 때만 작업이 진행되어야할 때
NOT_SUPPORTED 기존 트랜젝션은 중지하고 대기, 트랜젝션 없이 실행하다가 끝나면 기존 트랜젝션 실행 트랜잭션 없이 로직 수행 기존 트랜잭션에 영향을 주지 않아야할 때
REQUIRES_NEW 현재 트랜잭션은 중지되고 대기. 새로운 트랜잭션을 생성하고 종료되면 현재 트랜젝션이 다시 진행 신규 트랜잭션을 생성하고 로직을 실행 이전 트랜잭션과 구분하여 새로운 트랜잭션으로 처리가 필요할 때;
락과 함께 사용할 경우 데드락 조심
NESTED 현재 트랜잭션에 Save Point를 걸고 이후 트랜잭션을 수행 REQUIRED와 동일하게 작동
(신규 트랜잭션을 생성하고 로직이 수행)
DBMS특성에 따라 지원 혹은 미지원;
jpa에서 사용 불가
  • Nested is not possible in the JPA dialect because you cannot create a save point here. Nested, unlike Required New, creates a kind of save point. For example, if you are updating a huge batch of data, you won’t have to roll back everything in case of an error; you may roll back just to the save point.

 

두 함수간 트랜젝션을 전파하는 경우(출처: chat gpt..)

부모 함수 -> 자식 함수라고 가정할 때

트랜잭션 전파의 핵심은 부모 메서드에서 트랜잭션이 이미 시작되었는지 여부입니다. parentMethod()에서 트랜잭션이 시작되었다면, 그 안에서 호출되는 모든 자식 메서드(접근 제어자 public이든 private이든 상관없이)는 동일한 트랜잭션 경계 내에서 실행됩니다.

부모 자식 부모 transaction 전파 여부
public + @Transactional private in same class  O
public + @Transactional public in same class, @Transactional유무 상관없이 O
public + @Transactional public in different class, @Transactional유무 상관없이 O

1. Transaction은 public에서 시작, private은 함수의 일부라 판단하여 트랜젝션 이어짐

@Service
public class MyService {

  @Transactional
  public void parentMethod() {
    // Transaction starts here
    privateChildMethod(); // This method is part of the same transaction
  }

  private void privateChildMethod() {
    // This method participates in the transaction started by parentMethod
  }
}

2. 자식 함수가 같은 클래스에 있으면 부모 트랜젝션 전파됨

만약 반대로 부모가 @TRANSACTIONAL이 없고 같은 클래스의 자식에게 @TRANSACTIONAL이 있다면 자식의 트랜젝션은 신규로 생성되지 않음(프록시를 타지 않아서)

@Service
public class MyService {

  @Transactional
  public void parentMethod() {
    // Transaction starts here
    publicChildMethod(); // This call bypasses the proxy
  }

  public void publicChildMethod() {
    // This method does not participate in the transaction started by parentMethod
  }
}

3. 부모와 자식 클래스가 다를 경우, 자식이 기본 Transaction을 사용할 경우

클래스가 다를 경우 자식 함수가 proxy의 영향을 받기 때문에 부모의 트랜젝션이 자식에게 전파된다.

자식 함수가 @Transaction 어노테이션이 있건 없건 전파되는데, 만약 자식 함수도 Transaction 어노테이션이 있고 별다른 propagation 설정이 없다면 기본 전파 옵션이 Propagation.REQUIRED 이기 때문에 기존 트랜젝션을 탄다. 이 경우가 transaction 중첩이 가능한 부분이고, 위 옵션에 따라 달라진다.

@Service
public class ParentService {

  @Autowired
  private ChildService childService;

  @Transactional
  public void parentMethod() {
    // Transaction starts here
    childService.childMethod(); // This call goes through the proxy
  }
}

@Service
public class ChildService {

  @Transactional //있건없건 트랜젝션 영향 받음
  public void childMethod() {
    // This method participates in the transaction started by parentMethod
    // because the call goes through the Spring proxy
  }
}

https://www.baeldung.com/spring-transactional-propagation-isolation

한 번쯤 읽어볼 만한 시행착오

https://technixc.medium.com/how-to-use-transactional-annotation-like-a-pro-4129308ad069

728x90
반응형
반응형

2024.05.22 - [개발/sql] - DB isolation level

 

DB isolation level

isolation level 이란 무엇인가?디비 동시성을 관리하고 데이터 정합성을 유지하기 위해 서로 다른 트랜젝션끼리의 관계를 정의한 것 존재 이유?언제 그리고 어떤 식으로 한 트랜젝션이 만든 변화

bangpurin.tistory.com

Isolation Level은 동시 트랜잭션이 수행될때 다른 트랜잭션이 동일한 데이터에 대해서 어떻게 보일지에 대한 범위를 나타낸다.

​Isolation is the third letter in the ACID acronym. It means that concurrent transactions should not impact each other. 

 

위에서 DB단에서의 isolation level에 대해 살펴보았다. 이제 application 단에서의 isolation 설정을 알아보자.

 

isolation level을 아래와 같이 트랙잭션 별로 설정할 수 있다.

import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void readUncommittedTransaction() {
    // Your code here
}

@Transactional(isolation = Isolation.READ_COMMITTED)
public void readCommittedTransaction() {
    // Your code here
}

@Transactional(isolation = Isolation.REPEATABLE_READ)
public void repeatableReadTransaction() {
    // Your code here
}

@Transactional(isolation = Isolation.SERIALIZABLE)
public void serializableTransaction() {
    // Your code here
}
  • Isolation.DEFAULT: 디비의 default 설정에 따름
  • Isolation.READ_UNCOMMITTED
  • Isolation.READ_COMMITTED: 처음 업데이트 값으로 오버라이드 위험
  • Isolation.REPEATABLE_READ: 오버라이드 할 것 같으면 에러 발생
    • 에러 발생하면 재시도할 수 있음
    • @Retryable(maxAttempts = 15) // This is actually quite a lot. Ideally, 1–3 attempts should be sufficient.
    • 스래드 200개 생성됨..(과다하게 생성됨; 동시성 이슈)
    • 즉각 재시도(디비저장) 보다 다시 큐를 쌓도록하는게 동시성을 낮출 수
  • Isolation.SERIALIZABLE
    • 동시에 저장하려고하면 디비에서 에러발생
    • 그래도 100프로 보장 못함; 실패나면 롤백되는게 있음

 

주의사항

데이터 정합성(data integrity)과 성능(performance)을 고려하여 설정해야 한다.

사용하고자 하는 level 이 DB에서 지원하는 레벨인지 확인해야 한다.

 

애플리케이션 전체 기본 설정을 바꾸려면 아래와 같이 설정값을 추가한다.

(spring boot version 확인하고 넣도록 하자) 

springboot 2.7.3 ~ 3.2에는 아래와 같다.

spring.datasource.hikari.transaction-isolation

https://docs.spring.io/spring-boot/docs/2.7.3/reference/htmlsingle/

 

Spring Boot Reference Documentation

This section goes into more detail about how you should use Spring Boot. It covers topics such as build systems, auto-configuration, and how to run your applications. We also cover some Spring Boot best practices. Although there is nothing particularly spe

docs.spring.io

https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties.data.spring.datasource.hikari

 

Common Application Properties

 

docs.spring.io

 

728x90
반응형
반응형

isolation level 이란 무엇인가?

  • 디비 동시성을 관리하고 데이터 정합성을 유지하기 위해 서로 다른 트랜젝션끼리의 관계를 정의한 것
  • 트랜잭션이 동시에 수행될때 다른 트랜잭션이 동일한 데이터에 대해서 어떻게 보일지에 대한 범위를 나타낸다.

 

존재 이유?

  • 언제 그리고 어떤 식으로 한 트랜젝션이 만든 변화를 보이게 할지(visibility) 조절하면서 정합성(consistency)과 성능(performance) 사이의 균형(balance)을 맞추기 위함

 

isolation level 4가지

Read Uncommited

  • 제일 낮은 레벨
  • 언제? 이슈 위험 높음(정확도 낮음); 성능이 중요할 때
  • 정의: 커밋되지 않은, 수정된 데이터가 다른 트랜젝션에서도 보임(dirty reads)
  • 이슈: dirty read, non repeatable read, phantom read 모두 발생

 

Read Commited

  • 기본 설정인 DB: postgreSQL, oracleDB
  • 언제? 정합성이 중요하나 반복해서 읽었을 때 같을 필요 없을 경우
  • 정의: 커밋된 수정 사항만 다른 트랜젝션에서 보임
  • 이슈: 같은 데이터를 여러번 조회할 경우 다른 결과를 볼 수도 있음; 다른 트렌젝션에서 변경했음(non repeatable reads)
    • non repeatable read, phantom read 모두 발생

 

Repeatable Read

  • 기본 설정인 DB: mySQL
  • 언제? 정합성이 중요하고 한 트랜젝션 내에서 한 데이터를 여러번 조회했을 때 같은 결과가 나와야하는 경우
  • 정의: 같은 트랜젝션 내 한 데이터를 여러번 조회할 경우 항상 같은 결과를 보장함(트랜젝션 시작 전 커밋된 데이터만 보임)
  • 이슈: 다른 트랜젝션에서 삽입/삭제된 row(혹은 변화)를 볼 수 없음(phantom reads)

 

Serializable

  • 제일 높은 레벨
  • 언제? 데이터의 무결성과 정합성이 엄청 중요할 때(금융..)
  • 정의: 마치 동기로 실행하듯 하나 끝나고 다른 하나를 실행한다.(sequentially)
  • 이슈: 성능이 안좋음; 가끔 테이블 전체를 Lock 함

 

728x90
반응형

'개발 > sql' 카테고리의 다른 글

[mysql] basic functions  (0) 2024.09.09
[mysql] collation이란  (0) 2024.06.10
[mysql] merge into..?  (0) 2024.05.17
[mysql] 유저의 등수 구하기 rank under v8  (0) 2024.02.06
[DB] 분산환경에서 데이터 저장소 선택과 활용  (0) 2023.07.24
반응형

환경: mysql 5.7

oracle에는 merge into 가 있어 값이 있을때는 수정하고 없을때는 추가하여 pk 없음 에러 혹은 중복키 저장 에러가 나지 않고 작업을 진행할 수 있었는데, mysql에는 같은 기능을 하는 쿼리가 있는지 알아본다.

oracle

MERGE INTO [UPDATE되거나 INSERT 될 테이블]
USING [MERGE를 진행하고 싶은 대상, 조인, 서브쿼리도 사용 가능]
ON [조건]
WHEN MATCHED THEN [조건에 맞는 데이터가 있을 시 실행할 구문, UPDATE, DELETE]
WHEN NOT MATCHED THEN [조건에 맞는 데이터가 없을 시 실행할 구문, INSERT]
;

 

mysql

INSERT INTO 테이블 (
	[콜롬들...]
)VALUES(
	[값들...]
)
ON DUPLICATE KEY UPDATE
	[PK값들..]

예시

category 테이블 pk가 service_code, category_code 인 경우, category_name을 추가하거나 수정하려고 한다면..

CREATE TABLE `category` (
  `service_code` varchar(20) NOT NULL COMMENT '서비스 코드',
  `category_code` varchar(20) NOT NULL COMMENT '카테고리 코드',
  `category_name` varchar(20) NOT NULL COMMENT '카테고리 명',
  PRIMARY KEY (`service_code`,`category_code`)

 

INSERT INTO category 
	(service_code, category_code, category_name) 
VALUES
	('admin', 'character', '캐릭터')
ON DUPLICATE KEY UPDATE 
	service_code = 'admin' , category_code = 'character'
;

위처럼 쓸 수도 있고 아래처럼 작성해도 동일하다.

INSERT INTO category 
	(service_code, category_code, category_name) 
VALUES
	('admin', 'character', '캐릭터')
ON DUPLICATE KEY UPDATE 
	service_code = VALUES(service_code), category_code = VALUES(category_code)
;

날려보면 아래처럼 성공하는 로그가 찍힌다.

하지만 실제 row는 수정되지 않았다! 그렇다고 신규 row가 생기지도 않았다.

참고로 기존 row를 지우고 날리면 1로 결과가 떨어지고 신규 row가 추가된다. 허나 이 상태에서 category name을 바꾸고 날려보면 1로 떨어져도 반응이 없다..

 

쿼리를 아래처럼 수정하면 

INSERT INTO category (service_code, category_code, category_name) 
VALUES ('admin', 'character', '캐릭터797')
ON DUPLICATE KEY UPDATE 
    service_code = VALUES(service_code),
    category_code = VALUES(category_code),
    category_name = VALUES(category_name);

기존 row가 있을 경우 결과가 2로 떨어지고 데이터도 수정된 것을 확인할 수 있다. 없을 경우 1로 떨어지고 추가된다.

왜?!???

 

With ON DUPLICATE KEY UPDATE, the affected-rows value per row is 1 if the row is inserted as a new row, 2 if an existing row is updated, and 0 if an existing row is set to its current values. If you specify the CLIENT_FOUND_ROWS flag to the mysql_real_connect() C API function when connecting to mysqld, the affected-rows value is 1 (not 0) if an existing row is set to its current values.

결과에 대해 더 찾아보니 아래와 같이 내린다는 것을 알게 되었다.

  • 1: 신규 row로 insert
  • 2: 기존 row update
  • 0: 변경 없음

 참고

mysql 5.7의 경우

https://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html

 

MySQL :: MySQL 5.7 Reference Manual :: 13.2.5.2 INSERT ... ON DUPLICATE KEY UPDATE Statement

13.2.5.2 INSERT ... ON DUPLICATE KEY UPDATE Statement If you specify an ON DUPLICATE KEY UPDATE clause and a row to be inserted would cause a duplicate value in a UNIQUE index or PRIMARY KEY, an UPDATE of the old row occurs. For example, if column a is de

dev.mysql.com

 

mysql 8부터는 아래 문서를 확인해야 한다.

https://dev.mysql.com/doc/refman/8.0/en/insert-on-duplicate.html

 

MySQL :: MySQL 8.0 Reference Manual :: 15.2.7.2 INSERT ... ON DUPLICATE KEY UPDATE Statement

15.2.7.2 INSERT ... ON DUPLICATE KEY UPDATE Statement If you specify an ON DUPLICATE KEY UPDATE clause and a row to be inserted would cause a duplicate value in a UNIQUE index or PRIMARY KEY, an UPDATE of the old row occurs. For example, if column a is de

dev.mysql.com

 

 

728x90
반응형

'개발 > sql' 카테고리의 다른 글

[mysql] collation이란  (0) 2024.06.10
DB isolation level  (0) 2024.05.22
[mysql] 유저의 등수 구하기 rank under v8  (0) 2024.02.06
[DB] 분산환경에서 데이터 저장소 선택과 활용  (0) 2023.07.24
[형상관리] flyway vs liquibase  (0) 2022.07.08
반응형

try with resources

  • from java7; enhanced in java9
  • try 블락 안에 열린 리소스를 예외 여부와 상관없이 자동으로 닫아주는 기법
    • stream, db, network.. 등

 

예시

try (BufferedReader br = new BufferedReader(new FileReader("example.txt"))) {
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    // Exception handling
}

: BufferedReader, FileReader 와 같은 리소스의 close함수를 호출하지 않아도 자동으로 닫아줌

 

어떤 클래스들이 자동으로 닫히나?

AutoClosable interface 혹은 이를 확장한 AutoCloaseable interface를 구현한 리소스

public interface AutoCloseable {
    void close() throws Exception;
}
public interface Closeable extends AutoCloseable {
    public void close() throws IOException;
}

이를 구현한 예시로는

  • 스트림: FileInputStream, FileOutputStream, BufferedReader, BufferedWriter
  • reader/writer: InputStreamReader, OutputStreamWriter
  • sql connection: java.sql.Connection, java.sql.ResultSet, java.sql.Statement
  • 소켓: java.net.Socket
  • 채널: java.nio.channels > FileChannel, SocketChannel, ServerSocketChannel
  • zip: java.util.zip.ZipFile

 

728x90
반응형

+ Recent posts