CodeGym /Các khóa học /SQL SELF /Giới hạn và các vấn đề tiềm ẩn khi làm việc với giao dịch...

Giới hạn và các vấn đề tiềm ẩn khi làm việc với giao dịch: LOCK, DEADLOCK

SQL SELF
Mức độ, Bài học
Có sẵn

Giao dịch trong PostgreSQL đảm bảo tính cô lập — một trong những đặc điểm then chốt của ACID. Để làm được điều này, hệ thống dùng lock. Lock là cơ chế đảm bảo nhiều giao dịch không "đánh nhau" vì cùng một bản ghi hay bảng. Hãy tưởng tượng bạn đang xếp hàng ở siêu thị: mỗi lần chỉ có một người được thanh toán ở quầy. Lock cũng hoạt động kiểu như vậy, chỉ là nó kiểm soát quyền truy cập dữ liệu trong bảng thôi.

Các loại lock cơ bản

Trong PostgreSQL có vài loại lock, có thể bạn đã từng thấy trong EXPLAIN ANALYZE:

  1. ROW EXCLUSIVE (lock độc quyền trên dòng) — xuất hiện khi thay đổi dữ liệu trong một dòng. Đây là loại lock phổ biến nhất, ví dụ khi chạy INSERT, UPDATE hoặc DELETE.
  2. SHARE (lock chia sẻ) — dùng cho các thao tác đảm bảo dữ liệu không bị thay đổi trong lúc truy vấn, ví dụ khi chạy SELECT ... FOR SHARE.
  3. EXCLUSIVE (lock độc quyền) — ngăn mọi thao tác khác với dữ liệu, trừ việc đọc.

Có thể nói, lock giống như mấy con chó canh gác dữ liệu của chúng ta vậy.

Vấn đề với lock: Có thể gặp rắc rối gì?

Giờ khi đã hiểu lock là gì, cùng xem nó có thể gây ra vấn đề gì. Lớn nhất là vấn đề deadlock. Deadlock là tình huống hai (hoặc nhiều) giao dịch chờ nhau, khiến mọi thứ bị kẹt trong vòng lặp vô tận. Thường nó như này:

  1. Giao dịch 1 lock dòng A và muốn truy cập dòng B.
  2. Giao dịch 2 lock dòng B và muốn truy cập dòng A.
  3. Vì cả hai dòng đều bị lock, không giao dịch nào hoàn thành được.

Ví dụ về deadlock

Để cảm nhận nỗi đau của dev khi gặp deadlock, tưởng tượng kịch bản này nhé:

-- Giao dịch 1
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;

-- Đồng thời
-- Giao dịch 2
BEGIN;
UPDATE accounts SET balance = balance + 200 WHERE id = 2;

-- Giao dịch 1 cố lock id = 2
UPDATE accounts SET balance = balance - 100 WHERE id = 2;

-- Giao dịch 2 cố lock id = 1
UPDATE accounts SET balance = balance + 200 WHERE id = 1;

Và thế là xong! Mỗi giao dịch bị lock bởi giao dịch còn lại. PostgreSQL kiểu gì cũng sẽ phát hiện ra và ném lỗi deadlock.

Làm sao tránh deadlock?

  1. Truy cập dữ liệu theo thứ tự nhất quán. Cố gắng luôn truy cập dữ liệu theo cùng một thứ tự. Nếu giao dịch 1 lock dòng A trước rồi đến dòng B, thì giao dịch 2 cũng nên làm vậy: dòng A trước, dòng B sau.
-- Thứ tự truy cập đúng
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 200 WHERE id = 2;
COMMIT;
  1. Giảm thời gian giao dịch xuống tối thiểu. Giao dịch càng lâu thì càng dễ bị deadlock. Hãy kết thúc giao dịch càng nhanh càng tốt: BEGIN, làm hết các thay đổi cần thiết rồi COMMIT ngay.

  2. Dùng mức cô lập giao dịch hợp lý. Nếu bài toán của bạn không cần cô lập dữ liệu quá nghiêm ngặt, hãy cân nhắc dùng mức READ COMMITTED. Nó giúp tránh được một số loại lock.

Timeout và chẩn đoán lock

Có nhiều công cụ để chẩn đoán và phòng tránh lock trong PostgreSQL. Ví dụ, bạn có thể đặt thời gian chờ lock tối đa:

SET lock_timeout = '5s';  -- Đặt timeout lock là 5 giây

Nếu lock giữ lâu hơn thời gian này, giao dịch sẽ bị dừng lại, tránh bị kẹt hoàn toàn.

Theo dõi lock trong PostgreSQL

Một lệnh hữu ích để theo dõi lock là pg_locks. Nó hiển thị tất cả lock đang hoạt động trong hệ thống:

SELECT * FROM pg_locks;

Bạn sẽ thấy giao dịch nào đang giữ lock và giao dịch nào đang chờ được unlock. Rất hữu ích khi debug deadlock.

Đặc điểm của LOCK và lock thủ công đối tượng

Nếu bạn cần tự quản lý lock, hãy dùng lệnh LOCK:

LOCK TABLE orders IN ACCESS EXCLUSIVE MODE;

Chú ý: ACCESS EXCLUSIVE là loại lock mạnh nhất, nó cấm mọi thao tác khác với bảng, kể cả SELECT. Chỉ dùng khi thực sự cần thiết (ví dụ thay đổi cấu trúc bảng).

Để lock riêng từng dòng khi thay đổi, dùng SELECT ... FOR UPDATE:

SELECT * FROM accounts WHERE id = 1 FOR UPDATE;

Cách này đảm bảo các giao dịch khác không thể thay đổi dòng này cho đến khi giao dịch của bạn kết thúc.

Nên nhớ gì về lock?

Lock không phải là kẻ xấu, mà là công cụ. Nó giúp giữ dữ liệu nguyên vẹn, nhưng cần dùng cẩn thận. Đây là vài lời khuyên quan trọng:

  1. Đừng bắt đầu giao dịch nếu bạn không biết rõ mình cần nó để làm gì.
  2. Kết thúc giao dịch càng nhanh càng tốt.
  3. Tránh truy cập dữ liệu theo thứ tự khác nhau.
  4. Dùng mức cô lập phù hợp với bài toán của bạn.

Giờ thì bạn đã sẵn sàng làm việc với lock và deadlock rồi! Chúc bạn luôn có pg_locks và sự thông thái của ACID bên mình.

2
Nhiệm vụ
SQL SELF, mức độ, bài học
Đã khóa
Tạo và xem các khóa (lock)
Tạo và xem các khóa (lock)
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION