Quay lại

Laravel Deadlock, lockForUpdate, sharedLock trong SQL transaction Chuyên mục PHP và Laravel    2024-02-23    58 Lượt xem    29 Lượt thích    comment-3 Created with Sketch Beta. 0 Bình luận

Laravel Deadlock, lockForUpdate, sharedLock trong SQL transaction

Deadlock trong SQL transaction là gì?

Deadlock trong SQL transaction là một tình huống xung đột xảy ra khi hai hoặc nhiều transaction đang chờ đợi tài nguyên mà transaction khác đang sở hữu, và không ai có thể tiếp tục thực hiện mà không làm cho một trong số họ bị chặn mãi mãi. Trong một deadlock, không có transaction nào có thể hoàn thành và tiếp tục, do đó hệ thống phải can thiệp để giải quyết xung đột.

Khi nào xảy ra deadlock trong laravel?

Trong Laravel, deadlock có thể xảy ra khi hai hoặc nhiều truy vấn SELECT hoặc cập nhật (UPDATE) được thực hiện đồng thời trên cùng một tập hợp dữ liệu và mỗi truy vấn yêu cầu tài nguyên mà transaction khác đang sở hữu. Khi điều này xảy ra, Laravel sẽ không thể giải quyết xung đột một cách tự động mà sẽ phát sinh một ngoại lệ hoặc lỗi deadlock.

Giải quyết deadlock trong Laravel như thế nào?

Ứng dụng của Laravel trong việc xử lý deadlock là cung cấp các cơ chế để xử lý xung đột, bao gồm cơ chế tự động retry (thử lại) hoặc sử dụng các kỹ thuật như phân đoạn truy vấn hoặc lock để tránh deadlock. Bằng cách sử dụng các kỹ thuật này một cách chính xác, bạn có thể giảm thiểu nguy cơ xảy ra deadlock trong ứng dụng Laravel của mình và đảm bảo tính nhất quán của dữ liệu.

Ví dụ, bạn có thể sử dụng một cấu trúc try-catch để bắt các ngoại lệ deadlock trong Laravel và sau đó thực hiện lại transaction.

Cách 1: Chúng ta sử dụng một cấu trúc try-catch để bắt ngoại lệ QueryException, và sau đó kiểm tra xem liệu ngoại lệ có phải là deadlock không bằng cách so sánh mã lỗi (error code). Nếu ngoại lệ là deadlock, chúng ta có thể thực hiện lại transaction (retry transaction) bằng cách gọi hàm rollBack() và sau đó thực hiện lại transaction.

use Illuminate\Support\Facades\DB;
use Illuminate\Database\QueryException;

try {
    DB::beginTransaction();

    // Thực hiện các truy vấn SQL ở đây

    DB::commit();
} catch (QueryException $e) {
    if ($e->getCode() === '40001') {
        // Xử lý ngoại lệ deadlock bằng cách thực hiện lại giao dịch
        DB::rollBack();
        // Thực hiện lại giao dịch
    } else {
        // Xử lý các loại ngoại lệ khác
        throw $e;
    }
}

Cách 2: Sử dụng Closure (Closure-Based Approach) hàm DB::transaction chúng ta có thể tự định nghĩathực hiện retry Transaction 5 lần.

DB::transaction(function () use ($product, $user, $price) {
    $product->decrement('stock');
    $user->decrement('balance', $price);
}, 5);

Cách 3: Cũng tương tự như cách 1 nhưng nếu ae muốn log lại mỗi lần bị DeadLock, hay log lại số lần phải reTry thì có thể custom lại như sau, đoạn code trên sẽ tương tự như:

for	($currentAttempt = 0; $currentAttempt < 5; $currentAttempt++) {
    DB::beginTransaction();

    //Handle Exceptions
    try {
        // Perform multiple database operations, including updates, inserts, and deletions.
        $product->update(['stock' => $product->stock - 1]);

        $user->update(['balance' => $user->balance - $price]);

        // If everything goes right, we save the changes to database
        DB::commit();

    } catch(Throwble $e) {
        // In case of an exception we rollback all the queries
        DB::rollback();
        // Nếu exception là DeadLock thì mới retry!!
    	if ($this->causedByDeadlock($e) && $currentAttempt < $attempts) {
   			// ToDo SomeThing (Log,...)
    		continue;
    	}
        throw $e;
    }
}

