Hướng dẫn dùng rollback trong PHP

Trong thực tế, có rất nhiều trường hợp mà bạn cần phải thay đổi dữ liệu trong một hoặc nhiều bảng cơ sở dữ liệu. Trong một số tình huống dữ liệu có thể không nhất quán khi các thực thi lệnh riêng lẻ. Nếu câu lệnh đầu tiên được thực hiện một cách chính xác nhưng các câu lệnh tiếp theo thất bại thì dữ liệu có thể ở trạng thái không chính xác.

Có rất nhiều trường hợp mà bạn cần phải thay đổi dữ liệu trong một hoặc nhiều bảng cơ sở dữ liệu. Trong một số tình huống dữ liệu có thể không nhất quán khi các thực thi lệnh riêng lẻ. Nếu câu lệnh đầu tiên được thực hiện một cách chính xác nhưng các câu lệnh tiếp theo thất bại thì dữ liệu có thể ở trạng thái không chính xác.

Một ví dụ điển hình là chức năng chuyển số dư tài khoản của một hệ thống ngân hàng. A chuyển giao có thể yêu cầu 2 câu lệnh

    // Create Account
    $newAcct = Account::create([
        'accountname' => Input::get('accountname'),
    ]);

    // Create User
    $newUser = User::create([
        'username' => Input::get('username'),
        'account_id' => $newAcct->id,
    ]);
0 và
    // Create Account
    $newAcct = Account::create([
        'accountname' => Input::get('accountname'),
    ]);

    // Create User
    $newUser = User::create([
        'username' => Input::get('username'),
        'account_id' => $newAcct->id,
    ]);
1. Đầu tiên phải tăng số dư của tài khoản đích sau đó làm giảm số dư tài khoản thứ hai. Cuối cùng sẽ chèn 1 hàng mới vào trong bảng chuyển giao để cung cấp nhật ký của việc giao dịch này.

Nếu việc tăng số dư thực hiện chính xác nhưng việc thứ 2 và thứ 3 thất bại thì số dư của 1 tài khoản sẽ được tăng lên nhưng tài khoản nguồn sẽ không thay đổi và sẽ không có nhật ký để ghi lại vấn đề này.

Trong tình huống như vậy, chúng ta cần một cách để đảm bảo rằng tất cả các lệnh liên quan thực hiện chính xác trước khi thay đổi cơ sở dữ liệu. Chúng ta cũng cần để có thể đảo ngược tất cả các lệnh thực thi nếu có một thay đổi thất bại. Điều này đạt được bằng cách sử dụng các giao dịch (transaction).

Giao dịch là một nhóm các câu lệnh SQL có thứ tự các hoạt động thao tác trên cơ sở dữ liệu nhưng được xem như một đơn vị thao tác duy nhất và chúng chỉ có thể cùng nhau thành công hoặc cùng nhau thất bại.

Một giao dịch không được xem là thành công khi chỉ cần một thao tác đơn giản nào đó trong nó không hoàn thành. Trường hợp này được xem là thất bại.

Các giao dịch được bắt đầu trước khi dữ liệu thay đổi và cam kết một khi tất cả các bản cập nhật liên quan được thực hiện thành công. Cho đến khi giao dịch được cam kết, những thay đổi không được lưu trữ trong cơ sở dữ liệu. Tại bất kỳ thời điểm nào trong giao dịch, bạn có thể

    // Create Account
    $newAcct = Account::create([
        'accountname' => Input::get('accountname'),
    ]);

    // Create User
    $newUser = User::create([
        'username' => Input::get('username'),
        'account_id' => $newAcct->id,
    ]);
2 (thao tác lùi cơ sở dữ liệu về một trạng thái cũ.) để kết thúc giao dịch.

Trong các ứng dụng truy cập cơ sở dữ liệu, nhiều người dùng có thể đồng thời xem và sửa dữ liệu. Các thao tác đồng thời có thể dẫn đến dữ liệu không nhất quán và chính xác. Bên cạnh đảm bảo hoạt động đúng của một tập hợp các câu lệnh SQL, một giao dịch cần đảm bảo người dùng khác không thể sửa chữa các dòng liên quan đến giao dịch.

