Locking & Deadlock là gì? Cơ chế khóa và bế tắc trong Database
Trong một ứng dụng Backend, nhiều request có thể truy cập và thay đổi dữ liệu trong database cùng một thời điểm. Nếu không kiểm soát việc truy cập đồng thời phù hợp, các thao tác có thể xung đột hoặc khiến dữ liệu không còn nhất quán.
Để quản lý những tình huống này, database sử dụng các cơ chế như Locking (khóa). Tuy nhiên, khi nhiều transaction cùng giữ và chờ những khóa mà transaction khác đang nắm giữ, hệ thống có thể gặp Deadlock (bế tắc).
Hiểu rõ hai khái niệm này giúp Backend Developer thiết kế transaction an toàn hơn, kiểm soát truy cập dữ liệu đồng thời và xử lý các vấn đề hiệu năng trong những ứng dụng có nhiều request.
1. Locking là gì?
Locking là cơ chế mà database sử dụng để kiểm soát việc truy cập hoặc thay đổi tài nguyên dữ liệu khi nhiều transaction hoạt động đồng thời.
Một transaction có thể cần khóa dữ liệu trước khi thực hiện một thao tác để tránh xung đột với các transaction khác. Nếu transaction khác yêu cầu một khóa không tương thích trên cùng tài nguyên, nó có thể phải chờ cho đến khi khóa được giải phóng.
Ví dụ, một cửa hàng chỉ còn một sản phẩm trong kho:
| id | name | stock |
|---|---|---|
| 1 | Keyboard | 1 |
Hai khách hàng cùng gửi request đặt mua sản phẩm gần như đồng thời. Nếu cả hai request đều đọc số lượng tồn kho là 1 trước khi cập nhật, hệ thống có thể xử lý sai nếu logic kiểm tra và cập nhật không được thiết kế để an toàn khi chạy đồng thời.
Locking giúp database điều phối các thao tác xung đột để những transaction liên quan không tùy ý thay đổi cùng dữ liệu tại cùng thời điểm.
Locking hoạt động như thế nào?
Trong PostgreSQL, có thể sử dụng SELECT ... FOR UPDATE bên trong transaction để khóa các hàng được chọn, ngăn những transaction khác lấy khóa xung đột trên các hàng đó cho đến khi transaction hiện tại kết thúc.
Ví dụ:
BEGIN;
SELECT stock
FROM products
WHERE id = 1
FOR UPDATE;
-- Kiểm tra số lượng tồn kho.
-- Nếu còn hàng, thực hiện cập nhật.
UPDATE products
SET stock = stock - 1
WHERE id = 1 AND stock > 0;
COMMIT;
Khi transaction thực hiện SELECT ... FOR UPDATE, PostgreSQL khóa hàng phù hợp. Một transaction khác muốn lấy khóa xung đột trên hàng đó thường phải chờ.
Tuy nhiên, chỉ khóa dữ liệu chưa đủ để bảo đảm nghiệp vụ đặt hàng chính xác. Ứng dụng vẫn cần kiểm tra số lượng tồn kho và kết quả cập nhật. Điều kiện stock > 0 giúp tránh giảm số lượng xuống giá trị âm.
Đây là một ví dụ về row-level locking (khóa hàng) trong PostgreSQL.
2. Các loại Lock thường gặp
Database có thể sử dụng nhiều loại khóa khác nhau tùy theo hệ quản trị và thao tác đang thực hiện.
Shared Lock — Khóa chia sẻ
Shared lock cho phép nhiều thao tác giữ khóa đọc tương thích cùng lúc, nhưng có thể xung đột với những thao tác cần khóa ghi.
Exclusive Lock — Khóa độc quyền
Exclusive lock được sử dụng để bảo vệ tài nguyên khỏi những thao tác xung đột. Trong thời gian khóa được giữ, các transaction khác có thể phải chờ nếu yêu cầu khóa không tương thích.
Row-level Lock — Khóa hàng
Khóa ở mức hàng dữ liệu, cho phép các transaction thao tác trên những hàng khác nhau mà không nhất thiết phải chặn nhau.
Ví dụ, hai transaction cập nhật hai sản phẩm có ID khác nhau thường có thể tiến hành đồng thời nếu không có xung đột khác.
Table-level Lock — Khóa bảng
Khóa ở mức bảng, ảnh hưởng đến phạm vi dữ liệu rộng hơn. PostgreSQL sử dụng nhiều chế độ khóa bảng khác nhau, không phải tất cả đều chặn mọi thao tác đọc và ghi.
Cần lưu ý rằng cách phân loại và hành vi của lock phụ thuộc vào hệ quản trị database. Những ví dụ trên không có nghĩa mọi database đều sử dụng cùng một cơ chế khóa.
3. Deadlock là gì?

