Transaction
트랜잭션은 데이터의 정합성을 보장하기 위한 기능이다.
즉, 논리적 작업 셋 자체가 100% 적용되거나(Commit) 또는 아무것도 적요되지 않아야 함(Rollback)을 보장해주는 것이다.
트랜잭션은 ACID (Atomicity 원자성, Consistency 일관성, Isolation 격리성, Durability 지속성)를 보장해야 한다.
- Atomicity 원자성: 트랜잭션 내에서 실행한 작업들은 마치 하나의 작업인 것처럼 모두 성공하거나 혹은 모두 실패해야 한다.
- Consistency 일관성: 모든 트랜잭션은 일관성있는 데이터베이스 상태를 유지해야 한다. 예를 들면 데이터베이스에서 정한 무결성 제약 조건을 항상 만족해야 한다. (데이터베이스의 정확성, 일관성을 보장하기 위해 저장, 삭제, 수정 등을 제약하기 위한 조건. ex. 개체무결성, 참조무결성, …)
- Isolation 격리성: 동시에 실행되는 트랜잭션들이 서로에게 영향을 미치지 않도록 격리해야 한다. 예를 들면 동시에 같은 데이터를 수정하지 못하도록 해야 한다. 격리성은 동시성과 관련된 성능 이슈로 인해 격리 수준을 선택할 수 있다.
트랜잭션은 원자성, 일관성, 지속성을 보장한는데 문제가 되는것은 격리성 이다.
트랜잭션산에 격리성을 완벽히 보장하려면 동시에 처리되는 트랜잭션을 거의 차례대로 실행을 해야 한다. 하지만 이렇게 처리하면 처리 성능이 매우 나빠지게 된다. 이러한 문제로 인해 ANSI 표준은 트랜잭션의 격리 수준을 4단계로 나누어 정의한다.
MySQL의 격리 수준
트랜잭션의 격리 수준(Isolation Level)이란 동시에 여러 트랜잭션이 처리될 때, 특정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터를 볼 수 있도록 허용할지 말지를 결정하는 것이다.
격리 수준은 4가지로 정의한다.
- Read Uncommitted
- Read Committed
- Repeatable Read
- Serializable
순서대로 Read Uncommitted의 격리 수준이 가장 낮고 Serializable의 격리 수준이 가장 높다.
아래 표는 트랜잭션 격리 수준에 따라 발생하는 문제점을 나타낸다.
-
Dirty Read: 어떤 트랜잭션에서 처리한 작업(COMMIT/ROLLBACK)이 완료되지 않았음에도 불구하고 다른 트랜잭션에서 볼 수 있게 되는 현상 더티 리드를 유발하는 READ UNCOMMITTED 격리 수준은 RDBMS 표준에서는 트랜잭션의 격리 수준으로 인정하지 않을 정도로 정합성에 문제가 많은 격리 수준이므로 MySQL을 사용한다면 최소 READ COMMITTED 이상의 격리 수준을 사용할 것을 권장
-
Non-repeatable Read: 하나의 트랜잭션 안에 여러 스냅샷이 사용되는 경우를 말한다. 한 트랜잭션 내에서 같은 쿼리를 두번 수행할 때, 그 사이에 다른 트랜잭션이 값을 수정 또는 삭제함으로써 두 쿼리가 상이하게 나타나는 비 일관성이 발생하는 것
-
Panthom Read: 한 트랙잭션 내 같은 쿼리를 두 번 수행 시, 없었던 레코드가 두 번째 쿼리에서 발생하는 현상
Creating and using Transactions in TypeORM
Creating and using transactions
Transactions are created usin DataSource
or EntityManager
.
Examples:
await myDataSource.transaction(async (transactionalEntityManager) => { // execute queries using transactionalEntityManager }
The most important restriction when working in a transaction is to ALWAYS use the provided instance of entity manager - transactionalEntityManager in this example.
Specifying Isolation Levels
await myDataSource.manager.transaction( "SERIALIZABLE", (transactionalEntityManager) => {}, )
The follwoing database drivers support the standard isolation levels (READ UNCOMMITTED
, READ COMMITTED
, REPEATABLE READ
, SERIALIZABLE
):
- MySQL
- Ppstgres
- SQL Server
Using QueryRunner to create and control state of single database connection
QueryRunner
provides a single database connection. Transactions are organized using query runners. Single transactions can only be established on a single query runner. You can manually create a query runner instance and use it to manually control transaction state.
Example:
// create a new query runner const queryRunner = dataSource.createQueryRunner() // establish real database connection using our new query runner await queryRunner.connect() // now we can execute any queries on a query runner, for example: await queryRunner.query("SELECT * FROM users") // we can also access entity manager that works with connection created by a query runner: const users = await queryRunner.manager.find(User) // lets now open a new transaction: await queryRunner.startTransaction() try { // execute some operations on this transaction: await queryRunner.manager.save(user1) await queryRunner.manager.save(user2) await queryRunner.manager.save(photos) // commit transaction now: await queryRunner.commitTransaction() } catch (err) { // since we have errors let's rollback changes we made await queryRunner.rollbackTransaction() } finally { // you need to release query runner which is manually created: await queryRunner.release() }
startTransaction()
: starts a new transaction inside the query runner instance.commitTransaction()
: commits all cahnges made using the query runner instance.rollbackTransaction()
: rolls all cahnges made using the query runner instance back.