2. Thuộc tính của giao dịch

Một giao dịch có 4 thuộc tính then chốt (không thể thiếu) được viết tắt là ACID

Atomicity (Tính nguyên tố)

Đảm bảo tất cả các hành động trong một đơn vị giao dịch là thành công hoàn toàn. Ngược lại giao dịch sẽ bị dừng ngay tại thời điểm bị lỗi.

Tất nhiên các hoạt động trước trong giao dịch đó sẽ quay lại đúng trạng thái cũ của nó. Chẳng hạn việc chuyển tiền có thể thành công hay trục trặc vì nhiều lý do nhưng tính nguyên tố bảo đảm rằng một tài khoản sẽ không bị trừ tiền nếu như tài khoản kia chưa được cộng số tiền tương ứng.

Consistency(Tính nhất quán)

Đảm bảo cơ sở dữ liệu được thay đổi đúng trạng thái sau khi giao dịch thành công và không xảy ra lỗi.

Isolation(Tính cô lập) Đảm bảo giao dịch này hoạt động độc lập so với giao dịch khác.

Durability(Tính bền vững)

Đảm bảo kết quả hoặc tác động của giao dịch vẫn luôn tồn tại, kể cả khi hệ thống xảy ra lỗi.

3. Khi nào thì sử dụng Giao dịch

Giao dịch được dùng khi xử lý các vấn đề liên quan đến truy xuất dữ liệu đồng thời để đảm bảo tính toàn vẹn dữ liệu khi xảy ra sự thay đổi (các hành động như

    // Create Account
    $newAcct = Account::create([
        'accountname' => Input::get('accountname'),
    ]);

    // Create User
    $newUser = User::create([
        'username' => Input::get('username'),
        'account_id' => $newAcct->id,
    ]);
3…).

Khi một giao dịch bao gồm nhiều lệnh cập nhật, nó đảm bảo tất cả các cập nhật đều được thực hiện thành công, hoặc trong trường hợp một lệnh gặp sự cố toàn bộ transaction bị hủy bỏ. Khi đó dữ liệu trở về trạng thái như trước khi xảy ra transaction.

Nói cách khác giao dịch ngăn chặn tình huống dữ liệu được cập nhật nửa chừng, trong đó một phần được cập nhật còn một phần bị bỏ qua.

4. Mức độ cô lập (isolation level) giữa các giao dịch

Các giao dịch có thể đồng thời truy cập đến dữ liệu chia sẻ. . Tùy thuộc vào mức độ cô lập giữa các giao dịch có thể xuất hiện các hiện tượng.

Dirty reads Xảy ra khi một giao dịch được cho phép đọc dữ liệu từ một dòng đang bị sửa chữa bởi một giao dịch khác chưa committed.

Nonrepeatable reads

Có thể xảy ra với các trường hợp sau.

1 Giao dịch A đọc 1 bảng

2 Sau đó giao dịch B cập nhật bảng đó

3 Sau đó giao dịch B cập nhật bảng đó

4 Giao dịch A đọc lại bảng sau khi giao dịch B cập nhật

5 Giao dịch A thấy dữ liệu lấy ra không giống ban đầu

Phantom reads

Xảy ra khi trong một giao dịch , hai câu truy vấn giống nhau khi thi hành trả lại tập hợp các dòng khác nhau. Điều này xảy ra khi tại thời điểm giữa hai câu truy vấn đó, giao dịch thứ hai thêm vào các dòng dữ liệu mới thỏa mãn mệnh đề

    // Create Account
    $newAcct = Account::create([
        'accountname' => Input::get('accountname'),
    ]);

    // Create User
    $newUser = User::create([
        'username' => Input::get('username'),
        'account_id' => $newAcct->id,
    ]);
4 của câu truy vấn.

5. Thao tác giao dịch cơ bản trong MySQL

1 Tạo một giao dịch.

  START TRANSACTION;
  BEGIN;

Có thể thực hiện nhiều câu lệnh

    // Create Account
    $newAcct = Account::create([
        'accountname' => Input::get('accountname'),
    ]);

    // Create User
    $newUser = User::create([
        'username' => Input::get('username'),
        'account_id' => $newAcct->id,
    ]);