Deadlock là tình trạng hai hoặc nhiều transaction chờ những tài nguyên đang bị transaction khác giữ, tạo thành một chu trình chờ khiến các transaction liên quan không thể tiếp tục theo cách thông thường.
Ví dụ, có hai tài khoản ngân hàng là A và B. Hai transaction đồng thời thực hiện chuyển tiền theo hai hướng ngược nhau.
- Transaction 1 muốn chuyển tiền từ A sang B.
- Transaction 2 muốn chuyển tiền từ B sang A.
Giả sử hai transaction khóa các hàng theo thứ tự khác nhau:
- Transaction 1 khóa hàng tài khoản A.
- Transaction 2 khóa hàng tài khoản B.
- Transaction 1 yêu cầu khóa hàng B nhưng phải chờ Transaction 2.
- Transaction 2 yêu cầu khóa hàng A nhưng phải chờ Transaction 1.
Lúc này, Transaction 1 chờ Transaction 2, còn Transaction 2 chờ Transaction 1. Đây là một deadlock.
Minh họa Deadlock
Transaction 1 Transaction 2
| |
Khóa tài khoản A Khóa tài khoản B
| |
Chờ khóa B ----------------> |
| |
| <---------------- Chờ khóa A
| |
+---------- Deadlock ----------+
Trong PostgreSQL, hệ thống có cơ chế phát hiện deadlock. Khi phát hiện chu trình chờ, PostgreSQL sẽ hủy một transaction để phá vỡ bế tắc, cho phép những transaction còn lại tiếp tục khi các điều kiện khóa đã được giải phóng.
Ứng dụng cần xử lý lỗi từ database và có thể thử lại toàn bộ transaction bị hủy nếu thao tác đó an toàn để retry.
4. Phân biệt Locking và Deadlock
| Tiêu chí | Locking | Deadlock |
|---|---|---|
| Bản chất | Cơ chế kiểm soát truy cập dữ liệu | Tình trạng các transaction chờ khóa lẫn nhau theo chu trình |
| Mục đích hoặc nguyên nhân | Quản lý các thao tác có khả năng xung đột | Phát sinh khi các transaction giữ và chờ tài nguyên của nhau |
| Có phải lỗi không? | Không, đây là cơ chế bình thường của database | Là tình huống cần được phát hiện và xử lý |
| Hậu quả | Transaction khác có thể phải chờ khóa | Database thường phải hủy một transaction để phá vỡ chu trình |
| Cách xử lý | Chọn phạm vi và thời gian giữ khóa phù hợp | Giảm khả năng tạo chu trình chờ, xử lý lỗi và retry khi phù hợp |
Điều quan trọng là không phải mọi tình trạng chờ lock đều là deadlock. Nếu Transaction 2 chờ Transaction 1 hoàn thành rồi tiếp tục, đó chỉ là chờ khóa thông thường. Deadlock xuất hiện khi tồn tại một chu trình chờ giữa các transaction.
5. Nguyên nhân và cách hạn chế Deadlock
Deadlock thường xuất hiện khi các transaction truy cập nhiều tài nguyên theo thứ tự khác nhau hoặc giữ khóa trong thời gian dài.
Một số nguyên tắc giúp hạn chế tình trạng này:
5.1. Truy cập dữ liệu theo cùng một thứ tự
Nếu nhiều transaction cần khóa các tài khoản A và B, hãy thiết kế để chúng luôn khóa A trước rồi mới khóa B, hoặc theo thứ tự ID thống nhất.
Điều này giúp giảm nguy cơ các transaction giữ những khóa khác nhau rồi chờ khóa của nhau.
5.2. Giữ transaction ngắn
Không nên giữ transaction mở lâu hơn mức cần thiết. Những thao tác như gọi API bên ngoài hoặc chờ người dùng nhập dữ liệu nên được thực hiện ngoài transaction khi có thể.
Transaction kéo dài có thể khiến khóa được giữ lâu hơn và làm tăng thời gian chờ của các request khác.
5.3. Chỉ khóa dữ liệu cần thiết
Tránh khóa quá nhiều hàng hoặc bảng khi chỉ cần thay đổi một phần nhỏ dữ liệu. Phạm vi khóa rộng có thể làm tăng khả năng xung đột giữa các transaction.
5.4. Xử lý lỗi và retry
Khi database hủy transaction do deadlock, ứng dụng có thể thử lại toàn bộ transaction sau một khoảng chờ ngắn, với số lần thử có giới hạn.
Việc retry cần được thiết kế cẩn thận để tránh thực hiện lặp lại các tác động bên ngoài database hoặc gây ra giao dịch trùng lặp.
5.5. Theo dõi truy vấn và log
Khi deadlock xảy ra thường xuyên, cần kiểm tra log database, những transaction liên quan và các tài nguyên mà chúng đang khóa hoặc chờ.
Việc tìm nguyên nhân cụ thể giúp xác định có cần thay đổi thứ tự truy cập dữ liệu, thiết kế transaction hay tối ưu truy vấn hay không.
6. Locking và Deadlock trong dự án Backend thực tế
Trong một hệ thống Backend, nhiều request có thể cùng thực hiện thao tác trên database. Các chức năng như đặt hàng, cập nhật tồn kho, chuyển tiền hoặc xử lý thanh toán thường cần đặc biệt chú ý đến truy cập đồng thời.
Ví dụ, API đặt hàng có thể thực hiện những bước sau:
- Nhận request đặt hàng từ client.
- Bắt đầu transaction.
- Kiểm tra và khóa dữ liệu tồn kho cần thiết.
- Tạo đơn hàng và cập nhật số lượng sản phẩm.
- Commit transaction nếu các thao tác thành công; rollback khi xảy ra lỗi.
Nếu các transaction được thiết kế không phù hợp, hệ thống có thể gặp xung đột khóa, thời gian chờ kéo dài hoặc deadlock.
Backend Developer cần hiểu cách database quản lý khóa để chọn giải pháp phù hợp, thay vì chỉ tập trung vào việc viết các câu truy vấn hoặc gọi ORM.
7. Kết luận
Locking là cơ chế quan trọng giúp database kiểm soát những thao tác truy cập và thay đổi dữ liệu đồng thời. Deadlock là tình trạng bế tắc có thể xảy ra khi các transaction giữ và chờ những khóa của nhau theo một chu trình.
Trong phát triển Backend, mục tiêu không phải là loại bỏ mọi lock, bởi locking là một phần cần thiết để quản lý dữ liệu đồng thời. Thay vào đó, lập trình viên cần thiết kế transaction hợp lý, giảm thời gian giữ khóa, thống nhất thứ tự truy cập tài nguyên và xử lý lỗi deadlock an toàn.
Hiểu được mối quan hệ giữa locking, deadlock và transaction giúp Backend Developer xây dựng những hệ thống có khả năng xử lý nhiều request đồng thời mà vẫn duy trì tính nhất quán của dữ liệu.








Khoá học lập trình game con rắn cho trẻ em