Để tránh tình trạng kéo performance đi xuống thì logic business cố gắng để hết ngoài Transaction, trong Transaction chỉ thực thi truy vấn. Nếu để hết vào trong Transaction, hệ thống sẽ chạy lại hàm tính toán mất thêm nhiều thời gian. Trong khi mục đích của mình chỉ là thực thi câu truy vấn.

lockForUpdate trong laravel

Phương thức lockForUpdate() trong Laravel được sử dụng khi bạn muốn đảm bảo tính nhất quán của dữ liệu trong quá trình thực hiện các truy vấn SELECT và sau đó cập nhật hoặc xóa dữ liệu dựa trên kết quả của các truy vấn đó.

Bạn có thể sử dụng lockForUpdate() trong các tình huống sau:

  1. Cập nhật dữ liệu dựa trên kết quả của một truy vấn SELECT: Khi bạn thực hiện một truy vấn SELECT để đọc dữ liệu từ cơ sở dữ liệu và sau đó dự định cập nhật các hàng dữ liệu đó dựa trên kết quả của truy vấn đó, bạn có thể sử dụng lockForUpdate() để đảm bảo rằng các hàng dữ liệu được lựa chọn không bị thay đổi bởi các phiên làm việc khác trong quá trình cập nhật.

  2. Tránh xung đột và mất cập nhật: Khi nhiều người dùng hoặc quy trình đồng thời thực hiện các hoạt động trên cùng một tập hợp dữ liệu, việc sử dụng lockForUpdate() có thể giúp tránh được xung đột và mất cập nhật bằng cách đảm bảo rằng chỉ một phiên làm việc có thể cập nhật dữ liệu tại một thời điểm.

  3. Phục hồi dữ liệu không thể thay đổi: Khi bạn muốn đảm bảo rằng dữ liệu bạn đọc từ cơ sở dữ liệu không thay đổi trong suốt quá trình thực hiện các thao tác trên nó, bạn có thể sử dụng lockForUpdate() để đảm bảo rằng dữ liệu được khóa cho đến khi transaction hoặc truy vấn kết thúc.

Tóm lại, lockForUpdate() được sử dụng khi bạn cần đảm bảo tính nhất quán của dữ liệu trong các tình huống cần phải đọc và sau đó cập nhật hoặc xóa dữ liệu một cách an toàn mà không bị ảnh hưởng bởi các tác động từ các phiên làm việc khác.

Ứng dụng lockForUpdate trong laravel

Dưới đây là một số ví dụ về việc sử dụng lockForUpdate() trong Laravel:

Ví dụ 1: Lấy dữ liệu và áp dụng lock cho các hàng được chọn:

use App\Models\User;

$users = User::where('status', 'active')->lockForUpdate()->get();

Trong ví dụ này, chúng ta lấy tất cả các người dùng có trạng thái là "active" và áp dụng khóa cập nhật cho các hàng dữ liệu tương ứng.

Ví dụ 2: Lấy dữ liệu và áp dụng lock cho một hàng cụ thể:

use App\Models\Product;

$product = Product::where('id', $productId)->lockForUpdate()->first();

Trong ví dụ này, chúng ta lấy thông tin của một sản phẩm dựa trên ID cụ thể và áp dụng khóa cập nhật cho hàng dữ liệu đó.

Ví dụ 3: Thực hiện cập nhật sau khi lấy dữ liệu và áp dụng lock:

use App\Models\Order;

$order = Order::where('id', $orderId)->lockForUpdate()->first();

if ($order) {
    $order->status = 'completed';
    $order->save();
}

Trong ví dụ này, chúng ta lấy thông tin của một đơn hàng dựa trên ID, áp dụng khóa cập nhật cho hàng dữ liệu đó, và sau đó thực hiện cập nhật trạng thái của đơn hàng. Điều này đảm bảo rằng không có các thay đổi nào khác được thực hiện trên đơn hàng trong quá trình cập nhật này.

