SQLAlchemy를 사용한 PostgreSQL의 안전한 업데이트 작업
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
# setup
engine = create_engine(...)
session = Session(engine)
# read from the table Account
account = session.query(Account).get(1)
# modify the record, account is decimal
account.amount = account.amount + 100
session.commit()
왜 이것이 안전하지 않습니까? SELECT가 행 수준에서 잠금을 설정하지 않기 때문입니다. 행을 독점적으로 잠그는 업데이트가 발생할 때만 차단합니다(
FOR UPDATE/FOR NO KEY UPDATE
). 이 시나리오에서는 경쟁 조건이 발생할 수 있습니다.거래 1(T1)
거래 2(T2)
시작
금액 선택
시작
금액 += 100
금액 선택
금액 업데이트
금액 += 100
저지르다
금액 업데이트
커밋
SAME value as T1
T1은 T2가 읽기 전에 커밋하지 않으므로 T2는 T1과 동일한 값을 선택합니다. 결국 같은 일을 다시 저지릅니다.
따라서 기본 격리 모드에서 이러한 상황을 방지하는 방법은 다음과 같습니다. (읽기 커밋 모드)
해결 방법 1: 읽지 마십시오.
위의 스니펫은
read-modify-write
. 이를 방지하는 한 가지 방법은 열 값으로 직접 값을 읽고 변경하지 않는 것입니다. 주어진 읽기는 이 시나리오에서 꼭 필요한 것은 아닙니다.# same session setting as above
session.query(Account).filter_by(id=1)\
.update({"amount": Account.amount + 100})
session.commit()
해결 방법 2: 잠금 업데이트
복잡한 수정을 하고 싶고 먼저 읽기만 하면 되는 몇 가지 시나리오가 있습니다. 이 경우 데이터베이스에서 읽을 때 업데이트 잠금을 사용할 수 있습니다. SQLAlchemy에는 변경하려는 행을
with_for_update
잠금으로 잠그는 FOR UPDATE
메소드가 있습니다. FOR UPDATE
잠금이 자체 호환되지 않습니다. 다른 트랜잭션은 이 트랜잭션이 잠금을 해제할 때까지 기다려야 합니다.# same session setting as above
# locks the row that id = 1
account = session.query(Account).filter_by(id=1)\
.with_for_update().one()
account.amount = account.amount + 100
session.commit() # save and release the lock
솔루션 3: 버전 추적(낙관적 잠금)
동일한 행에서 업데이트를 실행하는 다른 모든 프로세스를 롤백하려는 경우. 테이블에 버전 열을 추가하여 이 행의 업데이트를 추적할 수 있습니다. 일부 행에 버전 v가 있다고 가정해 보겠습니다. 행을 업데이트하려는 경우 버전 v와 함께 행을 검색하고 버전을 v + 1로 업데이트합니다.
update account set version = v + 1, ... where version = v ...;
이것은 우리 자신을 구현하기에는 상당히 성가신 일입니다. 다행히도 대부분의 ORM 라이브러리는 버전 추적을 지원합니다. SQLAlchemy에서는 매퍼에서 버전 열 이름을 설정할 수 있으므로 업데이트할 때 버전을 관리하고 일부 프로세스가 동일한 행에서 작동하면 예외가 발생합니다.
class Account(Base):
__tablename__ = "account"
...
version = Column(Integer, nullable=False)
__mapper_args__ = {"version_id_col": version}
def version_tracking(change):
try:
account = session.query(Account).get(1)
account.amount = account.amount + change
print_account(account, change)
session.commit()
except StaleDataError:
print("someone has changed the account, plz retry.")
# some actions...
위에 제공된 모든 솔루션은 Postgres의 기본 격리 모델인 읽기 커밋 모드를 사용하고 있다고 가정합니다. 또한 대상 행을 선택할 때 집계를 사용하지 않는다고 가정합니다. 트랜잭션이 직렬로 작동하는 것처럼 작동하도록 더 엄격한 격리 모드를 사용하는 것과 같이 동일한 문제를 방지하는 몇 가지 방법이 더 있습니다. 그러나 이 기사에서는 이에 대해 다루지 않을 것입니다.
더 많은 정보를 위해서.
All Source Code
SQLAlchemy for update (kite)
SQLAlchemy Version Tracking
PSQL Transaction Isolation
PSQL Locking
Reference
이 문제에 관하여(SQLAlchemy를 사용한 PostgreSQL의 안전한 업데이트 작업), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/ivankwongtszfung/safe-update-operation-in-postgresql-using-sqlalchemy-3ela텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)