php - 확인 - mysql 동시성 제어




윌 래벨 데이터베이스 트랜잭션 잠금 테이블 (2)

나는 laravel5.5의 온라인 결제 응용 프로그램 데이터베이스 트랜잭션을 사용합니다. 각 지불 ( type , amount , create_at , gross_income )을 기록 할 company_account 테이블이 있습니다. 새 레코드가 생성되면 마지막 레코드의 gross_income 에 액세스해야합니다. 그래서 테이블을 잠글 때 동시에 많은 지불을 피하기 위해 테이블 ​​잠금을 읽고 쓰십시오.

나는 laravel의 문서를 참조했지만 트랜잭션이 테이블을 잠글 지 확실하지 않습니다. 트랜잭션이 테이블을 잠그면 잠금 유형 (읽기 잠금, 쓰기 잠금 또는 둘다)은 무엇입니까?

DB::transaction(function () {
    // create company_account record

    // create use_account record
}, 5);

암호:

DB::transaction(function ($model) use($model) {
    $model = Model::find($order->product_id);
    $user = $model->user;

    // **update** use_account record
    try {
        $user_account = User_account::find($user->id);
    } catch (Exception $e){
        $user_account = new User_account;
        $user_account->user_id  = $user->id;
        $user_account->earnings = 0;
        $user_account->balance  = 0;
    }
    $user_account->earnings += $order->fee * self::USER_COMMISION_RATIO;
    $user_account->balance += $order->fee * self::USER_COMMISION_RATIO;
    $user_account->save();

    // **create** company_account record
    $old_tiger_account = Tiger_account::latest('id')->first();

    $tiger_account = new Tiger_account;
    $tiger_account->type = 'model';
    $tiger_account->order_id = $order->id;
    $tiger_account->user_id = $user->id;
    $tiger_account->profit = $order->fee;
    $tiger_account->payment = 0;
    $tiger_account->gross_income = $old_tiger_account-> gross_income + $order->fee;
    $tiger_account->save();
}, 5);

참고 문헌 :
매개 변수를 Laravel DB :: transaction ()에 전달하는 방법


2 개의 테이블을 업데이트 중이므로 트랜잭션을 사용하여 변경 사항을 동기화해야합니다. 다음 코드를 살펴보십시오.

DB::transaction(function () {
    $model = Model::find($order->product_id);
    $user = $model->user();

    DB::insert("
        insert into user_account (user_id, earnings, balance) values (?, ?, ?)
        on duplicate key update
        earnings = earnings + values(earnings),
        balance = balance + values(balance)
    ", [$user->id, $order->fee * self::USER_COMMISION_RATIO, $order->fee * self::USER_COMMISION_RATIO]);

    DB::insert(sprintf("
        insert into tiger_account (`type`, order_id, user_id, profit, payment, gross_income)
            select '%s' as `type`, %d as order_id, %d as user_id, %d as profit, %d as payment, gross_income + %d as gross_income
            from tiger_account
            order by id desc
            limit 1
    ", "model", $order->id, $user->id, $order->fee, 0, $order->fee));

}, 5);

2 개의 원자 쿼리가 있습니다. 첫 번째는 user_account 테이블에 레코드를 tiger_account 하고 다른 하나는 tiger_account 레코드를 삽입합니다.

이 두 쿼리 사이에 끔찍한 일이 생기면 변경 사항이 적용되지 않도록 트랜잭션이 필요합니다. 끔찍한 일은 동시 요청이 아니라 PHP 응용 프로그램, 네트워크 파티션 또는 두 번째 쿼리가 실행되지 못하도록하는 다른 문제가 갑자기 사라지는 것입니다. 이 경우 첫 번째 쿼리의 변경 사항이 롤백되므로 데이터베이스가 일관된 상태로 유지됩니다.

두 쿼리 모두 원자 적이며 각 쿼리의 계산이 분리되어 수행되며 다른 쿼리는 현재이 테이블을 변경하지 않습니다. 2 명의 동시 요청이 동시에 동일한 사용자에 대해 2 회 지불을 처리 할 수 ​​있다고합니다. 첫 번째 레코드는 user_account 테이블에 레코드를 삽입하거나 업데이트하고 두 번째 쿼리는 레코드를 업데이트하며 둘 다 레코드를 tiger_account 추가하고 각 트랜잭션이 커밋 될 때 모든 변경 사항이 db에 영구적으로 설정됩니다.

내가 만든 몇 가지 가정 :

  • user_iduser_account 테이블의 기본 키입니다.
  • tiger_account 에는 최소 1 개의 레코드가 tiger_account . OP 코드에 $old_tiger_account 라는 $old_tiger_account 가 있는데, DB에 아무것도 없을 때 예상되는 동작이 명확하지 않기 때문입니다.
  • 모든 돈 필드는 부동 소수점이 아닌 정수입니다.
  • MySQL DB입니다. 이 접근법을 설명하기 위해 MySQL 구문을 사용합니다. 다른 SQL 플레이버는 구문이 약간 다를 수 있습니다.
  • 원시 쿼리의 모든 테이블 이름과 열 이름 명명 규칙을 분명하게 기억하지 마십시오.

경고의 말씀 . 이들은 원시 쿼리입니다. 앞으로는 리팩토링 모델에 특별한주의를 기울여야하며, 일부 애플리케이션 로직이 명령형 PHP에서 선언적 SQL로 바뀌면서 더 적은 통합 테스트를 작성해야합니다. 경쟁 조건이 없음을 보장하는 것은 공정한 가격이라고 생각하지만, 무료로 제공되지는 않습니다.


제 생각에, 각 레코드에 대한 온 - 더 - 플라이 (on-the-fly) 총비용을 따로 계산한다면, 테이블을 잠글 필요조차 없습니다. 테이블을 잠그면 웹 사이트가 직접 느려질 것입니다.

DB::transaction(function () use($order) {
    $model = Model::find($order->product_id);
    $user = $model->user;

    // **update** use_account record
    try {
        $user_account = User_account::find($user->id);
    } catch (Exception $e){
        $user_account = new User_account;
        $user_account->user_id  = $user->id;
        $user_account->earnings = 0;
        $user_account->balance  = 0;
    }
    $user_account->earnings += $order->fee * self::USER_COMMISION_RATIO;
    $user_account->balance += $order->fee * self::USER_COMMISION_RATIO;
    $user_account->save();

    // **create** company_account record
    $tiger_account = Tiger_account::create([
        'type' => 'model',
        'order_id' => $order->id,
        'user_id' => $user->id,
        'profit' => $order->fee,
        'payment' => 0,
    ]);

    $tiger_account->update([
        'gross_income' => Tiger_account::where('id', '<=', $tiger_account->id)->sum('fee'),
    ]);
});






table-locking