sharedLock trong laravel

Trong Laravel, sharedLock() là một phương thức được sử dụng để áp dụng khóa chia sẻ (shared lock) cho các hàng dữ liệu được lựa chọn trong một truy vấn SELECT. Khóa chia sẻ cho phép nhiều phiên làm việc đọc dữ liệu cùng một lúc, nhưng sẽ ngăn chặn việc cập nhật hoặc xóa dữ liệu cho đến khi khóa chia sẻ bị giải phóng.

Khi nào dùng sharedLock

  • Khi bạn muốn đảm bảo tính nhất quán của dữ liệu trong quá trình đọc, nhưng không muốn ngăn chặn các phiên làm việc khác đọc dữ liệu cùng một lúc.
  • Khi bạn cần thực hiện một số thao tác đọc phức tạp trên dữ liệu mà không muốn dữ liệu bị thay đổi bởi các thao tác cập nhật hoặc xóa song song.Ứng dụng sharedLock trong laravel.

Ứng dụng sharedLock trong laravel

use App\Models\Product;

$product = Product::where('id', $productId)->sharedLock()->first();

// Thực hiện các thao tác đọc trên $product

Trong ví dụ này, chúng ta lấy thông tin của một sản phẩm dựa trên ID cụ thể và áp dụng khóa chia sẻ cho hàng dữ liệu đó. Sau đó, chúng ta thực hiện các thao tác đọc trên dữ liệu sản phẩm, nhưng dữ liệu này vẫn bị khóa chia sẻ và không thể cập nhật hoặc xóa bởi các phiên làm việc khác. Điều này đảm bảo tính nhất quán của dữ liệu trong quá trình thực hiện các thao tác đọc.

So sánh giống và khác nhau của Deadlock, lockForUpdate, sharedLock trong Laravel

Dưới đây là sự so sánh về ý nghĩa, ứng dụng và sự khác nhau của Deadlock, lockForUpdate và sharedLock trong Laravel:

Giống nhau:

  1. Liên quan đến xử lý đồng thời dữ liệu: Cả Deadlock, lockForUpdate và sharedLock đều liên quan đến việc xử lý dữ liệu trong các tình huống đồng thời, khi có nhiều phiên làm việc cố gắng truy cập và/hoặc cập nhật cùng một tập hợp dữ liệu.

  2. Liên quan đến tính nhất quán của dữ liệu: Tất cả đều liên quan đến việc đảm bảo tính nhất quán của dữ liệu trong quá trình thực hiện các truy vấn và transaction, để tránh các vấn đề như mất cập nhật hoặc dữ liệu không nhất quán.

Khác nhau:

  1. Loại xung đột: Deadlock là một hiện tượng xung đột khi hai hoặc nhiều transaction đang chờ đợi tài nguyên mà transaction khác đang sở hữu, trong khi lockForUpdate và sharedLock là phương thức sử dụng để tránh xung đột bằng cách áp dụng các loại khóa khác nhau trên dữ liệu.

  2. Phương thức hoạt động: lockForUpdate và sharedLock là phương thức được sử dụng trực tiếp trong Laravel để áp dụng khóa cập nhật và khóa chia sẻ trên các hàng dữ liệu, trong khi Deadlock là một hiện tượng xung đột tự nhiên xảy ra trong hệ thống khi có sự cố về xung đột tài nguyên.

  3. Mục đích sử dụng: lockForUpdate được sử dụng khi cần đảm bảo rằng các hàng dữ liệu không bị thay đổi bởi các phiên làm việc khác trong quá trình cập nhật, trong khi sharedLock được sử dụng khi cần đảm bảo tính nhất quán của dữ liệu trong quá trình đọc mà không muốn ngăn chặn các phiên làm việc khác đọc dữ liệu cùng một lúc. Deadlock không phải là một phương thức cụ thể mà là một tình huống xung đột xảy ra trong quá trình thực hiện các transaction SQL.

Bình luận (0)

Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough

Bài viết liên quan

Learning English Everyday