MSSQL 테이블 변수, 임시 테이블 사용하기
실무에서 파트너사 알림톡 발송을 위해 대량의 데이터를 반복 Insert 할 일이 생겼는데 초기에 생각보다 시간이 너무 오래 걸려 효율적인 방법을 찾던 중 테이블 변수와 임시 테이블에 대해 알게 되어 정리할 겸 작성하기로 했다.
Join으로 조회한 데이터들을 While문을 통해 Row 접근으로 Update, Insert를 반복하는 작업인데 테이블 변수와 임시 테이블을 모르고 비효율적인 쿼리로 실행했을 땐 필요한 Data를 While문 안에서 계속 Join 하며 Select 했더니 아래 표처럼 기하급수적으로 시간이 늘어났다.
발송 Data Row 수 | 톡 발송 완료시간 |
50개 | 18초 |
100개 | 1분 10초 |
200개 | 4분 10초 |
300개 | 9분 20초 |
600개 | 37분 40초 |
시작한 직후엔 1초당 3개씩 발송하더니 시간이 갈수록 1초에 1개, 2초에 1개.... 이런 식으로 점점 딜레이가 됐었다.
운영계 DB에 불필요한 테이블을 만들지 않으려다 보니 이러한 결과가 나왔는데 테이블 변수나 임시 테이블을 사용하니 시간은 획기적으로 줄어들었다.
여담이지만 필요한 데이터를 파이썬으로 생성했더니 참 편한 것 같다.
테이블 변수
테이블 변수는 조회한 테이블 형태의 값들을 변수에 저장하는 것을 뜻한다.
변수이기 때문에 일반 변수와 동일하게 DECLARE로 "@"를 붙여 선언하고 메모리에 생성되기 때문에 쿼리가 실행되는 함수, 프로시저 안에서만 일시적으로 유효하다.
-- 테이블 변수 선언
DECLARE @TEMP_TABLE_NAME TABLE(
-- 컬럼 선언
COL1 VARCHAR(10),
COL2 INT
)
-- 기존 테이블 전체 테이블 변수로 복사
INSERT INTO @TEMP_TABLE_NAME
SELECT * FROM ORIGIN_TABLE;
-- Join한 결과에서 특정 컬럼만 테이블 변수로 복사
INSERT INTO @TEMP_TABLE_NAME
SELECT A.COL1, A.COL2, B.COL1, B.COL2 FROM ORIGIN_TABLE_1 AS A
INNER JOIN ORIGIN_TABLE_2 AS B
ON A.COL1 = B.COL1;
-- 조회 예시
SELECT * FROM @TEMP_TABLE_NAME;
장점
- 임시 테이블보다 빠르게 작동하며, 디스크 I/O를 줄일 수 있다.
- 트랜잭션 로그에 기록되지 않으므로 롤백이 필요하지 않다.
- 저장 프로시저, 함수, 트리거 등에서 사용할 수 있다.
- 컴파일 시점에 결정되므로, 재컴파일을 유발하지 않는다.
단점
- 통계를 생성하지 않아, 쿼리 최적화에 영향을 줄 수 있다.
- 인덱스를 생성할 수 없으므로, 대량의 데이터를 처리하는 데는 부적합하다. (100건 이상의 데이터 사용 시 성능 저하)
- 세션 간에 공유할 수 없다.
- ALTER TABLE 명령을 사용하여 구조를 변경할 수 없다.
임시 테이블
임시 테이블은 일시적으로 데이터를 저장하고 조작하는 데 사용하는 테이블이며 시스템 DB인 tempdb에 저장된다.
테이블이기 때문에 CREATE을 사용하고 "#"을 붙여 임시 테이블을 선언한다.
이때 "#"은 지역 임시 테이블을 뜻하고 테이블 변수와는 다르게 "##"을 통한 전역 임시 테이블 선언이 가능하다.
즉, 전역 선언을 통해 사용자 본인의 다른 Session에서도 접근이 가능하다.
명시적으로 삭제하지 않더라도 DB와 연결된 Session이 종료되면 자동으로 삭제되며 전역 임시 테이블의 경우 마지막 Session이 종료되면 삭제되기 때문에 실무에서 테이블 조작하기 편하게 해주는 것 같다.
주로 복잡한 쿼리를 단순화하거나 중간 결과를 저장하고 여러 프로시저에서 결과를 공유하는 용도로 사용한다.
-- 지역 임시 테이블 선언 ('#' 한 개)
CREATE TABLE #TEMP_TABLE_NAME (
-- 컬럼 선언
COL1 VARCHAR(10),
COL2 INT
)
-- 전역 임시 테이블 선언 ('#' 두 개)
CREATE TABLE ##TEMP_TABLE_NAME (
-- 컬럼 선언
COL1 VARCHAR(10),
COL2 INT
)
-- 기존 테이블 전체 테이블 변수로 복사
INSERT INTO #TEMP_TABLE_NAME
SELECT * FROM ORIGIN_TABLE;
-- Join한 결과에서 특정 컬럼만 테이블 변수로 복사
INSERT INTO #TEMP_TABLE_NAME
SELECT A.COL1, A.COL2, B.COL1, B.COL2 FROM ORIGIN_TABLE_1 AS A
INNER JOIN ORIGIN_TABLE_2 AS B
ON A.COL1 = B.COL1;
-- 조회 예시
SELECT * FROM #TEMP_TABLE_NAME;
장점
- 복잡한 쿼리를 단순화하고, 중간 결과를 저장하여 데이터 처리를 더 효율적으로 만들 수 있다.
- 큰 데이터 세트를 작은 부분으로 분할하여 처리하면 성능이 향상될 수 있다.
- 쿼리의 중간 결과를 확인하고 문제를 디버깅하는 데 도움이 된다.
- 사용자 세션별로 생성되므로 다른 사용자가 접근할 수 없어 보안에 좋다.
단점
- 트랜잭션 로그에 기록되므로, 많은 양의 데이터를 처리하면 로그 크기가 커질 수 있다.
- 디스크 I/O 작업에 대한 Overhead가 존재한다.
- 저장 프로시저 내부에서 실행할 때마다 재 컴파일이 발생한다.
적은 데이터(100건 이하)는 테이블 변수가 더 빠를 수 있지만 많은 데이터(100건 이상)는 임시 테이블이 성능적으로 유리하다.
필자는 애초에 비효율적인 쿼리였기 때문에 저장 프로시저 수정, 임시 테이블 적용, Update 필요 없는 행 로직 수행 제외 등 여러 차례에 걸쳐 튜닝한 결과 상당한 시간 단축을 이루었다.
다음은 데이터 1000건 기준의 테이블 변수와 임시 테이블 적용 후 알림톡 발송 실행 시간이다.
단일 Insert로는 최대 1000행이 가능하기 때문에 1000행을 기준으로 잡았으며 임시 테이블이 테이블 변수보다 25%가량 시간 단축 된 것을 확인할 수 있었다.
여태까진 쿼리에 대한 큰 고민을 하지 않은 것 같아 이번 경험을 계기로 끊임없이 고민하고 개선시키려 노력해야겠다.
'취업 후 학습' 카테고리의 다른 글
스프링 Annotation(어노테이션) 정리 (0) | 2023.06.28 |
---|---|
스프링이란? 스프링과 스프링 부트의 차이점은? (0) | 2023.06.20 |
리액트 data.map is not a function 에러 해결 (0) | 2023.05.03 |
파이썬 Pyautogui로 매크로 만들기 (0) | 2023.02.26 |
jQuery로 요소 찾기 (0) | 2023.01.03 |