트랜잭션
트랜잭션 (Transactions)
트랜잭션(transaction)은 하나의 단위로 커밋(commit)되거나 롤백(rollback)되는 SQL 문장들의 시퀀스예요. Snowflake 트랜잭션은 ACID 속성을 보장합니다.
본문
트랜잭션이란? (What is a transaction?)
트랜잭션은 원자적(atomic) 단위로 처리되는 SQL 문장들의 시퀀스예요. 트랜잭션 안의 모든 문장은 함께 적용되거나(커밋) 함께 취소됩니다(롤백). Snowflake 트랜잭션은 ACID 속성을 보장하며, 읽기와 쓰기를 모두 포함할 수 있어요.
트랜잭션은 다음 규칙을 따릅니다.
- 트랜잭션은 결코 중첩(nested)되지 않아요. 예를 들어 커밋된 내부 트랜잭션을 롤백하는 외부 트랜잭션을 만들거나, 롤백된 내부 트랜잭션을 커밋하는 외부 트랜잭션을 만들 수 없습니다.
- 트랜잭션은 단일 세션과 연결돼요. 여러 세션이 같은 트랜잭션을 공유할 수 없습니다. 같은 세션 안에서 겹치는 스레드로 트랜잭션을 처리하는 방법은 트랜잭션과 멀티스레딩 문서를 참고하세요.
용어: 이 문서에서 DDL은 CTAS 문(CREATE TABLE AS SELECT)과 데이터베이스 객체를 정의하는 기타 DDL 문을 포함해요. DML은 INSERT, UPDATE, DELETE, MERGE, TRUNCATE 문을 가리키고, 쿼리 문은 SELECT와 CALL 문을 가리킵니다. CALL 문(저장 프로시저를 호출)은 단일 문장이지만, 호출되는 저장 프로시저는 여러 문장을 포함할 수 있어요. 저장 프로시저와 트랜잭션에는 특별한 규칙이 있습니다.
명시적 트랜잭션 (Explicit transactions)
BEGIN 문을 실행해 트랜잭션을 명시적으로 시작할 수 있어요. Snowflake는 BEGIN WORK와 BEGIN TRANSACTION 동의어를 지원하며, BEGIN TRANSACTION 사용을 권장합니다. 트랜잭션은 COMMIT 또는 ROLLBACK을 실행해 명시적으로 끝낼 수 있어요. Snowflake는 COMMIT의 동의어로 COMMIT WORK, ROLLBACK의 동의어로 ROLLBACK WORK를 지원합니다.
일반적으로 트랜잭션이 이미 활성화되어 있으면 BEGIN TRANSACTION 문은 무시됩니다. 다만 사람이 읽을 때 COMMIT(또는 ROLLBACK) 문을 해당 BEGIN TRANSACTION 문과 짝지어 맞추기 어려워지므로, 불필요한 BEGIN TRANSACTION 문은 피해야 해요. 이 규칙의 한 가지 예외는 중첩된 저장 프로시저 호출과 관련되며, 스코프 트랜잭션(Scoped transactions) 문서에서 설명해요.
참고: 명시적 트랜잭션에는 DML 문과 쿼리 문만 포함해야 해요. DDL 문은 활성 트랜잭션을 암시적으로 커밋합니다(자세한 내용은 DDL 절 참고).
암시적 트랜잭션 (Implicit transactions)
트랜잭션은 명시적 BEGIN TRANSACTION 또는 COMMIT/ROLLBACK 없이도 암시적으로 시작되고 끝날 수 있어요. 암시적 트랜잭션은 명시적 트랜잭션과 동일하게 동작합니다. 다만 암시적 트랜잭션이 시작되는 시점을 결정하는 규칙은 명시적 트랜잭션의 시작 규칙과 달라요.
중지·시작 규칙은 문장이 DDL 문인지, DML 문인지, 쿼리 문인지에 따라 달라집니다. 문장이 DML 또는 쿼리 문이면 규칙은 AUTOCOMMIT이 활성화되었는지에 따라 달라져요.
DDL
각 DDL 문은 별도의 트랜잭션으로 실행돼요. 트랜잭션이 활성화된 동안 DDL 문이 실행되면, 그 DDL 문은 활성 트랜잭션을 암시적으로 커밋하고 DDL 문을 별도의 트랜잭션으로 실행합니다. DDL 문은 그 자체가 트랜잭션이므로 DDL 문을 롤백할 수 없어요. DDL을 포함한 트랜잭션은 명시적 ROLLBACK을 실행하기 전에 완료됩니다. DDL 문 바로 뒤에 DML 문이 오면 그 DML 문은 암시적으로 새 트랜잭션을 시작합니다.
AUTOCOMMIT
Snowflake는 AUTOCOMMIT 파라미터를 지원하며 기본값은 TRUE(활성화)예요.
AUTOCOMMIT이 활성화된 동안:
- 명시적 트랜잭션 밖의 각 문장은 마치 그 자체의 암시적 단일 문장 트랜잭션 안에 있는 것처럼 취급됩니다. 즉 그 문장은 성공하면 자동 커밋, 실패하면 자동 롤백돼요.
- 명시적 트랜잭션 안의 문장은
AUTOCOMMIT의 영향을 받지 않아요. 예를 들어AUTOCOMMIT이TRUE여도 명시적인BEGIN TRANSACTION ... ROLLBACK안의 문장은 롤백됩니다.
AUTOCOMMIT이 비활성화된 동안:
- 암시적
BEGIN TRANSACTION은 다음 시점에 실행됩니다:- 트랜잭션이 끝난 후 첫 DML 문(이전 트랜잭션을 끝낸 것이 DDL 문이든, 명시적
COMMIT/ROLLBACK이든 무관). AUTOCOMMIT을 비활성화한 후 첫 DML 문.
- 트랜잭션이 끝난 후 첫 DML 문(이전 트랜잭션을 끝낸 것이 DDL 문이든, 명시적
- 암시적
COMMIT은 다음과 같이 실행됩니다(트랜잭션이 이미 활성화된 경우):- DDL 문이 실행될 때.
ALTER SESSION SET AUTOCOMMIT문이 실행될 때 — 새 값이TRUE든FALSE든, 이전 값과 다른지 여부와 무관합니다. 예를 들어 이미FALSE인 상태에서AUTOCOMMIT을FALSE로 설정해도 암시적COMMIT이 실행돼요.
- 암시적
ROLLBACK은 다음과 같이 실행됩니다(트랜잭션이 이미 활성화된 경우):- 세션이 끝날 때.
- 저장 프로시저가 끝날 때. 저장 프로시저의 활성 트랜잭션이 명시적으로 시작됐든 암시적으로 시작됐든, Snowflake는 활성 트랜잭션을 롤백하고 오류 메시지를 발행해요.
주의: 저장 프로시저 안에서
AUTOCOMMIT설정을 변경하지 마세요. 오류 메시지가 반환됩니다.
트랜잭션의 암시적·명시적 시작/종료 혼합
혼동을 주는 코드를 피하려면, 같은 트랜잭션에서 암시적·명시적 시작과 종료를 혼합하지 않는 것이 좋아요. 다음은 법적으로 허용되지만 권장되지는 않습니다.
- 암시적으로 시작된 트랜잭션은 명시적
COMMIT또는ROLLBACK으로 끝낼 수 있음. - 명시적으로 시작된 트랜잭션은 암시적
COMMIT또는ROLLBACK으로 끝낼 수 있음.
트랜잭션 내 실패한 문장
트랜잭션은 단위로 커밋되거나 롤백되지만, 단위로 성공하거나 실패한다는 뜻과는 조금 달라요. 트랜잭션 안의 문장이 실패해도 트랜잭션을 롤백하는 대신 여전히 커밋할 수 있습니다.
트랜잭션 안의 DML 문이나 CALL 문이 실패하면, 그 실패한 문장이 만든 변경은 롤백됩니다. 그러나 트랜잭션은 전체가 커밋되거나 롤백될 때까지 활성 상태를 유지해요. 트랜잭션이 커밋되면 성공한 문장들이 만든 변경이 적용됩니다.
예를 들어, 두 개의 유효한 값과 한 개의 무효한 값을 테이블에 삽입하는 다음 코드를 보세요.
CREATE TABLE table1 (i int);
BEGIN TRANSACTION;
INSERT INTO table1 (i) VALUES (1);
INSERT INTO table1 (i) VALUES ('This is not a valid integer.'); -- FAILS!
INSERT INTO table1 (i) VALUES (2);
COMMIT;
SELECT i FROM table1 ORDER BY i;
실패한 INSERT 문 뒤의 문장들이 실행되면, 트랜잭션 안의 다른 문장 중 하나가 실패했어도 마지막 SELECT의 출력에 정수 값 1과 2의 행이 포함됩니다.
참고: 실패한
INSERT문 뒤의 문장들이 실행될 수도 있고 실행되지 않을 수도 있어요. 동작은 문장들이 어떻게 실행되고 오류가 어떻게 처리되는지에 따라 달라집니다. 예를 들어 이 문장들이 Snowflake Scripting 언어로 작성된 저장 프로시저 안에 있다면, 실패한INSERT문은 예외를 던져요. 예외가 처리되지 않으면 저장 프로시저는 완료되지 않고COMMIT이 실행되지 않으므로 열린 트랜잭션이 암시적으로 롤백됩니다. 그 경우 테이블에는 값 1과 2가 포함되지 않아요. 저장 프로시저가 예외를 처리하고 실패한INSERT전의 문장들은 커밋하지만 실패한INSERT후의 문장들은 실행하지 않으면, 테이블에는 값 1의 행만 저장됩니다. 이 문장들이 저장 프로시저 안에 없으면 동작은 실행 방식에 따라 달라져요. Snowsight를 통해 실행하면 첫 오류에서 실행이 중단되고, SnowSQL의-f(파일 이름) 옵션으로 실행하면 첫 오류에서 실행이 중단되지 않아 오류 뒤의 문장들도 실행됩니다.
트랜잭션과 멀티스레딩 (Transactions and multi-threading)
여러 세션은 같은 트랜잭션을 공유할 수 없지만, 단일 연결을 사용하는 여러 스레드는 같은 세션을 공유하므로 같은 트랜잭션을 공유해요. 이 동작은 예상치 못한 결과(예: 한 스레드가 다른 스레드에서 한 작업을 롤백하는 경우)를 낳을 수 있어요.
이 상황은 Snowflake 드라이버(예: Snowflake JDBC Driver)나 커넥터(예: Snowflake Connector for Python)를 사용하는 클라이언트 애플리케이션이 멀티스레드일 때 발생할 수 있어요. 두 개 이상의 스레드가 같은 연결을 공유하면 그 스레드들은 그 연결의 현재 트랜잭션도 공유합니다. 한 스레드의 BEGIN TRANSACTION, COMMIT, ROLLBACK은 그 공유 연결을 사용하는 모든 스레드에 영향을 줘요. 스레드가 비동기로 실행되면 결과는 예측할 수 없을 수 있습니다. 마찬가지로 한 스레드에서 AUTOCOMMIT 설정을 바꾸면 같은 연결을 사용하는 다른 모든 스레드의 AUTOCOMMIT 설정에 영향을 미쳐요.
Snowflake는 멀티스레드 클라이언트 프로그램이 다음 중 적어도 하나를 하도록 권장합니다.
- 각 스레드마다 별도의 연결 사용(별도 연결을 쓰더라도 여전히 레이스 컨디션이 발생해 예측할 수 없는 출력을 낼 수 있음에 유의).
- 단계가 수행되는 순서를 제어하기 위해 스레드를 비동기가 아니라 동기로 실행.
저장 프로시저와 트랜잭션 (Stored procedures and transactions)
일반적으로 이전 절에서 설명한 규칙은 저장 프로시저에도 적용돼요. 이 절은 저장 프로시저에 특화된 추가 정보를 제공합니다.
트랜잭션은 저장 프로시저 안에 있을 수 있고, 저장 프로시저는 트랜잭션 안에 있을 수 있어요. 그러나 트랜잭션은 저장 프로시저 안에 일부만 있고 밖에 일부가 있을 수 없으며, 한 저장 프로시저에서 시작해 다른 저장 프로시저에서 끝낼 수도 없습니다.
예를 들어:
- 저장 프로시저를 호출하기 전에 트랜잭션을 시작한 다음, 트랜잭션을 저장 프로시저 안에서 완료할 수 없어요. 이렇게 하려 하면 Snowflake는
Modifying a transaction that has started at a different scope is not allowed.같은 오류를 보고합니다. - 저장 프로시저 안에서 트랜잭션을 시작한 다음, 프로시저에서 돌아온 뒤에 트랜잭션을 완료할 수 없어요. 저장 프로시저 안에서 트랜잭션이 시작되었고 저장 프로시저가 끝날 때 여전히 활성 상태라면 오류가 발생하고 트랜잭션이 롤백됩니다.
이 규칙은 중첩 저장 프로시저에도 적용됩니다. 프로시저 A가 프로시저 B를 호출하면, 프로시저 B는 A에서 시작된 트랜잭션을 완료할 수 없고 그 반대도 마찬가지예요. A의 각 BEGIN TRANSACTION은 A에 대응하는 COMMIT(또는 ROLLBACK)이 있어야 하고, B의 각 BEGIN TRANSACTION은 B에 대응하는 COMMIT(또는 ROLLBACK)이 있어야 합니다.
저장 프로시저가 명시적 트랜잭션을 포함하면, 그 트랜잭션은 저장 프로시저 본문의 일부 또는 전부를 포함할 수 있어요. 예를 들어 다음 저장 프로시저에서는 일부 문장만 명시적 트랜잭션 안에 있습니다.
CREATE PROCEDURE ...
AS
$$
...
statement1;
BEGIN TRANSACTION;
statement2;
COMMIT;
statement3;
...
$$;
겹치지 않는 트랜잭션 (Non-overlapping transactions)
이 절에서는 트랜잭션 안에서 저장 프로시저 사용, 저장 프로시저 안에서 트랜잭션 사용을 설명해요.
트랜잭션 안에서 저장 프로시저 사용: 가장 단순한 경우로, 다음 조건이 충족되면 저장 프로시저가 트랜잭션 안에 있다고 간주해요.
- 저장 프로시저를 호출하기 전에
BEGIN TRANSACTION이 실행됨. - 저장 프로시저가 완료된 후에 대응하는
COMMIT(또는ROLLBACK)이 실행됨. - 저장 프로시저 본문에 명시적 또는 암시적
BEGIN TRANSACTION이나COMMIT(또는ROLLBACK)이 없음.
트랜잭션 안의 저장 프로시저는 바깥 트랜잭션의 규칙을 따릅니다. 트랜잭션이 커밋되면 프로시저 안의 모든 문장이 커밋되고, 트랜잭션이 롤백되면 프로시저 안의 모든 문장이 롤백돼요.
저장 프로시저 안에서 트랜잭션 사용: 저장 프로시저 안에서 0개, 1개 또는 그 이상의 트랜잭션을 실행할 수 있어요. 한 저장 프로시저에 두 트랜잭션이 있는 예시가 아래에 있어요. 이 코드에서는 네 개의 별도 트랜잭션이 실행되며, 각 트랜잭션은 프로시저 밖에서 시작해 완료되거나 프로시저 안에서 시작해 완료됩니다. 어느 트랜잭션도 프로시저 경계에 걸쳐 분할되지 않고(일부는 저장 프로시저 안, 일부는 밖), 어느 트랜잭션도 다른 트랜잭션에 중첩되지 않아요.
각 트랜잭션의 시작점과 끝점이 트랜잭션에 포함되는 문장을 결정합니다. 시작과 끝은 명시적 또는 암시적일 수 있어요. 각 SQL 문장은 오직 하나의 트랜잭션의 일부입니다. 바깥의 ROLLBACK이나 COMMIT은 안쪽의 COMMIT이나 ROLLBACK을 취소하지 않아요.
참고: "내부(inner)"와 "외부(outer)"라는 용어는 중첩 저장 프로시저 호출 같은 중첩 작업을 설명할 때 흔히 쓰입니다. 그러나 Snowflake의 트랜잭션은 진정한 "중첩"이 아니므로, 트랜잭션을 지칭할 때 혼동을 줄이기 위해 이 문서는 "inner/outer" 대신 "enclosed/enclosing"이라는 용어를 자주 사용해요.
스코프 트랜잭션 (Scoped transactions)
트랜잭션을 포함하는 저장 프로시저는 다른 트랜잭션 안에서 호출될 수 있어요. 예를 들어 저장 프로시저 안의 트랜잭션은 트랜잭션을 포함하는 다른 저장 프로시저에 대한 호출을 포함할 수 있습니다. Snowflake는 내부 트랜잭션을 중첩으로 취급하지 않고, 내부 트랜잭션을 별도의 트랜잭션으로 취급해요. Snowflake는 이를 "autonomous scoped transactions"(줄여서 "scoped transactions")라고 부릅니다.
각 스코프 트랜잭션의 시작점과 끝점이 그 트랜잭션에 포함되는 문장을 결정해요. 바깥 스코프 트랜잭션이 안쪽 스코프 트랜잭션과 시간상 겹칠 수는 있지만, 내용상 겹치지는 않습니다.
다음은 시간상 겹치는 세 개의 스코프 트랜잭션 예시예요. 이 예시에서 저장 프로시저 p1()은 트랜잭션 안에서 다른 저장 프로시저 p2()를 호출하고, p2()는 그 자체의 트랜잭션을 포함하므로 p2()에서 시작된 트랜잭션도 독립적으로 실행됩니다.
CREATE PROCEDURE p2()
...
$$
BEGIN TRANSACTION;
statement C;
COMMIT;
$$;
CREATE PROCEDURE p1()
...
$$
BEGIN TRANSACTION;
statement B;
CALL p2();
statement D;
COMMIT;
$$;
BEGIN TRANSACTION;
statement A;
CALL p1();
statement E;
COMMIT;
이 세 개의 스코프 트랜잭션에서:
- 저장 프로시저 밖에 있는 트랜잭션은 문장 A와 E를 포함.
- 저장 프로시저
p1()의 트랜잭션은 문장 B와 D를 포함. p2()의 트랜잭션은 문장 C를 포함.
스코프 트랜잭션 규칙은 재귀적 저장 프로시저 호출에도 적용돼요. 재귀 호출은 중첩 호출의 특정 유형일 뿐이며, 중첩 호출과 같은 트랜잭션 규칙을 따릅니다.
주의: 겹치는 스코프 트랜잭션은 같은 데이터베이스 객체(예: 테이블)를 조작하면 교착 상태(deadlock)를 일으킬 수 있어요. 스코프 트랜잭션은 필요한 경우에만 사용해야 합니다.
저장 프로시저 안 트랜잭션의 암시적 커밋
대부분의 DDL 문을 포함한 일부 명령은 활성 트랜잭션을 암시적으로 커밋해요. 바깥 저장 프로시저가 트랜잭션을 열고 안쪽 저장 프로시저가 그러한 명령을 실행하면, 그 명령은 Modifying a transaction that has started at a different scope is not allowed. 오류 메시지를 반환합니다.
예를 들어 다음 코드는 안쪽 프로시저의 DROP TAG 문이 바깥 프로시저에서 시작된 트랜잭션을 암시적으로 커밋하려 하므로 실패해요.
CREATE OR REPLACE PROCEDURE test_scoped_outer()
RETURNS VARIANT
LANGUAGE JAVASCRIPT
EXECUTE AS CALLER
AS $$
snowflake.execute({sqlText: `BEGIN TRANSACTION;`});
snowflake.execute({sqlText: `CALL test_scoped();`});
snowflake.execute({sqlText: `COMMIT;`});
$$;
CREATE OR REPLACE PROCEDURE test_scoped()
RETURNS VARIANT
LANGUAGE JAVASCRIPT
EXECUTE AS CALLER
AS $$
snowflake.execute({sqlText: `CREATE OR REPLACE TAG test;`}); -- works
snowflake.execute({sqlText: `DROP TAG IF EXISTS test;`}); -- fails
$$;
CALL test_scoped_outer();
이 오류를 피하려면, 다른 스코프에서 시작된 활성 트랜잭션 안에서 호출될 수 있는 저장 프로시저 안에서 DDL 문(또는 트랜잭션을 암시적으로 커밋하는 다른 명령)을 실행하지 마세요.
저장 프로시저 끝에서 트랜잭션의 암시적 롤백
AUTOCOMMIT이 비활성화되면 암시적 트랜잭션과 저장 프로시저를 결합할 때 특히 주의해야 해요. 실수로 저장 프로시저 끝에 활성 트랜잭션을 남기면 그 트랜잭션은 롤백됩니다.
CREATE PROCEDURE p1() ...
$$
INSERT INTO parent_table ...;
INSERT INTO child_table ...;
$$;
ALTER SESSION SET AUTOCOMMIT = FALSE;
CALL p1;
COMMIT WORK;
이 예시에서 AUTOCOMMIT을 설정하는 명령은 활성 트랜잭션을 커밋합니다. 새 트랜잭션은 즉시 시작되지 않아요. 저장 프로시저에는 DML 문이 있어 암시적으로 새 트랜잭션을 시작합니다. 그 암시적 BEGIN TRANSACTION에는 저장 프로시저 안에 짝이 맞는 COMMIT 또는 ROLLBACK이 없어요. 저장 프로시저 끝에 활성 트랜잭션이 있으므로 그 활성 트랜잭션은 암시적으로 롤백됩니다.
전체 저장 프로시저를 단일 트랜잭션에서 실행하려면 저장 프로시저를 호출하기 전에 트랜잭션을 시작하고 호출 후에 커밋하세요. 이 경우 BEGIN과 COMMIT이 올바르게 짝지어져 코드가 오류 없이 실행됩니다. 대안으로 BEGIN TRANSACTION과 COMMIT을 모두 저장 프로시저 안에 넣을 수도 있어요.
스코프 트랜잭션에서 잘못 짝지어진 BEGIN/COMMIT 블록
스코프 트랜잭션에서 BEGIN/COMMIT 블록을 올바르게 짝짓지 않으면 Snowflake가 오류를 보고해요. 그 오류는 추가 영향(저장 프로시저 완료 방해, 바깥 트랜잭션 커밋 방해 등)을 줄 수 있습니다. 예를 들어 다음 코드에서는 바깥 저장 프로시저와 안쪽 저장 프로시저의 일부 문장이 롤백되어 삽입되는 값은 osp1_alpha뿐이에요.
CREATE OR REPLACE PROCEDURE outer_sp1()
...
AS
$$
INSERT 'osp1_alpha';
BEGIN WORK;
INSERT 'osp1_beta';
CALL inner_sp2();
INSERT 'osp1_delta';
COMMIT WORK;
INSERT 'osp1_omega';
$$;
CREATE OR REPLACE PROCEDURE inner_sp2()
...
AS
$$
BEGIN WORK;
INSERT 'isp2';
-- Missing COMMIT, so implicitly rolls back!
$$;
CALL outer_sp1();
안쪽 프로시저 inner_sp2()가 끝나면 Snowflake는 inner_sp2()의 BEGIN에 대응하는 COMMIT(또는 ROLLBACK)이 없음을 감지해, inner_sp2()에서 시작된 스코프 트랜잭션을 암시적으로 롤백하고 CALL 실패로 인한 오류도 반환해요. inner_sp2()에 대한 CALL이 outer_sp1() 안에 있었으므로 저장 프로시저 outer_sp1() 자체도 실패하고 오류를 반환합니다. outer_sp1()이 끝까지 실행되지 않으므로 osp1_delta, osp1_omega 값을 위한 INSERT 문은 실행되지 않고, outer_sp1()의 열린 트랜잭션은 커밋 대신 암시적으로 롤백되어 osp1_beta 값의 삽입이 커밋되지 않아요.
Apache Iceberg™ 테이블과 트랜잭션
Snowflake 트랜잭션 원칙은 일반적으로 Apache Iceberg™ 테이블에도 적용돼요. Iceberg 테이블에 특화된 트랜잭션에 대한 자세한 내용은 Iceberg 트랜잭션 문서를 참고하세요.
READ COMMITTED 격리 수준
READ COMMITTED는 현재 테이블에 대해 지원되는 유일한 격리 수준이에요. READ COMMITTED 격리에서는 문장이 시작되기 전에 커밋된 데이터만 보이며, 커밋되지 않은 데이터는 절대 보지 못해요.
다중 문장 트랜잭션 안에서 문장이 실행될 때:
- 문장은 문장이 시작되기 전에 커밋된 데이터만 봅니다.
- 같은 트랜잭션의 연속된 두 문장은, 첫 문장과 두 번째 문장 실행 사이에 다른 트랜잭션이 커밋되면 서로 다른 데이터를 볼 수 있어요.
- 문장은 같은 트랜잭션 안에서 이전에 실행된 문장이 만든 변경을 봅니다(아직 커밋되지 않았더라도).
세션 간 읽기 일관성 (Read consistency across sessions)
일반적으로 Snowflake는 주어진 세션 안에서 발생하는 모든 변경(DDL과 DML 작업이 도입한 변경 등)에 대해 읽기 일관성을 유지해요. 사용자가 새 세션을 시작하면, 세션이 시작되기 전에 커밋된 모든 변경과 세션 안에서 커밋된 모든 변경이 그 세션의 이후 쿼리에 즉시 보입니다.
거의 동시에 실행되는 세션들에 걸쳐 읽기 일관성이 보장되도록 확장하고, 쿼리 응답 시간에 작은 지연(보통 밀리초)을 감수할 수 있다면 READ_CONSISTENCY_MODE 파라미터를 'GLOBAL'로 설정하세요. 이 파라미터를 설정하면 동시에 실행되는 세션에서 발생하는 거의 동시의 변경을 쿼리가 읽도록 기본 동작이 바뀌어요. 이 수준의 일관성을 보장하는 대안은 모든 쿼리를 같은 세션에서 실행하는 것입니다.
기본값인 'SESSION' 값에서는, 세션 1과 세션 2가 동시에 실행될 때 세션 1이 테이블 t에 행을 삽입하면 세션 1은 새 행을 즉시 보지만 세션 2는 같은 쿼리를 실행해도 새 행을 보지 못할 수 있습니다. 이 상황에서 같은 쿼리에 대해 세션 1과 2가 같은 결과를 얻도록 보장하려면 다음 세 단계 중 하나를 따르세요(가장 권장되는 순서부터).
- 서로 의존하는 모든 쿼리에 단일 세션을 사용.
- 세션 1에서 변경이 커밋된 후 세션 2를 시작.
ALTER ACCOUNT명령으로READ_CONSISTENCY_MODE를'GLOBAL'로 설정:
ALTER ACCOUNT SET READ_CONSISTENCY_MODE = 'GLOBAL';
이 파라미터는 ACCOUNTADMIN 권한을 가진 사용자만 계정 수준에서 설정할 수 있어요.
리소스 잠금 (Resource locking)
트랜잭션 작업은 리소스(예: 테이블)가 수정되는 동안 그 리소스에 대한 잠금을 획득해요. 잠금은 해제될 때까지 다른 문장이 리소스를 수정하지 못하게 차단합니다. 대부분의 상황에서 다음 지침이 적용됩니다.
COMMIT작업(AUTOCOMMIT과 명시적COMMIT포함)은 리소스를 잠그지만 보통 잠깐 동안만이에요.CREATE TABLE,CREATE DYNAMIC TABLE,CREATE STREAM,ALTER TABLE작업은CHANGE_TRACKING = TRUE를 설정할 때 기본 리소스를 잠그지만 보통 잠깐 동안만이에요.- 테이블이 잠기면
UPDATE와DELETEDML 작업만 차단되고,INSERT작업은 차단되지 않아요. UPDATE,DELETE,MERGE문은 다른UPDATE,DELETE,MERGE문과 병렬로 실행되지 않게 하는 잠금을 보유합니다.- 하이브리드 테이블의 경우 잠금이 개별 행에 보유돼요.
UPDATE,DELETE,MERGE문의 잠금은 같은 행(들)에 작동하는 병렬UPDATE,DELETE,MERGE문만 방지합니다. 같은 테이블의 서로 다른 행에 대한UPDATE,DELETE,MERGE는 진행될 수 있어요. - 대부분의
INSERT와COPY문은 새 파티션만 씁니다. 이 문들은 종종 다른INSERT,COPY작업과 병렬로 실행될 수 있고, 때로는UPDATE,DELETE,MERGE문과도 병렬로 실행될 수 있어요. - 다른 세션에서 같은 객체에 대해
INSERT·COPY문을 DDL 문과 동시에 실행하는 것은 일관성 문제를 일으킬 수 있으므로 피하세요. 명시적 트랜잭션 안의 객체에INSERT또는COPY문이 실행되는 동안에는, 트랜잭션 기간 동안 다른 세션에서 같은 객체에 대한 DDL 문을 피하세요.
문장이 보유한 잠금은 트랜잭션의 COMMIT 또는 ROLLBACK 시 해제됩니다.
잠금 타임아웃 파라미터
잠금 타임아웃을 제어하는 두 파라미터가 있어요: LOCK_TIMEOUT과 HYBRID_TABLE_LOCK_TIMEOUT.
LOCK_TIMEOUT: 차단된 문장은 기다리는 리소스를 사용할 수 있게 될 때까지 잠금을 획득하거나 타임아웃됩니다. 문장이 차단되는 시간(초)은 LOCK_TIMEOUT 파라미터로 설정할 수 있어요. 예를 들어 현재 세션의 잠금 타임아웃을 2시간(7200초)으로 바꾸려면:
ALTER SESSION SET LOCK_TIMEOUT=7200;
SHOW PARAMETERS LIKE 'lock_timeout';
LOCK_TIMEOUT의 기본값은 43200이고, 세션 레벨로 설정되며, 리소스를 잠그려 시도할 때 타임아웃되어 문장을 중단하기까지 기다리는 시간(초)을 나타냅니다. 값 0은 잠금 대기를 끕니다(즉, 문장이 즉시 잠금을 획득하거나 중단). 문장이 여러 리소스를 잠가야 하면 각 잠금 시도에 타임아웃이 별도로 적용됩니다.
HYBRID_TABLE_LOCK_TIMEOUT: 하이브리드 테이블에서 차단된 문장은 기다리는 테이블이 사용 가능해질 때까지 행 수준 잠금을 획득하거나 타임아웃됩니다. 문장이 차단되는 시간(초)은 이 파라미터로 설정할 수 있어요. 예를 들어 현재 세션의 하이브리드 테이블 잠금 타임아웃을 10분(600초)으로 바꾸려면:
ALTER SESSION SET HYBRID_TABLE_LOCK_TIMEOUT=600;
SHOW PARAMETERS LIKE 'hybrid_table_lock_timeout';
이 파라미터의 기본값은 3600이며, 잠금을 획득하려 시도할 때 타임아웃되어 문장을 중단하기까지 기다리는 시간(초)을 나타냅니다.
교착 상태 (Deadlocks)
교착 상태는 동시 트랜잭션이 서로에게 잠긴 리소스를 기다릴 때 발생할 수 있어요. 다음 규칙을 유의하세요.
- autocommit 쿼리 문이 동시에 실행되는 동안에는 교착 상태가 발생할 수 없어요.
SELECT문은 항상 읽기 전용이므로 표준 테이블과 하이브리드 테이블 모두에 해당됩니다. - 표준 테이블의 autocommit DML 작업에서는 교착 상태가 발생할 수 없지만, 하이브리드 테이블의 autocommit DML 작업에서는 발생할 수 있어요.
- 트랜잭션이 명시적으로 시작되고 각 트랜잭션에서 여러 문장이 실행될 때 교착 상태가 발생할 수 있어요. Snowflake는 교착 상태를 감지하고 교착 상태의 일부인 가장 최근 문장을 희생자로 선택합니다. 그 문장은 롤백되지만 트랜잭션 자체는 활성 상태로 남아 커밋되거나 롤백되어야 해요.
- 교착 상태 감지는 시간이 걸릴 수 있어요.
트랜잭션과 잠금 관리 (Managing transactions and locks)
Snowflake는 트랜잭션과 잠금을 모니터링·관리하는 데 도움이 되는 다음 SQL 명령을 제공해요.
DESCRIBE TRANSACTIONROLLBACKSHOW LOCKSSHOW TRANSACTIONS
LOCK_WAIT_HISTORY 뷰는 특정 잠금이 요청되고 획득된 시점을 보여주며, 잠금과 관련된 트랜잭션의 상세 기록을 기록해요.
또한 Snowflake는 세션 내 트랜잭션에 대한 정보를 얻기 위한 다음 컨텍스트 함수를 제공합니다.
CURRENT_STATEMENTCURRENT_TRANSACTIONLAST_QUERY_IDLAST_TRANSACTION
트랜잭션을 중단하려면 SYSTEM$ABORT_TRANSACTION 함수를 호출할 수 있어요.
트랜잭션 중단 (Aborting transactions)
세션에서 트랜잭션이 실행 중인데 세션이 갑자기 끊겨 트랜잭션이 커밋되거나 롤백되지 못하면, 그 트랜잭션은 보유하고 있던 리소스 잠금을 포함해 분리(detached)된 상태로 남게 돼요. 이런 일이 발생하면 트랜잭션을 중단해야 할 수 있습니다. 실행 중인 트랜잭션을 중단하려면 트랜잭션을 시작한 사용자나 계정 관리자가 시스템 함수 SYSTEM$ABORT_TRANSACTION을 호출할 수 있어요.
사용자가 트랜잭션을 중단하지 않으면:
- 다른 트랜잭션이 같은 테이블에 잠금을 획득하지 못하게 차단하면서 5분 동안 유휴 상태면 자동으로 중단되고 롤백됩니다.
- 다른 트랜잭션이 같은 테이블을 수정하는 것을 차단하지 않으면서 4시간보다 오래되면 자동으로 중단되고 롤백됩니다.
- 하이브리드 테이블을 읽거나 쓰면서 5분 동안 유휴 상태면, 다른 트랜잭션이 같은 테이블을 수정하는 것을 차단하는지 여부와 무관하게 자동으로 중단되고 롤백됩니다.
트랜잭션 안의 문장 오류가 트랜잭션을 중단하도록 하려면 세션 또는 계정 수준에서 TRANSACTION_ABORT_ON_ERROR 파라미터를 설정하세요.
LOCK_WAIT_HISTORY 뷰로 차단된 트랜잭션 분석
LOCK_WAIT_HISTORY 뷰는 차단된 트랜잭션을 분석하는 데 유용한 트랜잭션 세부 정보를 반환해요. 출력의 각 행에는 잠금을 기다리는 트랜잭션의 세부 정보와 그 잠금을 보유하거나 그 잠금을 앞에서 기다리는 트랜잭션의 세부 정보가 포함됩니다.
예를 들어 트랜잭션 B가 잠금을 기다리는 트랜잭션이고, 트랜잭션 B가 타임스탬프 T1에 잠금을 요청했으며, 트랜잭션 A가 잠금을 보유하는 트랜잭션이라고 해볼게요. 트랜잭션 A의 쿼리 2가 블로커 쿼리(blocker query)입니다. 트랜잭션 A(잠금을 보유하는 트랜잭션)에서 트랜잭션 B(잠금을 기다리는 트랜잭션)가 기다리기 시작한 첫 문장이 쿼리 2이기 때문이에요. 다만 트랜잭션 A의 이후 쿼리(쿼리 5)도 잠금을 획득했다는 점에 유의하세요. 이 트랜잭션들의 이후 동시 실행으로 트랜잭션 B가 트랜잭션 A에서 잠금을 획득하는 다른 쿼리에서 차단될 수 있습니다. 따라서 첫 번째 블로커 트랜잭션의 모든 쿼리를 조사해야 해요.
오래 실행 중인 문장 검사
지난 24시간 동안 잠금을 기다린 트랜잭션을 Account Usage QUERY_HISTORY 뷰로 조회해요.
SELECT query_id, query_text, start_time, session_id, execution_status, total_elapsed_time,
compilation_time, execution_time, transaction_blocked_time
FROM snowflake.account_usage.query_history
WHERE start_time >= dateadd('hours', -24, current_timestamp())
AND transaction_blocked_time > 0
ORDER BY transaction_blocked_time DESC;
결과를 검토하고 TRANSACTION_BLOCKED_TIME 값이 높은 쿼리의 쿼리 ID를 기록하세요. 그 쿼리들의 블로커 트랜잭션을 찾으려면 그 쿼리 ID가 있는 행에 대해 LOCK_WAIT_HISTORY 뷰를 조회합니다.
SELECT object_name, lock_type, transaction_id, blocker_queries
FROM snowflake.account_usage.lock_wait_history
WHERE query_id = '<query_id>';
결과의 blocker_queries 컬럼에 여러 쿼리가 있을 수 있어요. 출력에서 각 블로커 쿼리의 transaction_id를 기록한 뒤, blocker_queries 출력의 각 트랜잭션에 대해 QUERY_HISTORY 뷰를 조회하세요.
SELECT query_id, query_text, start_time, session_id, execution_status, total_elapsed_time, compilation_time, execution_time
FROM snowflake.account_usage.query_history
WHERE transaction_id = '<transaction_id>';
트랜잭션과 잠금 모니터링 (Monitoring transactions and locks)
SHOW TRANSACTIONS 명령으로 현재 사용자(그 사용자의 모든 세션에서) 또는 계정의 모든 사용자가 모든 세션에서 실행 중인 트랜잭션 목록을 반환할 수 있어요. 다음 예제는 현재 사용자의 세션에 대한 것입니다.
SHOW TRANSACTIONS;
모든 Snowflake 트랜잭션에는 고유한 트랜잭션 ID가 할당돼요. id 값은 부호 있는 64비트(long) 정수이며, 범위는 -9,223,372,036,854,775,808 (-2^63)부터 9,223,372,036,854,775,807 (2^63 - 1)까지입니다.
세션에서 현재 실행 중인 트랜잭션의 트랜잭션 ID를 반환하려면 CURRENT_TRANSACTION 함수를 사용할 수 있어요.
SELECT CURRENT_TRANSACTION();
모니터링하려는 트랜잭션 ID를 알고 있으면 DESCRIBE TRANSACTION 명령으로 트랜잭션에 대한 세부 정보를(실행 중이거나 커밋·중단된 후) 반환할 수 있어요.
DESCRIBE TRANSACTION 1721161383427000000;
하이브리드 테이블의 트랜잭션 및 잠금 가시성
하이브리드 테이블에 접근하는 트랜잭션 또는 하이브리드 테이블 행의 잠금에 대한 명령·뷰 출력을 볼 때 다음 동작을 유의하세요.
- 트랜잭션은 다른 트랜잭션을 차단하거나 차단된 경우에만 나열됩니다. 하이브리드 테이블에 접근하는 트랜잭션은 행 수준 잠금(
ROW유형)을 보유한다는 점을 기억하세요. 두 트랜잭션이 같은 테이블의 서로 다른 행에 접근하면 서로를 차단하지 않습니다. - 트랜잭션은 차단된 트랜잭션이 5초 이상 차단된 경우에만 나열됩니다. 트랜잭션이 더 이상 차단되지 않으면 출력에 계속 나타날 수 있지만 15초를 넘지 않아요.
SHOW LOCKS출력에서도 비슷한 규칙이 적용됩니다. 한 트랜잭션이 잠금을 보유하고 다른 트랜잭션이 그 특정 잠금에서 차단된 경우에만 잠금이 나열됩니다.type컬럼에서 하이브리드 테이블 잠금은ROW를 보여주고,resource컬럼은 항상 차단하는 트랜잭션 ID를 보여줍니다. 하이브리드 테이블에 대한 조회는 종종 쿼리 ID를 생성하지 않아요.LOCK_WAIT_HISTORY뷰에서lock_type과object_name컬럼은 모두Row값을 보여주고,schema_id·schema_name컬럼은 항상 비어 있으며(각각0과NULL),object_id컬럼은 항상 차단하는 객체의 ID를 보여줍니다.blocker_queries컬럼은 차단하는 트랜잭션을 보여주는 정확히 한 요소를 가진 JSON 배열이에요. 여러 트랜잭션이 같은 행에서 차단되면 출력에 여러 행으로 표시됩니다.
모범 사례 (Best practices)
- 트랜잭션은 서로 관련되고 함께 성공하거나 실패해야 하는 문장을 포함해야 해요. 예를 들어 한 계정에서 돈을 인출하고 같은 돈을 다른 계정에 입금하는 경우죠. 롤백이 발생하면 지급인 또는 수취인 중 한쪽이 돈을 갖게 되며, 돈은 결코 "사라지지" 않습니다.
- 일반적으로 한 트랜잭션에는 관련 문장만 포함해야 해요. 문장을 덜 세분화하면 트랜잭션이 롤백될 때 실제로 롤백할 필요가 없었던 유용한 작업까지 롤백할 수 있어요.
- 표준 테이블에서는 더 큰 트랜잭션이 어떤 경우 성능을 개선할 수 있지만, 하이브리드 테이블에서는 일반적으로 그렇지 않아요. 다만 앞선 항목이 정말로 그룹으로 커밋·롤백해야 하는 문장만 묶는 것의 중요성을 강조했지만, 더 큰 트랜잭션이 때로는 유용할 수 있습니다. Snowflake에서도 대부분의 데이터베이스처럼 트랜잭션 관리는 리소스를 소비해요. 예를 들어 한 트랜잭션에서 10행을 삽입하는 것이 10개의 별도 트랜잭션에서 각각 한 행씩 삽입하는 것보다 일반적으로 더 빠르고 저렴합니다. 여러 문장을 단일 트랜잭션으로 결합하면 성능이 개선될 수 있어요.
- 과도하게 큰 트랜잭션은 병렬성을 줄이거나 교착 상태를 늘릴 수 있어요. 성능 개선을 위해 무관한 문장을 그룹화하기로 결정했다면, 트랜잭션이 리소스에 잠금을 획득해 다른 쿼리를 지연시키거나 교착 상태로 이어질 수 있다는 점을 기억하세요.
- 하이브리드 테이블의 경우:
AUTOCOMMITDML 문은 일반적으로 비-AUTOCOMMIT DML 문보다 훨씬 빠르게 실행됩니다. 비교적 작은AUTOCOMMITDML 문은 비-AUTOCOMMIT DML 문보다 훨씬 빠르고, 5초 미만으로 실행되거나 1MB 이하의 데이터에 접근하는 DML 문은 더 오래 실행되거나 더 큰 DML 문에는 없는 빠른 모드를 활용합니다. - Snowflake는
AUTOCOMMIT을 활성 상태로 유지하고 가능한 한 명시적 트랜잭션을 사용하는 것을 권장합니다. 명시적 트랜잭션을 사용하면 독자가 트랜잭션의 시작과 끝이 어디인지 쉽게 볼 수 있어요. 이를AUTOCOMMIT과 결합하면 (예를 들어 저장 프로시저 끝에서) 의도하지 않은 롤백이 발생할 가능성이 줄어듭니다. - 새 트랜잭션을 암시적으로 시작하려고
AUTOCOMMIT을 바꾸는 것은 피하세요. 대신BEGIN TRANSACTION을 사용해 새 트랜잭션이 시작되는 곳을 더 명확히 하세요. - 한 줄에
BEGIN TRANSACTION문을 두 번 이상 실행하는 것은 피하세요. 불필요한BEGIN TRANSACTION문은 트랜잭션이 실제로 시작되는 곳을 보기 어렵게 하고,COMMIT/ROLLBACK명령을 해당BEGIN TRANSACTION과 짝짓기 어렵게 만듭니다.
예제 (Examples)
스코프 트랜잭션과 저장 프로시저의 간단한 예제
저장 프로시저가 값 12의 행을 삽입한 뒤 롤백하는 트랜잭션을 포함하고, 바깥 트랜잭션은 커밋하는 예시예요. 바깥 트랜잭션 스코프의 모든 행은 유지되고, 안쪽 트랜잭션 스코프의 행은 유지되지 않습니다. 저장 프로시저의 일부만 그 자체 트랜잭션 안에 있으므로, 저장 프로시저 안에 있지만 저장 프로시저의 트랜잭션 밖에 있는 INSERT 문이 삽입한 값은 유지된다는 점에 유의하세요.
create table tracker_1 (id integer, name varchar);
create table tracker_2 (id integer, name varchar);
create procedure sp1()
returns varchar
language javascript
AS
$$
// This is part of the outer transaction that started before this
// stored procedure was called. This is committed or rolled back
// as part of that outer transaction.
snowflake.execute (
{sqlText: "insert into tracker_1 values (11, 'p1_alpha')"}
);
// This is an independent transaction. Anything inserted as part of this
// transaction is committed or rolled back based on this transaction.
snowflake.execute (
{sqlText: "begin transaction"}
);
snowflake.execute (
{sqlText: "insert into tracker_2 values (12, 'p1_bravo')"}
);
snowflake.execute (
{sqlText: "rollback"}
);
// This is part of the outer transaction started before this
// stored procedure was called. This is committed or rolled back
// as part of that outer transaction.
snowflake.execute (
{sqlText: "insert into tracker_1 values (13, 'p1_charlie')"}
);
// Dummy value.
return "";
$$;
begin transaction;
insert into tracker_1 values (00, 'outer_alpha');
call sp1();
insert into tracker_1 values (09, 'outer_zulu');
commit;
결과에는 00, 11, 13, 09가 포함되어야 하고, ID = 12 행은 포함되지 않아야 해요. 그 행은 롤백된 안쪽 트랜잭션의 스코프에 있었습니다. 다른 모든 행은 바깥 트랜잭션의 스코프에 있었고 커밋됐습니다. 특히 ID 11, 13 행은 저장 프로시저 안에 있었지만 가장 안쪽 트랜잭션 밖에 있어서 바깥 트랜잭션의 스코프에 있었고, 그와 함께 커밋됐어요.
트랜잭션 성공과 무관하게 정보 로깅
스코프 트랜잭션의 간단하고 실용적인 예시예요. 트랜잭션이 특정 정보를 로깅하고, 그 로깅된 정보는 트랜잭션 자체의 성공 여부와 무관하게 보존됩니다. 이 기법은 각 시도한 작업이 성공했는지 여부와 무관하게 모든 시도 작업을 추적하는 데 사용할 수 있어요.
create table data_table (id integer);
create table log_table (message varchar);
create procedure log_message(MESSAGE VARCHAR)
returns varchar
language javascript
AS
$$
// This is an independent transaction. Anything inserted as part of this
// transaction is committed or rolled back based on this transaction.
snowflake.execute (
{sqlText: "begin transaction"}
);
snowflake.execute (
{sqlText: "insert into log_table values ('" + MESSAGE + "')"}
);
snowflake.execute (
{sqlText: "commit"}
);
// Dummy value.
return "";
$$;
create procedure update_data()
returns varchar
language javascript
AS
$$
snowflake.execute (
{sqlText: "begin transaction"}
);
snowflake.execute (
{sqlText: "insert into data_table (id) values (17)"}
);
snowflake.execute (
{sqlText: "call log_message('You should see this saved.')"}
);
snowflake.execute (
{sqlText: "rollback"}
);
// Dummy value.
return "";
$$;
begin transaction;
call update_data();
rollback;
data_table은 트랜잭션이 롤백되었으므로 비어 있지만, log_table은 비어 있지 않습니다. log_table에 대한 삽입은 data_table에 대한 삽입과 별도의 트랜잭션에서 수행됐기 때문이에요.
스코프 트랜잭션과 저장 프로시저의 예제
다음 예제들은 아래 테이블과 저장 프로시저를 사용해요. 적절한 파라미터를 전달함으로써 호출자는 저장 프로시저 안에서 BEGIN TRANSACTION, COMMIT, ROLLBACK 문이 실행되는 위치를 제어할 수 있습니다.
세 단계 중 중간 단계 커밋하기: 이 예제는 3개의 트랜잭션을 포함하며, 가장 바깥 트랜잭션이 감싸고 가장 안쪽 트랜잭션을 감싸는 "중간" 단계를 커밋합니다. 가장 바깥과 가장 안쪽 트랜잭션은 롤백됩니다. 결과적으로 중간 트랜잭션의 행(12, 21, 23)만 커밋됩니다.
begin transaction;
insert into tracker_1 values (00, 'outer_alpha');
call sp1_outer('begin transaction', 'begin transaction', 'rollback', 'commit');
insert into tracker_1 values (09, 'outer_charlie');
rollback;
세 단계 중 중간 단계 롤백하기: 이 예제는 "중간" 단계를 롤백해 가장 바깥과 가장 안쪽 트랜잭션을 커밋합니다. 중간 트랜잭션의 행(12, 21, 23)을 제외한 모든 행이 커밋됩니다.
begin transaction;
insert into tracker_1 values (00, 'outer_alpha');
call sp1_outer('begin transaction', 'begin transaction', 'commit', 'rollback');
insert into tracker_1 values (09, 'outer_charlie');
commit;
저장 프로시저의 트랜잭션에서 오류 처리 사용
다음 코드는 저장 프로시저의 트랜잭션에 대한 간단한 오류 처리를 보여줘요. 파라미터 값 'fail'이 전달되면 저장 프로시저는 존재하는 두 테이블과 존재하지 않는 한 테이블에서 삭제를 시도하고, 오류를 잡아 오류 메시지를 반환합니다. 'fail'이 전달되지 않으면 존재하는 두 테이블에서의 삭제를 시도해 성공해요.
begin transaction;
create table parent(id integer);
create table child (child_id integer, parent_ID integer);
-- ----------------------------------------------------- --
-- Wrap multiple related statements in a transaction,
-- and use try/catch to commit or roll back.
-- ----------------------------------------------------- --
create or replace procedure cleanup(FORCE_FAILURE varchar)
returns varchar not null
language javascript
as
$$
var result = "";
snowflake.execute( {sqlText: "begin transaction;"} );
try {
snowflake.execute( {sqlText: "delete from child where parent_id = 1;"} );
snowflake.execute( {sqlText: "delete from parent where id = 1;"} );
if (FORCE_FAILURE === "fail") {
// To see what happens if there is a failure/rollback,
snowflake.execute( {sqlText: "delete from no_such_table;"} );
}
snowflake.execute( {sqlText: "commit;"} );
result = "Succeeded";
}
catch (err) {
snowflake.execute( {sqlText: "rollback;"} );
return "Failed: " + err; // Return a success/error indicator.
}
return result;
$$
;
commit;
오류를 강제한 호출(예: call cleanup('fail');)은 Failed: SQL compilation error: Object 'NO_SUCH_TABLE' does not exist or not authorized.를 반환하고, 오류 없는 호출(예: call cleanup('do not fail');)은 Succeeded를 반환해요.