5 sau lệnh
    // Create Account
    $newAcct = Account::create([
        'accountname' => Input::get('accountname'),
    ]);

    // Create User
    $newUser = User::create([
        'username' => Input::get('username'),
        'account_id' => $newAcct->id,
    ]);
6;

2 Kết thúc giao dịch

  COMMIT, ROLLBACK
  1. Lệnh COMMIT lưu các thay đổi vào cơ sở dữ liệu.
  2. Lệnh ROLLBACK hủy các thay đổi thực hiện trong giao dịch và cơ sở dữ liệu được phục hồi về trạng thái trước giao dịch.

    // Create Account
    $newAcct = Account::create([
        'accountname' => Input::get('accountname'),
    ]);

    // Create User
    $newUser = User::create([
        'username' => Input::get('username'),
        'account_id' => $newAcct->id,
    ]);
7 định nghĩa một điểm đánh dấu trong một giao dịch.

    // Create Account
    $newAcct = Account::create([
        'accountname' => Input::get('accountname'),
    ]);

    // Create User
    $newUser = User::create([
        'username' => Input::get('username'),
        'account_id' => $newAcct->id,
    ]);
8 TO
    // Create Account
    $newAcct = Account::create([
        'accountname' => Input::get('accountname'),
    ]);

    // Create User
    $newUser = User::create([
        'username' => Input::get('username'),
        'account_id' => $newAcct->id,
    ]);
7 cho phép phục hồi lại trước điểm đánh dấu

6. Ứng dụng transaction trong Laravel

Tôi có một ví dụ như sau.

    // Create Account
    $newAcct = Account::create([
        'accountname' => Input::get('accountname'),
    ]);

    // Create User
    $newUser = User::create([
        'username' => Input::get('username'),
        'account_id' => $newAcct->id,
    ]);

Ta đi tạo một Acount và một User. Vào tạo user phụ thuộc vào việc tạo Acount thành công hay không ? Vì acount_id là ID của một user.

Nhìn vào ví dụ này, chúng ta có thể thấy vấn đó có thể xảy ra các trường hợp như sau.

a. Account không được tạo.

Nếu tài khoản không được tạo ra, không có ID để tạo một User. Trong trường hợp này, tài khoản và người dùng sẽ không được tạo ra.

b. User không được tạo

Tuy nhiên, nếu tài khoản (Acount) đã được tạo ra, thì User cũng không được tạo trong trường hợp này.

Transactional của chúng ta

    // Start transaction
    beginTransaction();

    // Run Queries
    $acct = createAccount();
    $user = createUser();

    // If there's an error
    //    or queries don't do their job,
    //    rollback!
    if( !$acct || !$user )
    {
        rollbackTransaction();
    } else {
        // Else commit the queries
        commitTransaction();
    }

Transactions cơ bản trong Laravel. Đầu tiên, để chạy một transaction với Laravel. Ta sử dụng lệnh như bên dưới

    DB::transaction() method:

method:Là phương thức, bên trong đó là các thao tác, làm việc với database.

DB::transaction(function()
{
    $newAcct = Account::create([
        'accountname' => Input::get('accountname')
    ]);

    $newUser = User::create([
        'username' => Input::get('username'),
        'account_id' => $newAcct->id,
    ]);
});

Làm thế nào để biết được code được rollback hoặc commit trong transaction?

Bạn có thể theo dõi đoạn code này

    public function transaction(Closure $callback)
    {
        $this->beginTransaction();

        // We'll simply execute the given callback within a try / catch block
       // and if we catch any exception we can rollback the transaction
       // so that none of the changes are persisted to the database.
        try {
           $result = $callback($this);
           $this->commit();
        }
          // If we catch an exception, we will roll back so nothing gets messed
         // up in the database. Then we'll re-throw the exception so it can
         // be handled how the developer sees fit for their applications.
         catch (\Exception $e) {
           $this->rollBack();
           throw $e;
         }
        return $result;
    }

Rất đơn giản, nếu một ngoại lệ của bất cứ loại nào được ném trong việc đóng cửa, sau đó giao dịch được cuộn lại. Điều này có nghĩa rằng nếu có một lỗi SQL, sau đó giao dịch được rollback. mạnh mẽ hơn, tuy nhiên, điều này có nghĩa là chúng ta có thể ném ngoại lệ của riêng của chúng tôi để rollback một giao dịch.

