CodeGym /Các khóa học /SQL SELF /Mức cô lập SERIALIZABLE: cô lập hoàn toàn v...

Mức cô lập SERIALIZABLE: cô lập hoàn toàn và ngăn chặn Phantom Read

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

SERIALIZABLE — đây là mức cô lập giao dịch cao nhất trong PostgreSQL. Mức này đảm bảo rằng kết quả của các giao dịch song song sẽ giống hệt như khi chúng được thực hiện TUẦN TỰ, từng cái một. Không có bất kỳ bất thường nào của thực thi song song (ví dụ như Dirty Read, Non-Repeatable Read, Phantom Read) có thể xảy ra.

Nói đơn giản, SERIALIZABLE đảm bảo trật tự tuyệt đối và tính nhất quán giữa các giao dịch song song. Nó giống như PostgreSQL nói: "Tất cả giao dịch — xếp hàng nhé các bạn!"

Tại sao cần mức SERIALIZABLE? Đôi khi bạn muốn chắc chắn 100% rằng dữ liệu của mình luôn nhất quán, bất chấp các thay đổi song song. Hãy tưởng tượng cảnh trong siêu thị, nơi các thu ngân phục vụ khách cùng lúc. Nếu không ai kiểm soát thứ tự, có thể số hàng hóa ra khỏi cửa hàng sẽ nhiều hơn số đã mua. Với SERIALIZABLE thì chuyện đó không thể xảy ra.

Ví dụ thiết lập mức SERIALIZABLE

Để đặt mức cô lập SERIALIZABLE, bạn dùng lệnh:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

Ví dụ, tạo một giao dịch sử dụng mức này:

BEGIN; -- Bắt đầu giao dịch
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- Đặt mức cô lập
SELECT * FROM products WHERE category = 'Electronics'; -- Lấy danh sách sản phẩm
UPDATE products SET stock = stock - 1 WHERE product_id = 123; -- Cập nhật tồn kho
COMMIT; -- Xác nhận thay đổi

Case: đặt vé rạp chiếu phim

Cùng xem một ví dụ thực tế nơi mức SERIALIZABLE là không thể thiếu. Giả sử bạn đang phát triển hệ thống đặt vé rạp chiếu phim online. Người dùng chọn chỗ ngồi, và bạn muốn đảm bảo rằng không có hai khách cùng mua một chỗ cùng lúc.

Đầu tiên tạo bảng chỗ ngồi:

CREATE TABLE seats (
    seat_id SERIAL PRIMARY KEY,
    is_booked BOOLEAN DEFAULT FALSE
);

Bây giờ thêm vài chỗ ngồi:

INSERT INTO seats (is_booked) VALUES (FALSE), (FALSE), (FALSE);

Ví dụ về giao dịch với SERIALIZABLE.

Đây là cách đặt chỗ an toàn:

BEGIN; -- Bắt đầu giao dịch
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- Mức cô lập SERIALIZABLE

-- Kiểm tra chỗ còn trống không
SELECT is_booked FROM seats WHERE seat_id = 1;

-- Đặt chỗ
UPDATE seats SET is_booked = TRUE WHERE seat_id = 1;

COMMIT; -- Xác nhận đặt chỗ

Nếu có giao dịch song song thứ hai cố đặt cùng chỗ đó, PostgreSQL sẽ không để xảy ra lộn xộn và sẽ báo lỗi conflict serialize.

Ngăn chặn Phantom Read

Giờ cùng tìm hiểu về "phantom read" mà chúng ta muốn tránh. Phantom Read xảy ra khi một giao dịch nhìn thấy dữ liệu được thêm bởi giao dịch khác trong quá trình nó đang chạy. Ví dụ, giao dịch của bạn mong đợi một số dòng nhất định, nhưng bất ngờ giao dịch khác thêm hoặc xóa dòng, làm thay đổi kết quả.

Xem ví dụ:

Dữ liệu trước khi bắt đầu giao dịch

id balance user
1 1000 Alice
2 500 Bob

Giao dịch 1

BEGIN;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- Đếm số user có balance lớn hơn 400
SELECT COUNT(*) FROM accounts WHERE balance > 400;

-- Mong đợi kết quả: 2 (Alice và Bob)

Giao dịch 2

Ở session khác, một giao dịch song song được thực hiện:

BEGIN;
INSERT INTO accounts (id, balance, user) VALUES (3, 700, 'Charlie');
COMMIT;

Quay lại Giao dịch 1

-- Lặp lại truy vấn
SELECT COUNT(*) FROM accounts WHERE balance > 400;

Bây giờ, nếu không dùng SERIALIZABLE, kết quả sẽ là 3 thay vì 2, vì Charlie được thêm vào trong lúc Giao dịch 1 đang chạy. Đó chính là Phantom Read.

Nhưng với SERIALIZABLE, PostgreSQL đảm bảo Giao dịch 1 sẽ không thấy Charlie, vì "thế giới" của nó đã bị đóng băng tại thời điểm bắt đầu giao dịch.

Đặc điểm và hạn chế của mức SERIALIZABLE

Chúng ta đã hiểu SERIALIZABLE giúp đạt được cô lập lý tưởng như thế nào. Nhưng trên đời này có gì hoàn hảo mà không có nhược điểm đâu? Nói thật nhé.

Giảm hiệu năng
SERIALIZABLE tốn nhiều tài nguyên hơn hẳn so với các mức READ COMMITTED hay REPEATABLE READ. Vì sao? PostgreSQL phải giả lập thực thi tuần tự, theo dõi mọi xung đột có thể giữa các giao dịch.

Lỗi serialize
Nếu PostgreSQL phát hiện không thể thực hiện giao dịch theo "thứ tự lý tưởng", nó sẽ báo lỗi serialize (serialization_failure) và rollback giao dịch.

Ví dụ lỗi:

ERROR: could not serialize access due to concurrent update

Để xử lý tình huống này, ta có thể chạy lại giao dịch sau khi thất bại:

DO $$
DECLARE
    done BOOLEAN := FALSE;
BEGIN
    WHILE NOT done LOOP
        BEGIN
            -- Bắt đầu giao dịch
            BEGIN;
            SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

            -- Thực hiện thao tác
            UPDATE accounts SET balance = balance - 100 WHERE id = 1;

            -- Xác nhận thay đổi
            COMMIT;
            done := TRUE; -- Thoát vòng lặp nếu thành công
        EXCEPTION WHEN serialization_failure THEN
            ROLLBACK; -- Rollback khi lỗi
        END;
    END LOOP;
END;
$$;

Đây là cách làm quen thuộc trong các hệ thống dùng SERIALIZABLE.

Quan trọng!

Đoạn code này được viết bằng PL-SQL. Mình sẽ quay lại với nó sau nhé. Chỉ muốn cho bạn xem code đẹp và chạy được thôi. Và cũng để bạn thấy tại sao cần PL-SQL :)

Khi nào nên dùng SERIALIZABLE?

Dùng mức cô lập này khi cái giá của sai sót là rất lớn:

  • Giao dịch tài chính, như xử lý thanh toán hoặc chia thưởng.
  • Hệ thống quản lý kho, để tránh trùng lặp đơn hàng.
  • Đặt chỗ online, nơi cần loại trừ xung đột khi đặt tài nguyên.

Nếu bạn xây dựng hệ thống mà dữ liệu phải nhất quán 100%, còn hiệu năng không quá quan trọng, SERIALIZABLE sẽ là bạn thân nhất của bạn.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION