Khái niệm về khóa bi quan (Pessimistic Locking) và khóa lạc quan (Optimistic Locking)
Trong quản trị cơ sở dữ liệu và phát triển phần mềm, khi nhiều tiến trình cùng truy cập và chỉnh sửa một nguồn dữ liệu duy nhất, xung đột là điều khó tránh khỏi. Để giải quyết vấn đề này, hai chiến lược phổ biến nhất được sử dụng là khóa bi quan và khóa lạc quan.1. Khóa bi quan (Pessimistic Locking)
Chiến lược này tiếp cận vấn đề với thái độ thận trọng. Hệ thống giả định rằng xung đột chắc chắn sẽ xảy ra, do đó nó sẽ ngăn chặn mọi truy cập khác ngay khi một tiến trình bắt đầu đọc dữ liệu để chuẩn bị cập nhật.
- Cơ chế: Khi một giao dịch (transaction) truy vấn dữ liệu, nó sẽ yêu cầu một khóa độc quyền. Các giao dịch khác muốn sửa đổi hoặc đọc dữ liệu này (tùy loại khóa) sẽ phải đợi cho đến khi khóa được giải phóng.
- Triển khai: Thường được thực hiện ở tầng cơ sở dữ liệu thông qua các câu lệnh như
SELECT ... FOR UPDATEtrong MySQL hoặc sử dụngReentrantLocktrong ngôn ngữ lập trình Java. - Ưu điểm: Đảm bảo tính toàn vẹn dữ liệu tuyệt đối, tránh hiện tượng ghi đè dữ liệu bị mất (lost update).
- Nhược điểm: Hiệu năng hệ thống giảm đáng kể do các luồng phải chờ đợi nhau, dễ gây ra tình trạng nghẽn cổ chai hoặc Deadlock.
2. Khóa lạc quan (Optimistic Locking)
Ngược lại, khóa lạc quan giả định rằng xung đột hiếm khi xảy ra. Nó không khóa dữ liệu khi đọc mà chỉ thực hiện kiểm tra tính hợp lệ vào thời điểm dữ liệu được ghi xuống.
- Cơ chế: Thường sử dụng một trường số phiên bản (version) hoặc dấu thời gian (timestamp). Khi cập nhật, hệ thống sẽ so sánh số phiên bản hiện tại trong DB với số phiên bản mà tiến trình đã đọc trước đó. Nếu khớp, việc cập nhật được thực thi và số phiên bản tăng lên. Nếu không khớp, nghĩa là dữ liệu đã bị thay đổi bởi bên thứ ba, thao tác sẽ bị từ chối.
- Triển khai: Thường được thực hiện ở tầng ứng dụng (Application Level).
- Ưu điểm: Phù hợp với các hệ thống có lưu lượng đọc lớn, ít ghi, giúp tối ưu hóa hiệu năng vì không tốn tài nguyên duy trì khóa.
- Nhược điểm: Nếu xung đột xảy ra thường xuyên, ứng dụng sẽ phải thực hiện logic thử lại (retry), gây lãng phí chu kỳ xử lý của CPU.
Tích hợp khóa lạc quan vào MyBatis-Plus
MyBatis-Plus cung cấp một giải pháp tự động hóa việc quản lý số phiên bản thông qua interceptor, giúp lập trình viên không cần phải viết thủ công các điều kiện WHERE version = ? trong mọi câu lệnh SQL.
Bước 1: Cấu hình Bean Interceptor
Bạn cần đăng ký OptimisticLockerInnerInterceptor vào hệ thống cấu hình của MyBatis-Plus.
@Configuration
public class MybatisConcurrencyConfig {
@Bean
public MybatisPlusInterceptor concurrencyInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// Thêm plugin khóa lạc quan
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
Bước 2: Định nghĩa trường Version trong Entity
Sử dụng annotation @Version trên thuộc tính đại diện cho phiên bản dữ liệu. Kiểu dữ liệu thường là Integer hoặc Long.
@Data
public class ProductStock {
private Long id;
private String productName;
private Integer quantity;
@Version
private Integer recordRevision; // Trường dùng để kiểm soát xung đột
}
Nguyên lý hoạt động
Khi thực hiện lệnh updateById, MyBatis-Plus sẽ tự động biến đổi câu lệnh SQL. Ví dụ, nếu recordRevision hiện tại là 1:
-- SQL được sinh ra tự động
UPDATE product_stock
SET quantity = 50, record_revision = 2
WHERE id = 1001 AND record_revision = 1;
Nếu một tiến trình khác đã cập nhật bản ghi này lên phiên bản 2 trước đó, điều kiện WHERE record_revision = 1 sẽ không tìm thấy dòng nào, và kết quả trả về là 0 bản ghi được cập nhật, từ đó kích hoạt cơ chế báo lỗi hoặc xử lý xung đột.
Tùy biến bộ chặn khóa lạc quan (Custom Interceptor)
Trong một số trường hợp đặc biệt, bạn có thể muốn can thiệp sâu hơn vào quá trình kiểm tra khóa trước khi SQL được thực thi bằng cách triển khai giao diện InnerInterceptor.
public class AdvancedConflictResolver implements InnerInterceptor {
@Override
public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) {
// Logic kiểm tra tùy chỉnh
if (parameter instanceof ProductStock) {
ProductStock stock = (ProductStock) parameter;
Integer currentVer = stock.getRecordRevision();
if (currentVer == null || currentVer < 0) {
throw new RuntimeException("Phiên bản dữ liệu không hợp lệ.");
}
// Có thể thêm logic logging hoặc kiểm tra quyền tại đây
}
}
}
Sau khi tạo lớp tùy chỉnh, bạn chỉ cần đăng ký nó vào MybatisPlusInterceptor tương tự như plugin mặc định. Việc sử dụng khóa lạc quan là một lựa chọn thông minh cho các ứng dụng web hiện đại, nơi hiệu năng và khả năng mở rộng được ưu tiên hàng đầu.