Hãy tưởng tượng bạn đang xây dựng một tính năng chuyển tiền cho một ứng dụng ngân hàng. Quy trình nghe có vẻ đơn giản: Trừ tiền ở tài khoản A, rồi cộng tiền vào tài khoản B. Nhưng chuyện gì sẽ xảy ra nếu ngay sau khi tài khoản A vừa bị trừ tiền thì... bùm, server sập hoặc database mất kết nối? Tài khoản A mất tiền nhưng tài khoản B vẫn chưa nhận được gì. Đây chính là cơn ác mộng mang tên "dữ liệu không nhất quán" mà mọi lập trình viên Backend đều phải đối mặt. Rất may, PostgreSQL Transaction sinh ra để làm người hùng cứu cánh cho những tình huống oái oăm này.
Chuyện gì xảy ra khi code "nửa đường gãy gánh"?
Trong thế giới database, chúng ta thường phải thực hiện một chuỗi các câu lệnh SQL liên quan mật thiết đến nhau. Nếu thực hiện rời rạc, mỗi câu lệnh là một thực thể độc lập, bạn sẽ rất dễ rơi vào cảnh dữ liệu "ông chẳng bà chuộc". Khi một bước trong quy trình thất bại, những bước trước đó đã được lưu lại (commit) sẽ không thể tự động quay lại trạng thái cũ.

Đây không chỉ là vấn đề về tiền bạc. Nó có thể là việc bạn tạo một đơn hàng nhưng chưa kịp lưu các mặt hàng trong giỏ, hoặc tạo một tài khoản người dùng nhưng chưa kịp tạo profile đi kèm. Kết quả là database của bạn đầy rác và lỗi logic xuất hiện ở khắp nơi. Để giải quyết triệt để, chúng ta cần một cơ chế bảo đảm: Hoặc là tất cả cùng thành công, hoặc là không có gì thay đổi cả. Nhưng làm sao để dữ liệu không bị "lạc trôi" khi hệ thống bất ngờ gặp sự cố?
Transaction (Giao dịch) là một đơn vị công việc logic chứa một hoặc nhiều câu lệnh SQL, đảm bảo tính toàn vẹn của dữ liệu trong mọi điều kiện.
PostgreSQL Transaction – Người hùng với nguyên tắc "Tất cả hoặc không gì"
PostgreSQL Transaction hoạt động dựa trên một nguyên tắc cực kỳ đơn giản: Bạn mở một phiên làm việc, thực hiện bao nhiêu thay đổi tùy thích, nhưng những thay đổi đó chỉ được "khắc lên đá" khi bạn ra lệnh COMMIT. Nếu có bất kỳ lỗi nào xảy ra giữa chừng, bạn chỉ cần gọi lệnh ROLLBACK, và database sẽ quay về trạng thái y hệt như lúc bạn chưa bắt đầu. Giống như bạn có một nút "Undo" quyền năng cho mọi thao tác dữ liệu vậy.
Để trở thành một chuyên gia Backend thực thụ, bạn không thể không biết đến 4 chữ vàng ACID – tiêu chuẩn khắt khe mà PostgreSQL tuân thủ tuyệt đối:
- Atomicity (Tính nguyên tử): Đảm bảo tất cả các lệnh trong Transaction được thực thi như một khối duy nhất. Một lệnh hỏng, cả khối hỏng.
- Consistency (Tính nhất quán): Dữ liệu luôn chuyển từ trạng thái hợp lệ này sang trạng thái hợp lệ khác theo đúng quy tắc.
- Isolation (Tính cô lập): Các Transaction chạy đồng thời không được "nhìn trộm" hay làm phiền nhau.
- Durability (Tính bền vững): Khi đã lưu thành công, dữ liệu sẽ không bao giờ mất, kể cả khi mất điện đột ngột.
Nghe thì có vẻ lý thuyết, nhưng chính nhờ ACID mà cách PostgreSQL Index hoạt động hay các truy vấn phức tạp của bạn mới có được sự tin cậy tuyệt đối. Nhưng Transaction mạnh đến đâu mà được coi là "tấm khiên" bảo vệ database tối thượng như vậy?
Cách triển khai Transaction cực mượt trong Node.js
Khi làm việc với Node.js, đặc biệt là thư viện pg, việc quản lý Transaction đòi hỏi sự tỉ mỉ. Bạn không nên dùng chung một pool cho toàn bộ quá trình vì Transaction cần một kết nối (client) cố định để giữ trạng thái từ lúc BEGIN đến khi COMMIT.
const { Pool } = require('pg');
const pool = new Pool();
async function transferMoney(fromId, toId, amount) {
const client = await pool.connect(); // Lấy một kết nối riêng
try {
await client.query('BEGIN'); // Bắt đầu Transaction
const subtractQuery = 'UPDATE accounts SET balance = balance - $1 WHERE id = $2';
await client.query(subtractQuery, [amount, fromId]);
const addQuery = 'UPDATE accounts SET balance = balance + $1 WHERE id = $2';
await client.query(addQuery, [amount, toId]);
await client.query('COMMIT'); // Chốt đơn, lưu mọi thay đổi
console.log('Chuyển tiền thành công rực rỡ!');
} catch (e) {
await client.query('ROLLBACK'); // Có biến! Quay xe ngay lập tức
console.error('Lỗi rồi, đã hoàn tác mọi thay đổi:', e);
} finally {
client.release(); // Trả kết nối về cho pool để người khác dùng
}
}
Việc kết hợp Transaction với cách Error Handling chuẩn chỉnh sẽ giúp server của bạn trở nên vô cùng lỳ lợm. Bạn sẽ không còn phải thức đêm để đi sửa từng dòng dữ liệu bị lệch do lỗi code nữa. Vậy thực tế khi code Node.js, chúng ta nên "múa" Transaction như thế nào để vừa an toàn vừa không làm chậm hệ thống?
Đừng bao giờ quên lệnh ROLLBACK trong block catch, nếu không kết nối sẽ bị treo và dẫn đến cạn kiệt tài nguyên hệ thống.
Sử dụng Transaction giống như việc bạn mua bảo hiểm cho dữ liệu của mình. Có thể bình thường bạn thấy nó hơi rườm rà, nhưng đến lúc gặp sự cố, bạn sẽ thầm cảm ơn vì đã áp dụng nó. Bạn có thể tham khảo thêm tại tài liệu chính thức của PostgreSQL để hiểu sâu hơn về các mức độ cô lập (Isolation Levels). Transaction là cứu cánh, nhưng nếu lạm dụng hoặc giữ Transaction quá lâu, hiệu năng (Performance) của database sẽ bị ảnh hưởng như thế nào? Chúng ta sẽ cùng mổ xẻ vấn đề này ở bài viết tới tại DIA DEMY nhé!




Vui lòng đăng nhập để bình luận.