Nó giống như thế này:

DB::transaction(function()
{
    $newAcct = Account::create([
        'accountname' => Input::get('accountname')
    ]);

    $newUser = User::create([
        'username' => Input::get('username'),
        'account_id' => $newAcct->id,
    ]);

    if( !$newUser )
    {
        throw new \Exception('User not created for account');
    }
});

Advanced Transactions in Laravel

I recently found myself needing more control over handling transaction. My create() methods also handled validation by throwing a custom ValidationException if there was a validation issue. If this exception was caught, the server responded by redirecting the user with the error messages.

Hiện tại, Tôi đang cần control hay handle một transaction, Phương thức

    // Start transaction
    beginTransaction();

    // Run Queries
    $acct = createAccount();
    $user = createUser();

    // If there's an error
    //    or queries don't do their job,
    //    rollback!
    if( !$acct || !$user )
    {
        rollbackTransaction();
    } else {
        // Else commit the queries
        commitTransaction();
    }
0 của tôi cũng được handle bởi
    // Start transaction
    beginTransaction();

    // Run Queries
    $acct = createAccount();
    $user = createUser();

    // If there's an error
    //    or queries don't do their job,
    //    rollback!
    if( !$acct || !$user )
    {
        rollbackTransaction();
    } else {
        // Else commit the queries
        commitTransaction();
    }
1. Một custom của
    // Start transaction
    beginTransaction();

    // Run Queries
    $acct = createAccount();
    $user = createUser();

    // If there's an error
    //    or queries don't do their job,
    //    rollback!
    if( !$acct || !$user )
    {
        rollbackTransaction();
    } else {
        // Else commit the queries
        commitTransaction();
    }
2, nếu những validation này có vấn đề. Server sẽ trả về thông tin error trong đó có message lỗi.

    try {
        // Validate, then create if valid
        $newAcct = Account::create( ['accountname' => Input::get('accountname')] );
    } catch(ValidationException $e)
    {
        // Back to form with errors
        return Redirect::to('/form')
            ->withErrors( $e->getErrors() )
            ->withInput();
    }

    try {
        // Validate, then create if valid
        $newUser = User::create([
            'username' => Input::get('username'),
            'account_id' => $newAcct->id
        ]);
    } catch(ValidationException $e)
    {
        // Back to form with errors
        return Redirect::to('/form')
            ->withErrors( $e->getErrors() )
            ->withInput();
    }

Làm thế nào để có thể put một transaction if ValidationExceptions luôn luôn bắt. Các bạn có thể xem ở ví dụ này.

    // Start transaction!
    DB::beginTransaction();

    try {
        // Validate, then create if valid
        $newAcct = Account::create( ['accountname' => Input::get('accountname')] );
    } catch(ValidationException $e)
    {
        // Rollback and then redirect
        // back to form with errors
        DB::rollback();
        return Redirect::to('/form')
            ->withErrors( $e->getErrors() )
            ->withInput();
    } catch(\Exception $e)
    {
        DB::rollback();
        throw $e;
    }

    try {
        // Validate, then create if valid
        $newUser = User::create([
            'username' => Input::get('username'),
            'account_id' => $newAcct->id
        ]);
    } catch(ValidationException $e)
    {
        // Rollback and then redirect
        // back to form with errors
        DB::rollback();
        return Redirect::to('/form')
            ->withErrors( $e->getErrors() )
            ->withInput();
    } catch(\Exception $e)
    {
        DB::rollback();
        throw $e;
    }

    // If we reach here, then
    // data is valid and working.
    // Commit the queries!
    DB::commit();

Kết luận

Ở bài viết này, tôi đã giới giới thiệu cho các bạn về transaction trong database. Vì sao phải sử dụng transaction. Nhưng ứng dụng nào nên sử dụng transaction (Ví dụ như hệ thống ngân hàng).

Qua đó, tô đã hướng dẫn các bạn cách sử dụng transaction trong Laravel. Một framework đình đám nhất của PHP hiện nay.