Skip to content
isdnetworks
Go back

트랜잭션이 이미 끝나 있었다

주문 처리에서 중간에 실패하면 ROLLBACK 으로 전부 물러나게 만들었다. 실패하는 경우를 시험해 보니 일부만 물러났다.

Table of contents

Open Table of contents

안쪽에서도 트랜잭션을 쓰고 있었다

바깥 코드는 세 가지를 하나로 묶고 있었다.

$this->db->trans_start();

$orderNo = $this->order_model->create($data);
$this->stock_model->decrease($items);
$this->point_model->use($userId, $point);

$this->db->trans_complete();

재고를 줄이는 모델 안을 열어 보니 거기서도 trans_begintrans_commit 을 직접 부르고 있었다.

원인은 안쪽이 끝내서가 아니라 안쪽이 여는 순간이었다. MySQL 은 트랜잭션을 겹칠 수 없고 열려 있는 상태에서 START TRANSACTION 을 다시 내면 그 시점에 앞의 것이 암묵적으로 커밋된다. 매뉴얼도 트랜잭션은 중첩될 수 없으며 그것이 START TRANSACTION 의 암묵 커밋에서 따라 나오는 결과라고 적어 뒀다.

그러니 재고 모델에 들어가는 순간 주문 생성이 확정된다. 그 뒤에 실패해도 앞의 것은 안 물러난다.

여는 자리를 한 곳으로 정했다

트랜잭션을 여는 계층을 컨트롤러로 정하고 모델에서는 trans_begin 을 쓰지 않게 했다. 모델 하나만 쓰는 경우에도 컨트롤러가 연다. 규칙이 하나면 어느 자리에서 열려 있는지 따질 일이 없다.

public function decrease($items) {
    foreach ($items as $i) { ... }   // 트랜잭션 없음
}

trans_starttrans_complete 를 쓰면 프레임워크가 깊이를 세서 가장 바깥에서만 실제로 확정한다. DB 쪽에서 중첩이 되는 것이 아니라 드라이버가 안쪽 호출을 세기만 하고 넘기는 것이다. 그것도 되지만 어느 깊이인지를 계속 추적해야 해서 여는 자리를 정하는 쪽이 단순했다.

되돌아가는지 확인했다

고친 뒤 일부러 실패시켜 봤다. 포인트 차감 직후에 예외를 던지고 세 테이블을 확인했다.

SELECT COUNT(*) FROM orders WHERE order_no = ?;     -- 0
SELECT qty FROM stock WHERE product_no = ?;          -- 원래 값
SELECT point FROM member WHERE member_no = ?;        -- 원래 값

셋 다 원래대로였다. ROLLBACK 은 일부러 실패시켜 보기 전에는 확인이 안 된다. 정상 경로만 돌리면 그 코드는 한 번도 안 돈다.

트랜잭션 밖의 것

물러나지 않는 것도 있었다. move_uploaded_filecurl_execmail()memcached 갱신 넷은 데이터베이스 밖이라 ROLLBACK 이 돌아도 그대로 남는다.

순서를 바꿔서 확정된 뒤에 하게 했다.

$this->db->trans_start();
// 데이터베이스 작업만
$this->db->trans_complete();

if ($this->db->trans_status() === false) {
    return $this->fail();
}

$this->mailer->sendOrderConfirm($orderNo);
$this->cache->delete("product:{$productNo}");

이러면 물릴 일이 없다. 밖의 것이 실패해도 주문은 남고 그건 따로 재시도한다.

오래 걸리는 것을 트랜잭션 안에 두지 않는 것도 같이 정했다. curl_exec 을 안에서 부르면 CURLOPT_TIMEOUT 만큼 기다리는 동안 InnoDB 의 행 잠금이 유지되고 다른 요청이 밀린다. 결제 승인을 트랜잭션 밖으로 빼고 그 결과를 받은 뒤에 짧게 열어 저장했다.

정리


Share this post on:

Previous Post
설정 하나가 없어서 전체가 멈췄다
Next Post
주문번호를 맞힐 수 있었다