1. Transaction là gì?
Transaction (giao dịch) là một nhóm thao tác trên database được xem như một đơn vị xử lý logic. Mục tiêu là bảo đảm những thay đổi liên quan được thực hiện theo cách nhất quán: nếu giao dịch thành công thì các thay đổi cần thiết được xác nhận; nếu gặp lỗi khiến giao dịch không thể hoàn thành, các thay đổi chưa commit sẽ được rollback.
Transaction đặc biệt quan trọng khi một nghiệp vụ cần thay đổi nhiều bản ghi hoặc nhiều bảng nhưng không được phép để dữ liệu chỉ cập nhật một phần.
Ví dụ, khi chuyển 500.000 đồng từ tài khoản A sang tài khoản B, hệ thống cần thực hiện hai thao tác: trừ tiền của A và cộng tiền cho B. Nếu chỉ trừ tiền A thành công nhưng thao tác cộng tiền B thất bại, dữ liệu sẽ không còn phản ánh đúng giao dịch.
2. Khi nào cần sử dụng Transaction?
Không phải mọi câu SQL đều cần được nhóm vào một transaction tường minh. Nếu chỉ lấy danh sách sản phẩm bằng một câu SELECT, application thường không cần tự mở transaction nhiều câu lệnh.
Transaction trở nên quan trọng khi một nghiệp vụ gồm nhiều thao tác mà kết quả của chúng phải nhất quán với nhau.
Ví dụ, khi tạo đơn hàng, application có thể phải tạo bản ghi trong orders, thêm nhiều bản ghi order_items, giảm số lượng tồn kho và ghi nhận trạng thái liên quan. Nếu orders đã được tạo nhưng một thao tác quan trọng phía sau thất bại, hệ thống có thể lưu lại một đơn hàng chưa hoàn chỉnh.
Đưa các thao tác database liên quan vào cùng một transaction giúp application commit toàn bộ phần thay đổi cần thiết hoặc rollback khi phát hiện lỗi.
3. BEGIN, COMMIT và ROLLBACK
Ba thao tác thường gặp nhất khi làm việc với transaction là BEGIN, COMMIT và ROLLBACK.
BEGIN bắt đầu một transaction tường minh. Các câu lệnh SQL tiếp theo thuộc về transaction đó cho đến khi transaction kết thúc.
COMMIT xác nhận transaction. Nếu transaction thành công và commit được database chấp nhận, những thay đổi của nó được ghi nhận như một giao dịch hoàn tất.
ROLLBACK hủy các thay đổi chưa commit trong transaction hiện tại. Nó thường được dùng khi một thao tác thất bại hoặc khi application phát hiện điều kiện nghiệp vụ không được đáp ứng.
Ví dụ:
BEGIN;
UPDATE accounts
SET balance = balance - 500000
WHERE id = 1;
UPDATE accounts
SET balance = balance + 500000
WHERE id = 2;
COMMIT;
Đây là ví dụ minh họa cho một giao dịch chuyển tiền. Nếu cần hủy giao dịch trước khi commit, có thể thực hiện:
ROLLBACK;
4. ACID là gì?
ACID là bốn thuộc tính mô tả những bảo đảm quan trọng của transaction: Atomicity, Consistency, Isolation và Durability.
Bốn thuộc tính này giải quyết bốn vấn đề khác nhau. Atomicity bảo đảm giao dịch không bị hoàn thành một phần; Consistency liên quan đến việc duy trì các quy tắc hợp lệ của dữ liệu; Isolation kiểm soát tương tác giữa các transaction đồng thời; Durability bảo đảm tính bền vững của những thay đổi đã commit theo các bảo đảm mà database và cấu hình vận hành cung cấp.
Atomicity – Tính nguyên tử
Atomicity có thể hiểu là tất cả hoặc không gì cả đối với những thay đổi thuộc transaction.
Quay lại ví dụ chuyển tiền, giao dịch gồm hai thay đổi:
- Trừ 500.000 đồng khỏi tài khoản A.
- Cộng 500.000 đồng vào tài khoản B.
Nếu thao tác đầu thành công nhưng thao tác thứ hai thất bại, transaction cần được rollback để tránh tình trạng tiền bị trừ khỏi A nhưng không được cộng vào B.
Atomicity giúp application không phải tự xây dựng cơ chế hoàn tác thủ công cho từng câu lệnh trong một giao dịch database thông thường.
Consistency – Tính nhất quán
Consistency có nghĩa là transaction phải đưa database từ một trạng thái hợp lệ sang trạng thái hợp lệ khác theo những quy tắc và ràng buộc đã xác định.
Ví dụ, nếu số lượng sản phẩm trong kho không được âm, hệ thống phải bảo vệ quy tắc đó khi tạo đơn hàng. Nếu mỗi email phải duy nhất, database cần có unique constraint tương ứng để ngăn những bản ghi vi phạm quy tắc.
Cần hiểu rằng database không tự biết toàn bộ quy tắc nghiệp vụ của application. Consistency được bảo vệ bởi sự kết hợp giữa database constraints, transaction và logic nghiệp vụ được triển khai đúng cách.
Isolation – Tính cô lập
Isolation liên quan đến cách các transaction tương tác với nhau khi nhiều user hoặc request thao tác database cùng lúc.
Giả sử kho hàng chỉ còn một sản phẩm. Hai request cùng lúc kiểm tra tồn kho và đều nhận được kết quả là còn một sản phẩm. Nếu cả hai tiếp tục tạo đơn hàng mà không có cơ chế kiểm soát thích hợp, hệ thống có thể bán vượt số lượng thực tế.
Đây là vấn đề concurrency. Mỗi transaction có thể riêng lẻ trông hợp lý, nhưng khi nhiều transaction chạy đồng thời, kết quả kết hợp của chúng có thể vi phạm quy tắc nghiệp vụ.
Durability – Tính bền vững
Durability nghĩa là những thay đổi đã commit thành công được giữ lại theo các bảo đảm lưu trữ và phục hồi mà database cung cấp, kể cả khi xảy ra sự cố như tiến trình bị dừng hoặc server gặp crash trong những tình huống được hỗ trợ.
Ví dụ, khi hệ thống xác nhận một giao dịch ngân hàng đã thành công, application cần tin rằng giao dịch đó sẽ không biến mất chỉ vì database khởi động lại sau đó.
5. Locking và Race Condition trong Database
Transaction không tự động loại bỏ mọi race condition. Nếu hai request cùng đọc một giá trị rồi cập nhật dựa trên kết quả đó, dữ liệu vẫn có thể sai nếu application không sử dụng cơ chế đồng bộ phù hợp.
Ví dụ, số lượng tồn kho ban đầu là 1. Hai request cùng đọc thấy còn một sản phẩm, rồi mỗi request đều quyết định đặt mua. Nếu chúng không phối hợp đúng cách, cả hai có thể cố hoàn tất đơn hàng trong khi thực tế chỉ có đủ hàng cho một đơn.
Có hai cách tiếp cận thường gặp để xử lý loại tình huống này.
Cập nhật có điều kiện
Thay vì chỉ đọc số lượng rồi cập nhật sau, application có thể dùng một câu lệnh cập nhật có điều kiện:
UPDATE products
SET stock = stock - 1
WHERE id = 10
AND stock >= 1;
Sau đó, kiểm tra số dòng đã được cập nhật. Nếu đúng một row bị thay đổi, việc trừ tồn kho thành công. Nếu không có row nào được cập nhật, sản phẩm có thể không tồn tại hoặc không còn đủ hàng, và application phải xử lý tình huống đó phù hợp.
Row Locking
Trong những nghiệp vụ cần đọc một bản ghi rồi quyết định thay đổi dựa trên dữ liệu đó, có thể dùng locking read như SELECT ... FOR UPDATE trong các hệ quản trị hỗ trợ phù hợp.
Row lock giúp các transaction cạnh tranh trên cùng bản ghi phải phối hợp theo cơ chế khóa, thay vì tự do sửa cùng dữ liệu cùng lúc. Tuy nhiên, locking cần được sử dụng cẩn thận vì có thể gây lock wait hoặc deadlock.
6. Deadlock và Retry Transaction
Deadlock xảy ra khi những transaction giữ các lock khác nhau và chờ nhau theo cách không thể tiếp tục. Ví dụ, transaction A giữ khóa của tài khoản 1 rồi chờ khóa tài khoản 2, trong khi transaction B giữ khóa tài khoản 2 rồi chờ tài khoản 1.
Database thường có cơ chế phát hiện deadlock và chọn một transaction làm nạn nhân để rollback, qua đó cho những transaction còn lại tiếp tục.
Một cách hạn chế deadlock là luôn cập nhật các tài nguyên theo một thứ tự nhất quán nếu nghiệp vụ cho phép. Ví dụ, khi phải khóa nhiều tài khoản, có thể chọn thứ tự theo ID thay vì mỗi request tự khóa theo thứ tự ngẫu nhiên.
Tuy nhiên, tránh deadlock hoàn toàn không phải lúc nào cũng khả thi. Khi cần retry, phải xác định loại lỗi có thể retry và giới hạn số lần thử. Không được tự động retry mọi lỗi mà không phân biệt, bởi lỗi validation hoặc thiếu tiền không thể giải quyết bằng cách thử lại ngay lập tức.
7. Tổng kết
Transactions & ACID là nền tảng để bảo đảm những thay đổi dữ liệu liên quan được xử lý đúng cách trong database.
Transaction gom nhiều câu lệnh thành một đơn vị logic. BEGIN bắt đầu giao dịch, COMMIT xác nhận những thay đổi và ROLLBACK hủy các thay đổi chưa commit khi giao dịch không thể hoàn thành.
ACID gồm bốn thuộc tính:
- Atomicity: Các thay đổi thuộc transaction được xử lý theo nguyên tắc tất cả hoặc không gì cả.
- Consistency: Những quy tắc và ràng buộc của dữ liệu cần được duy trì qua transaction.
- Isolation: Các transaction đồng thời tương tác theo mức cô lập và locking được database hỗ trợ.
- Durability: Những thay đổi đã commit được bảo vệ theo cơ chế lưu trữ và phục hồi của hệ thống.
Trong PostgreSQL và MySQL, cách transaction hoạt động chi tiết có thể khác nhau, đặc biệt ở isolation level mặc định và hành vi locking. Vì vậy, khi viết backend với Go hoặc Gin, cần hiểu cả transaction lẫn cách database cụ thể xử lý concurrency.







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




