Optimistic locking là một kỹ thuật được sử dụng trong các hệ thống cơ sở dữ liệu để giải quyết vấn đề xung đột dữ liệu khi nhiều người cùng truy cập và sửa đổi dữ liệu trong cùng một thời điểm. Kỹ thuật này được sử dụng trong các hệ thống có tính đồng thời cao, đặc biệt là các hệ thống web.

Cơ chế optimistic locking hoạt động bằng cách kiểm tra trạng thái của dữ liệu trước và sau khi sửa đổi. Nếu trạng thái của dữ liệu trước khi sửa đổi khác với trạng thái của dữ liệu sau khi sửa đổi, có nghĩa là dữ liệu đã bị thay đổi bởi một người dùng khác và xảy ra xung đột dữ liệu. Từ đó hệ thống sẽ thông báo cho người dùng hiện tại biết về sự cố xảy ra và yêu cầu họ sửa đổi lại dữ liệu hoặc từ bỏ việc thực hiện sửa đổi.

Một ví dụ minh họa cho optimistic locking là khi hai người dùng cùng truy cập vào một tài nguyên trên một trang web và cả hai cùng cố gắng sửa đổi tài nguyên đó. Nếu chỉ sử dụng cơ chế locking đơn giản, một người dùng có thể ghi đè lên sửa đổi của người dùng kia, gây ra sự cố mất dữ liệu hoặc dữ liệu không chính xác. Sử dụng optimistic locking sẽ giúp tránh được xung đột dữ liệu này và đảm bảo tính nhất quán và chính xác của dữ liệu.

Tiếp tục minh hoạ cụ thể cho ví dụ trên, ta có 2 user A và user B, giả sử user A đã đọc thông tin của order số 1234 và đang cố ghi thông tin này vào database. Trong khi đó, user B cũng muốn cập nhật thông tin của order số 1234 và đang thực hiện việc này. Nếu không sử dụng optimistic locking, khi cả hai user A và B lưu thông tin vào database, thông tin của user B sẽ được ghi đè lên thông tin của user A và mọi thông tin mà user A thực hiện sẽ bị mất đi.

Tuy nhiên, khi sử dụng optimistic locking, khi user A thực hiện việc lưu thông tin của order số 1234, hệ thống sẽ kiểm tra lại phiên bản (version) của thông tin trước khi user A cập nhật. Nếu phiên bản không giống với phiên bản mà user A đã đọc trước đó, hệ thống sẽ thông báo lỗi và yêu cầu user A đọc lại thông tin trước khi tiếp tục cập nhật. Như vậy, thông tin của user B sẽ không bị ghi đè lên thông tin của user A và các thao tác của cả hai user đều được bảo toàn.

Schema::create('products', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->string('name');
    $table->text('description');
    $table->float('price');
    $table->integer('version')->default(0); // Thêm trường version
    $table->timestamps();
});

Ứng dụng trong Laravel, ta có thể sử dụng Eloquent để thực hiện optimistic locking. Khi tạo bảng trong database, ta cần thêm một trường kiểu integer có tên là "version" để lưu trữ phiên bản của thông tin. Sau đó, ta có thể thêm đoạn mã sau vào model của bảng đó:

use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    // Định nghĩa tên bảng
    protected $table = 'orders';

    // Kích hoạt tính năng optimistic locking
    protected $touches = ['order'];

    // Thực hiện cập nhật thông tin với optimistic locking
    public function updateOrder($data)
    {
        $this->fill($data);
        $this->save();
    }
}

Ở đây, ta đã kích hoạt tính năng optimistic locking bằng cách sử dụng thuộc tính "touches" và sau đó thực hiện việc cập nhật thông tin với hàm "updateOrder". Khi thực hiện việc cập nhật thông tin, Eloquent sẽ kiểm tra phiên bản trước khi cập nhật và tự động tăng phiên bản lên mỗi khi cập nhật thành công.

Nếu muốn xử lý lỗi khi phiên bản không khớp, ta có thể sử dụng phương thức "update" trong Eloquent. Ví dụ:

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\OptimisticLockingException;

class Order extends Model
{
    // Định nghĩa tên bảng
    protected $table = 'orders';

    // Kích hoạt tính năng optimistic locking
    protected $touches = ['order'];

    // Thực hiện cập nhật thông tin với optimistic locking
    public function updateOrder($data)
    {
        $this->fill($data);
        try {
            $this->update();
        } catch (OptimisticLockingException $e) {
            // Xử lý lỗi khi phiên bản không khớp
            return false;
        }
        return true;
    }
}

Dưới đây, ta đã sử dụng phương thức "update" của Eloquent để cập nhật thông tin với optimistic locking. Nếu phiên bản không khớp, Eloquent sẽ bắn ra ngoại lệ "OptimisticLockingException". Ta có thể xử lý lỗi này bằng cách trả về giá trị false trong hàm "updateOrder".

Ngoài ra, Laravel cũng cung cấp một số tính năng khác để hỗ trợ cho optimistic locking, ví dụ như sử dụng thêm trường "updated_at". Khi một bản ghi được cập nhật, trường "updated_at" cũng sẽ được cập nhật đồng thời. Khi user khác thực hiện cập nhật, nếu trường "updated_at" đã thay đổi, Eloquent sẽ phát hiện và quăng ra ngoại lệ "OptimisticLockingException".

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\OptimisticLockingException;

class Order extends Model
{
    // Định nghĩa tên bảng
    protected $table = 'orders';

    // Kích hoạt tính năng optimistic locking
    protected $touches = ['order'];

    // Thực hiện cập nhật thông tin với optimistic locking
    public function updateOrder($data)
    {
        $this->fill($data);
        $this->updated_at = now(); // Cập nhật trường updated_at
        try {
            $this->update();
        } catch (OptimisticLockingException $e) {
            // Xử lý lỗi khi phiên bản không khớp
            return false;
        }
        return true;
    }
}

Trường hợp dưới đây, ta đã sử dụng phương thức "now" của Laravel để lấy thời điểm hiện tại và cập nhật giá trị cho trường "updated_at". Khi thực hiện cập nhật thông tin, ta chỉ cần gọi hàm "update" như bình thường và Laravel sẽ kiểm tra tự động trường "updated_at" để đảm bảo tính nhất quán của dữ liệu.

Ngoài ra bạn có thể dễ dàng, override phương thức "save", và cho giá trị `version` tự động tăng bằng phương thức `increment()`. Lúc này, mỗi khi bạn lưu một bản ghi của model, giá trị của trường `version` sẽ được tăng lên 1. Bạn có thể sử dụng trường `version` này để kiểm tra xem liệu một bản ghi có bị thay đổi bởi người dùng khác trong khi bạn đang chỉnh sửa hay không.

class Product extends Model
{
    protected $fillable = ['name', 'description', 'price', 'version']; // Thêm trường version vào fillable

    public $timestamps = false; // Tắt timestamps

    public function save(array $options = [])
    {
        if (!$this->exists) {
            $this->setAttribute('version', 1);
        } else {
            $this->increment('version');
        }
        return parent::save($options);
    }
}

Với những tính năng hỗ trợ của Laravel, việc thực hiện optimistic locking trở nên đơn giản và dễ dàng hơn, giúp chúng ta xây dựng được các ứng dụng an toàn và bảo mật hơn.

Không chỉ như vậy, trong Laravel, chúng ta cũng có thể sử dụng một tính năng khác để tăng tính bảo mật cho optimistic locking, đó là "locking by column". Tính năng này cho phép chúng ta chỉ định riêng một trường trong bảng dữ liệu để sử dụng cho việc locking thay vì sử dụng trường "updated_at" như mặc định.

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\OptimisticLockingException;

class Order extends Model
{
    // Định nghĩa tên bảng
    protected $table = 'orders';

    // Định nghĩa trường để sử dụng cho locking
    protected $lockColumn = 'version';

    // Thực hiện cập nhật thông tin với optimistic locking
    public function updateOrder($data)
    {
        $this->fill($data);
        $this->{$this->lockColumn}++; // Tăng giá trị của trường locking
        try {
            $this->update();
        } catch (OptimisticLockingException $e) {
            // Xử lý lỗi khi phiên bản không khớp
            return false;
        }
        return true;
    }
}

Ở đây, chúng ta đã sử dụng thuộc tính "$lockColumn" để chỉ định trường được sử dụng cho locking. Khi thực hiện cập nhật thông tin, ta chỉ cần tăng giá trị của trường locking này lên 1 và gọi hàm "update" để cập nhật dữ liệu. Nếu phiên bản hiện tại của dữ liệu đã bị thay đổi, Eloquent sẽ phát hiện ra và quăng ra ngoại lệ "b".

Việc sử dụng "locking by column" cho phép chúng ta tùy chọn trường để sử dụng cho locking, giúp tăng tính linh hoạt và đáp ứng nhu cầu của từng ứng dụng cụ thể. Tuy nhiên, việc sử dụng trường locking khác với trường "updated_at" có thể làm cho việc xử lý phiên bản trở nên phức tạp hơn, đặc biệt khi thực hiện các thao tác quản lý dữ liệu như backup hoặc restore.

 

So sánh Pessimistic Locking và Optimistic Locking

Pessimistic Locking là một kỹ thuật sử dụng khóa để ngăn chặn nhiều người dùng truy cập đồng thời vào cùng một tài nguyên. Kỹ thuật này được sử dụng khi có nguy cơ cao xảy ra xung đột dữ liệu trong môi trường đa người dùng.

Khi sử dụng Pessimistic Locking, một khóa sẽ được sử dụng để khóa tài nguyên, ngăn chặn các người dùng khác truy cập vào tài nguyên đó. Chỉ có người dùng nắm giữ khóa đó mới có thể truy cập được vào tài nguyên. Khi người dùng hoàn thành việc sử dụng tài nguyên, khóa sẽ được giải phóng và tài nguyên sẵn sàng cho người dùng khác sử dụng.

Sự khác biệt giữa Optimistic LockingPessimistic Locking là cách mà chúng đối phó với xung đột dữ liệu. Optimistic Locking cho phép nhiều người dùng truy cập và chỉ khóa khi xảy ra xung đột. Trong khi đó, Pessimistic Locking khóa tài nguyên trước đó để ngăn chặn xung đột, dẫn đến một số người dùng phải đợi trước khi truy cập được tài nguyên.

Trong trường hợp Optimistic Locking, việc kiểm tra xung đột sẽ được thực hiện khi một người dùng cố gắng cập nhật tài nguyên. Điều này được thực hiện bằng cách so sánh phiên bản (version) của tài nguyên đang được sử dụng với phiên bản mới nhất của tài nguyên đó. Nếu phiên bản của người dùng khác được cập nhật trước khi người dùng hiện tại lưu thay đổi của mình, thì hệ thống sẽ trả về một lỗi và yêu cầu người dùng làm mới dữ liệu của mình và cập nhật lại trước khi lưu thay đổi.

Trên đây là một số thông tin về optimistic locking và cách thực hiện trong Laravel. Việc sử dụng optimistic locking sẽ giúp chúng ta tránh được những lỗi xảy ra khi nhiều user cùng thực hiện việc cập nhật thông tin trong cùng một thời điểm, đồng thời giúp bảo toàn dữ liệu của hệ thống.




Các thành viên đã like bài viết: