TLDR: Bao gồm các hoạt động liên quan trong giao dịch với BEGIN/commit. Giao dịch đảm bảo tất cả các hoạt động cùng thành công hoặc tất cả đều thất bại. PostgreSQL cung cấp đảm bảo ACID. Sử dụng các mức cách ly thích hợp để cân bằng tính nhất quán và hiệu suất. Xử lý bế tắc một cách khéo léo.
Giao dịch đảm bảo dữ liệu luôn nhất quán ngay cả khi nhiều thao tác xảy ra đồng thời hoặc xảy ra lỗi.
Giao dịch là gì?
Giao dịch là một chuỗi các hoạt động được thực hiện dưới dạng một đơn vị:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
Cả hai bản cập nhật đều xảy ra hoặc không xảy ra. Không chuyển nhượng một phần.
Các lệnh giao dịch cơ bản
-- Start transaction
BEGIN;
-- Do work
INSERT INTO users (username, email) VALUES ('alice', 'alice@example.com');
INSERT INTO posts (user_id, title) VALUES (1, 'First Post');
-- If everything worked:
COMMIT;
-- If something went wrong:
-- ROLLBACK;
Khôi phục khi có lỗi
BEGIN;
INSERT INTO users (username, email) VALUES ('bob', 'bob@example.com');
INSERT INTO users (username, email) VALUES ('bob', 'duplicate@example.com'); -- Fails (duplicate username)
ROLLBACK; -- Undo the first INSERT too
PostgreSQL tự động khôi phục nếu xảy ra lỗi và bạn không xử lý.
Thuộc tính axit
Giao dịch cung cấp đảm bảo ACID:
Tính nguyên tử
Mọi hoạt động đều thành công hoặc tất cả đều thất bại. Không thực hiện một phần.
BEGIN;
UPDATE inventory SET quantity = quantity - 5 WHERE product_id = 1;
INSERT INTO orders (product_id, quantity) VALUES (1, 5);
COMMIT;
Nếu một trong hai câu lệnh không thành công, cả hai đều quay trở lại. inventory và các đơn hàng luôn được đồng bộ hóa.
Tính nhất quán
Cơ sở dữ liệu di chuyển từ một hợp lệ trạng thái đến cái khác. Các ràng buộc được thực thi.
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
-- Total balance unchanged:
SELECT SUM(balance) FROM accounts; -- Same before and after
Cách ly
Các giao dịch đồng thời không can thiệp lẫn nhau.
-- Transaction 1
BEGIN;
SELECT balance FROM accounts WHERE id = 1; -- Sees 500
-- ... do something ...
COMMIT;
-- Transaction 2 (running simultaneously)
BEGIN;
UPDATE accounts SET balance = 1000 WHERE id = 1;
COMMIT;
-- Transaction 1 doesn't see Transaction 2's changes until it commits
Độ bền
Khi đã commit, các thay đổi vẫn tồn tại ngay cả khi hệ thống gặp sự cố.
BEGIN;
INSERT INTO critical_data (value) VALUES ('important');
COMMIT;
-- Power failure here
-- On restart, the insert is still there
PostgreSQL ghi vào Nhật ký ghi trước (WAL) trước khi xác nhận commit, đảm bảo độ bền.
Mức độ cô lập
Kiểm soát cách các giao dịch nhìn thấy những thay đổi của nhau. Đánh đổi giữa tính nhất quán và tính đồng thời.
Đọc không cam kết
Không được hỗ trợ trong PostgreSQL (được coi là Đọc đã commit). Cho phép đọc bẩn trong cơ sở dữ liệu khác.
Đọc đã commit (Mặc định)
Chỉ thấy các thay đổi của đã commit. Mức độ phổ biến nhất.
-- Transaction 1
BEGIN;
UPDATE products SET price = 100 WHERE id = 1;
-- Not committed yet
-- Transaction 2
BEGIN;
SELECT price FROM products WHERE id = 1; -- Sees old price
-- Wait for Transaction 1 to commit
SELECT price FROM products WHERE id = 1; -- Now sees 100
COMMIT;
Đọc lặp lại
Các truy vấn sẽ thấy một ảnh chụp nhanh nhất quán từ khi bắt đầu giao dịch.
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SELECT balance FROM accounts WHERE id = 1; -- Sees 500
-- Another transaction updates the balance to 1000 and commits
SELECT balance FROM accounts WHERE id = 1; -- Still sees 500 (snapshot)
COMMIT;
Ngăn chặn các lần đọc không lặp lại nhưng có thể gây ra lỗi tuần tự hóa:
-- Transaction 1
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT * FROM products WHERE id = 1;
UPDATE products SET stock = stock - 1 WHERE id = 1;
-- Transaction 2 modified this row
COMMIT; -- ERROR: could not serialize access
Có thể tuần tự hóa
Sự cô lập mạnh mẽ nhất. Các giao dịch được thực hiện như thể chúng chạy lần lượt.
BEGIN ISOLATION LEVEL SERIALIZABLE;
-- ... operations ...
COMMIT;
Ngăn chặn mọi sự bất thường nhưng gây ra nhiều lỗi tuần tự hóa hơn. Sử dụng khi tính nhất quán là rất quan trọng.
Đặt mức mặc định:
ALTER DATABASE mydb SET default_transaction_isolation TO 'repeatable read';
Điểm lưu
Hoàn trả một phần trong các giao dịch:
BEGIN;
INSERT INTO users (username) VALUES ('alice');
SAVEPOINT before_posts;
INSERT INTO posts (user_id, title) VALUES (1, 'Post 1');
INSERT INTO posts (user_id, title) VALUES (1, 'Post 2'); -- Might fail
-- If post insert fails, rollback to savepoint
ROLLBACK TO before_posts;
-- User insert still committed
COMMIT;
Hữu ích cho các hoạt động phức tạp trong đó một số bộ phận có thể thất bại trong khi những bộ phận khác thành công.
Xử lý truy cập đồng thời
Khóa cấp hàng
CHỌN ĐỂ CẬP NHẬT khóa các hàng:
BEGIN;
SELECT * FROM inventory WHERE product_id = 1 FOR UPDATE;
-- Other transactions wait here
UPDATE inventory SET quantity = quantity - 1 WHERE product_id = 1;
COMMIT;
Ngăn chặn hai giao dịch giảm cùng một lúc inventory.
Các loại khóa
-- Exclusive lock (blocks all other locks)
SELECT * FROM products WHERE id = 1 FOR UPDATE;
-- Shared lock (blocks FOR UPDATE but allows FOR SHARE)
SELECT * FROM products WHERE id = 1 FOR SHARE;
-- Skip locked rows
SELECT * FROM queue WHERE processed = false FOR UPDATE SKIP LOCKED LIMIT 10;
-- Error if rows are locked
SELECT * FROM products WHERE id = 1 FOR UPDATE NOWAIT;
ĐỂ CẬP NHẬT BỎ QUA LOCKED rất phù hợp cho hàng đợi công việc - mỗi công nhân nhận được các hàng khác nhau.
Khóa tư vấn
Khóa cấp ứng dụng sử dụng số tùy ý:
-- Try to acquire lock 1234
SELECT pg_try_advisory_lock(1234);
-- Do work
UPDATE shared_resource SET value = value + 1;
-- Release lock
SELECT pg_advisory_unlock(1234);
Hữu ích cho việc phối hợp giữa các ứng dụng.
Bế tắc
Hai giao dịch chờ đợi lẫn nhau:
-- Transaction 1
BEGIN;
UPDATE products SET price = 100 WHERE id = 1;
-- Waiting for Transaction 2
-- Transaction 2
BEGIN;
UPDATE products SET price = 200 WHERE id = 2;
UPDATE products SET price = 150 WHERE id = 1; -- Waits for Transaction 1
-- Transaction 1 continues
UPDATE products SET price = 250 WHERE id = 2; -- Waits for Transaction 2
-- DEADLOCK
PostgreSQL phát hiện bế tắc và hủy bỏ một giao dịch:
ERROR: deadlock detected
DETAIL: Process 1234 waits for ShareLock on transaction 5678; blocked by process 5678.
Xử lý mã ứng dụng:
try:
execute_transaction()
except DeadlockError:
retry_transaction()
Ngăn chặn bế tắc bằng cách truy cập tài nguyên theo thứ tự nhất quán:
-- Always update products in ID order
BEGIN;
UPDATE products SET price = 100 WHERE id IN (1, 2, 5) ORDER BY id;
COMMIT;
Giao dịch dài hạn
Tránh giữ các giao dịch ở trạng thái mở:
-- Bad
BEGIN;
SELECT * FROM products;
-- User thinks for 10 minutes
UPDATE products SET ...;
COMMIT;
Giao dịch dài:
- Chặn các giao dịch khác
- Ngăn chặn chân không dọn dẹp hàng chết
- Giữ khóa lâu hơn mức cần thiết
Giữ giao dịch ngắn gọn:
-- Good
SELECT * FROM products;
-- Let user decide
-- When ready:
BEGIN;
UPDATE products SET ...;
COMMIT;
Các mô hình giao dịch thực tế
Chuyển tiền
BEGIN;
UPDATE accounts SET balance = balance - 100
WHERE id = 1 AND balance >= 100;
IF NOT FOUND THEN
ROLLBACK;
RAISE EXCEPTION 'Insufficient funds';
END IF;
UPDATE accounts SET balance = balance + 100
WHERE id = 2;
COMMIT;
Xử lý các mục trong hàng đợi
BEGIN;
-- Lock and get next item
SELECT * FROM queue
WHERE processed = false
ORDER BY created_at
FOR UPDATE SKIP LOCKED
LIMIT 1;
-- Process item
-- ... do work ...
-- Mark processed
UPDATE queue SET processed = true WHERE id = ...;
COMMIT;
Nhiều công nhân có thể xử lý các mục khác nhau cùng một lúc.
Máy đếm nguyên tử
BEGIN;
UPDATE statistics
SET view_count = view_count + 1,
updated_at = CURRENT_TIMESTAMP
WHERE page_id = 123;
COMMIT;
Thực tiễn tốt nhất về giao dịch
Giữ giao dịch ngắn: Nhận khóa, thực hiện công việc, commit. Không giữ các giao dịch trong khi người dùng nhập hoặc lệnh gọi API bên ngoài.
Lỗi xử lý: Luôn có cách xử lý lỗi khôi phục khi không thành công.
Sử dụng mức cách ly thích hợp: Đọc đã commit trong hầu hết các trường hợp, Đọc lặp lại hoặc Có thể tuần tự hóa khi cần.
Atránh các giao dịch lồng nhau: PostgreSQL không hỗ trợ các giao dịch lồng nhau thực sự. Thay vào đó hãy sử dụng điểm lưu trữ.
Thử lại lỗi tuần tự hóa: Khi sử dụng Đọc lặp lại hoặc Có thể tuần tự hóa, hãy thử lại các xung đột.
Index khóa ngoại: Giúp tránh tình trạng chờ khóa trong khi cập nhật.
Khóa màn hình chờ:
SELECT
pid,
usename,
pg_blocking_pids(pid) AS blocked_by,
query
FROM pg_stat_activity
WHERE cardinality(pg_blocking_pids(pid)) > 0;
Giao dịch giữ cho dữ liệu của bạn nhất quán ngay cả khi truy cập đồng thời và không thành công. Tiếp theo, chúng tôi sẽ đề cập đến việc sao lưu và phục hồi - cách bảo vệ dữ liệu của bạn và khôi phục dữ liệu khi cần.