1. Locking là gì?

Locking (cơ chế khóa) là cơ chế database sử dụng để kiểm soát việc nhiều transaction cùng truy cập hoặc thay đổi dữ liệu, nhằm tránh những xung đột có thể làm sai lệch kết quả.

Trong một ứng dụng có nhiều user, nhiều request có thể đồng thời thao tác trên cùng một bản ghi. Nếu database không kiểm soát những thao tác xung đột, một transaction có thể ghi đè dữ liệu của transaction khác hoặc khiến nghiệp vụ không còn đúng.

Ví dụ, sản phẩm A chỉ còn một đơn vị trong kho. Hai request cùng đặt mua sản phẩm này gần như đồng thời.

Cả hai request đều có thể đọc được:

stock = 1

Nếu cả hai cùng quyết định rằng sản phẩm vẫn còn hàng và tiếp tục xử lý đơn hàng, hệ thống có nguy cơ bán vượt số lượng tồn kho.

Locking giúp database điều phối những thao tác cạnh tranh trên dữ liệu đó. Tùy loại khóa và isolation level, một transaction có thể phải chờ transaction khác hoàn thành trước khi được tiếp tục.

2. Vì sao cần Locking?

Locking giúp giải quyết những vấn đề phát sinh khi nhiều transaction đồng thời thao tác trên dữ liệu có liên quan.

Lost Update – Ghi đè mất cập nhật

Giả sử số dư tài khoản là 1.000.000 đồng.

Transaction A đọc số dư và tính rằng cần trừ 200.000 đồng. Cùng lúc, transaction B đọc cùng số dư và tính rằng cần trừ 300.000 đồng.

Nếu cả hai transaction tự tính giá trị mới từ dữ liệu đã đọc rồi ghi lại toàn bộ số dư, một lần cập nhật có thể ghi đè lần còn lại. Số dư cuối cùng có thể không phản ánh đầy đủ cả hai giao dịch.

Race Condition – Điều kiện chạy đua

Race condition xảy ra khi kết quả phụ thuộc vào thứ tự hoặc thời điểm thực thi của những thao tác đồng thời.

Ví dụ, hai request cùng kiểm tra tồn kho trước khi trừ số lượng. Bản thân từng request có thể chạy đúng theo logic riêng, nhưng kết quả kết hợp lại vi phạm quy tắc nghiệp vụ.

Dirty Write và những xung đột ghi

Database cần kiểm soát việc các transaction đồng thời ghi lên cùng dữ liệu để tránh những trạng thái không nhất quán. Những hệ quản trị transaction hiện đại thường tự động xử lý phần lớn các xung đột ghi thông qua locking và các cơ chế kiểm soát đồng thời.

3. Các loại Lock thường gặp

Shared Lock – Khóa chia sẻ

Shared lock cho phép nhiều transaction cùng giữ khóa đọc tương thích trên một đối tượng dữ liệu, nhưng ngăn những thao tác ghi xung đột trong thời gian khóa còn hiệu lực.

Ví dụ, transaction A lấy shared lock trên một bản ghi. Transaction B có thể lấy shared lock tương thích trên bản ghi đó, nhưng muốn cập nhật hoặc xóa bản ghi thường phải chờ khóa được giải phóng.

Trong Laravel Query Builder, có thể dùng:

DB::table('products')
    ->where('id', 10)
    ->sharedLock()
    ->first();

Tên phương thức và hành vi cụ thể phụ thuộc database driver và loại khóa mà hệ quản trị sử dụng.

Exclusive Lock – Khóa độc quyền

Exclusive lock là loại khóa có tính loại trừ cao hơn, thường được dùng khi transaction cần sửa đổi dữ liệu.

Khi một transaction giữ exclusive row lock trên bản ghi, transaction khác thường không thể đồng thời sửa hoặc lấy loại khóa xung đột trên cùng bản ghi cho đến khi khóa được giải phóng.

4. Pessimistic Locking và Optimistic Locking

Pessimistic Locking

Pessimistic locking giả định rằng xung đột có thể xảy ra, vì vậy ứng dụng chủ động khóa dữ liệu trước khi thực hiện chuỗi thao tác cần bảo vệ.

Ví dụ SQL:

BEGIN;

SELECT id, stock
FROM products
WHERE id = 10
FOR UPDATE;

-- Kiểm tra tồn kho.
-- Nếu đủ hàng, cập nhật số lượng.

COMMIT;

Optimistic Locking

Optimistic locking giả định xung đột không thường xuyên xảy ra. Thay vì giữ khóa trong suốt quá trình xử lý, application kiểm tra xem dữ liệu có thay đổi kể từ khi nó được đọc hay không.

Một cách triển khai phổ biến là sử dụng column version.

Ví dụ ban đầu:

id = 10
stock = 5
version = 3

Khi cần cập nhật, application dùng điều kiện:

UPDATE products
SET stock = 4,
    version = 4
WHERE id = 10
  AND version = 3;

Nếu câu lệnh cập nhật đúng một row, phiên bản vẫn phù hợp và thao tác thành công. Nếu không có row nào được cập nhật, dữ liệu có thể đã bị transaction khác thay đổi và application cần đọc lại hoặc báo xung đột.

5. Deadlock là gì?

Deadlock là tình trạng hai hoặc nhiều transaction giữ những khóa mà transaction khác cần, tạo thành vòng chờ khiến không transaction nào trong vòng đó có thể tiếp tục nếu không có sự can thiệp.

Ví dụ có hai tài khoản:

  • Tài khoản A: id = 1
  • Tài khoản B: id = 2

Hai transaction muốn chuyển tiền giữa hai tài khoản nhưng khóa chúng theo thứ tự khác nhau.

Thời điểm Transaction A Transaction B
1 Khóa tài khoản 1
2 Khóa tài khoản 2
3 Chờ khóa tài khoản 2
4 Chờ khóa tài khoản 1

Transaction A giữ khóa tài khoản 1 và chờ khóa tài khoản 2. Transaction B giữ khóa tài khoản 2 và chờ khóa tài khoản 1.

Hai transaction tạo thành vòng chờ. Đây là deadlock.

6. Nguyên nhân phổ biến gây Deadlock

Khóa nhiều bản ghi theo thứ tự khác nhau

Đây là nguyên nhân dễ hình dung nhất. Các transaction cùng cần tài nguyên nhưng lấy khóa theo thứ tự không nhất quán.

Ví dụ, một transaction xử lý tài khoản 1 rồi tài khoản 2, trong khi transaction khác xử lý tài khoản 2 rồi tài khoản 1.

Transaction kéo dài

Transaction càng kéo dài thì khóa càng được giữ lâu. Những transaction khác có nhiều cơ hội phải chờ, làm tăng nguy cơ va chạm và hình thành chu trình deadlock.

Ví dụ không nên giữ transaction đang khóa tồn kho trong khi application chờ người dùng xác nhận đơn hàng hoặc thực hiện một HTTP request chậm.

7. Xử lý Deadlock và Retry Transaction

Khi database phát hiện deadlock, một transaction thường bị hủy để giải phóng khóa. Application cần phân biệt lỗi deadlock với validation error, dữ liệu không hợp lệ hoặc những lỗi khác.

Một quy trình xử lý phù hợp:

  1. Nhận diện lỗi transaction có thể retry.
  2. Rollback toàn bộ transaction thất bại.
  3. Chờ một khoảng ngắn, có thể sử dụng exponential backoff và jitter.
  4. Chạy lại toàn bộ transaction với số lần thử có giới hạn.
  5. Nếu vẫn thất bại, ghi log đủ thông tin và trả lỗi phù hợp.

Sử dụng Locking trong Laravel Eloquent

Laravel Query Builder và Eloquent cung cấp lockForUpdate() cùng sharedLock() để sử dụng pessimistic locking. Laravel cung cấp tiện ích locking, nhưng hành vi khóa cuối cùng vẫn phụ thuộc database engine và SQL được thực thi.

8. Tổng kết

Locking giúp database điều phối những transaction cạnh tranh trên cùng dữ liệu. Shared lock và exclusive lock là hai khái niệm nền tảng; row-level lock và table-level lock thể hiện các phạm vi khóa khác nhau.

Pessimistic locking dùng khóa tường minh để bảo vệ một chuỗi thao tác; optimistic locking phát hiện xung đột khi cập nhật. SELECT ... FOR UPDATE đặc biệt hữu ích khi cần đọc, kiểm tra và sửa dữ liệu trong cùng transaction.

Deadlock xảy ra khi các transaction giữ khóa và chờ nhau theo chu trình. Những biện pháp quan trọng nhất để hạn chế deadlock là lấy khóa theo thứ tự nhất quán, giữ transaction ngắn, thu hẹp phạm vi truy vấn, sử dụng index phù hợp và cập nhật nguyên tử khi có thể.

Bài viết khác

Eloquent ORM

1. Eloquent ORM là gì? Eloquent ORM là ORM được tích hợp sẵn trong Laravel, sử dụng mô hình Active Record để tương tác với database. Eloquent cho phép developer làm việc với dữ liệu thông qua các Model trong PHP thay vì phải viết SQL thủ công cho mọi thao tác. Mỗi model thường […]

ORM

ORM là gì? (Object-Relational Mapping) Trong quá trình phát triển Backend, ứng dụng thường xuyên cần tương tác với database để lưu trữ, truy vấn, cập nhật và xóa dữ liệu. Một cách phổ biến để thực hiện những thao tác này là viết SQL trực tiếp. Tuy nhiên, khi dự án phát triển, việc […]

UAT(Kiểm thử chấp nhận người dùng)

UAT là gì? Trong toàn bộ Vòng đời phát triển phần mềm (SDLC), sau khi trải qua hàng loạt các cấp độ kiểm thử kỹ thuật nội bộ như Unit Testing, Integration Testing, System Testing, sản phẩm phần mềm vẫn chưa thể chính thức phát hành. Bước đệm cuối cùng và mang tính chất quyết […]

N+1 Query Problem

N+1 Query Problem là gì? Trong quá trình phát triển Backend, ứng dụng thường cần truy xuất dữ liệu từ database để trả về cho client. Một API có thể cần lấy danh sách đơn hàng cùng thông tin khách hàng tương ứng. Tuy nhiên, nếu ứng dụng truy vấn dữ liệu liên quan riêng […]

Transactions & ACID

Transactions & ACID là gì? Trong Backend, một request gửi đến API có thể yêu cầu hệ thống thực hiện nhiều thao tác trên database. Chẳng hạn, khi khách hàng đặt hàng, hệ thống có thể cần tạo đơn hàng, lưu chi tiết đơn hàng và cập nhật số lượng tồn kho. Nếu một thao […]

ORM – Object-Relational Mapping Fundamentals

1. ORM là gì? ORM (Object-Relational Mapping) là kỹ thuật ánh xạ giữa các đối tượng trong chương trình và dữ liệu được lưu trữ trong cơ sở dữ liệu quan hệ. Trong ứng dụng backend, dữ liệu thường được biểu diễn bằng struct hoặc object trong code, trong khi database lưu dữ liệu dưới […]

Leave a Reply

Your email address will not be published. Required fields are marked *