Explore all courses in this category.
Explore all courses in this category.
Nguyễn Đình Cường
0 comment
09 Nov, 2026
Chuyển 100 nghìn từ tài khoản A sang B gồm hai bước: trừ tiền A và cộng tiền B. Nếu hệ thống sập giữa hai bước, tiền biến mất. Transaction ra đời để bảo đảm một nhóm thao tác hoặc hoàn thành trọn vẹn, hoặc như chưa từng xảy ra.
Bạn bắt đầu transaction, thực hiện các câu lệnh, rồi chốt (COMMIT) nếu mọi thứ ổn hoặc hủy (ROLLBACK) nếu có lỗi:
START TRANSACTION;UPDATE accounts SET balance = balance - 100000 WHERE id = 1;UPDATE accounts SET balance = balance + 100000 WHERE id = 2;COMMIT; -- hoặc ROLLBACK nếu có sự cốTrong MySQL, bảng cần dùng engine hỗ trợ transaction như InnoDB (mặc định hiện nay).
Khi nhiều người cùng thao tác, có thể xảy ra:
Mức cô lập (isolation level) quyết định bạn chấp nhận hiện tượng nào, đổi lại là hiệu năng. Bốn mức theo chuẩn SQL gồm Read Uncommitted, Read Committed, Repeatable Read và Serializable; mức càng cao càng an toàn nhưng càng hạn chế đồng thời. MySQL InnoDB mặc định dùng Repeatable Read, còn PostgreSQL mặc định Read Committed.
Tình huống kinh điển: hai người cùng mua món hàng chỉ còn một cái. Nếu cả hai đọc "còn 1" rồi cùng trừ, bạn bán lố. Giải pháp là khóa dòng khi đọc:
START TRANSACTION;SELECT stock FROM products WHERE id = 7 FOR UPDATE;-- kiểm tra stock > 0 ở tầng ứng dụngUPDATE products SET stock = stock - 1 WHERE id = 7;COMMIT;FOR UPDATE khiến transaction khác muốn sửa dòng này phải chờ. Cách khác gọn hơn là kiểm tra điều kiện ngay trong câu cập nhật:
UPDATE products SET stock = stock - 1 WHERE id = 7 AND stock > 0;-- kiểm tra số dòng bị ảnh hưởng: 0 nghĩa là hết hàng// LaravelDB::transaction(function () use ($from, $to, $amount) { $from->decrement('balance', $amount); $to->increment('balance', $amount);});// Djangofrom django.db import transactionwith transaction.atomic(): ...Nếu có ngoại lệ ném ra trong khối, framework tự rollback. Hãy giữ transaction ngắn: không gọi API bên ngoài, không chờ người dùng bên trong, vì giữ khóa lâu làm nghẽn cả hệ thống.
Deadlock xảy ra khi hai transaction mỗi bên giữ khóa mà bên kia cần. Database phát hiện và hủy một trong hai với lỗi. Cách giảm thiểu: luôn khóa các tài nguyên theo cùng một thứ tự, giữ transaction ngắn, và viết code thử lại khi gặp lỗi deadlock.
Transaction truyền thống chỉ bảo đảm trong phạm vi một database. Khi nghiệp vụ trải qua nhiều dịch vụ hoặc nhiều database, ví dụ trừ kho ở một dịch vụ, tính tiền ở dịch vụ khác, bạn không thể dùng một lệnh COMMIT chung. Lúc đó người ta dùng các mẫu như saga, trong đó mỗi bước có một thao tác bù trừ để hoàn tác nếu bước sau thất bại, hoặc chấp nhận nhất quán cuối cùng (eventual consistency). Đây là chủ đề nâng cao, nhưng chỉ cần biết giới hạn của transaction đơn đã giúp bạn thận trọng khi chia nhỏ hệ thống thành microservice.
Một mẹo thực tế nữa là luôn kiểm thử các tình huống đồng thời. Viết một kịch bản chạy hai tiến trình cùng mua món hàng cuối cùng, và xác nhận chỉ một bên thành công. Những lỗi liên quan đến đua dữ liệu hiếm khi lộ ra khi bạn tự bấm thử từng bước, nhưng sẽ xuất hiện ngay khi có hàng trăm người dùng thật.
Bất kỳ khi nào một nghiệp vụ gồm nhiều bước ghi liên quan nhau (đặt hàng, thanh toán, chuyển tiền), hãy bọc trong transaction. Đồng thời hiểu mức cô lập và khóa dòng để xử lý các tình huống đồng thời. Đó là nền tảng của dữ liệu đáng tin cậy.
Nếu muốn luyện tập cùng bài tập thực tế, hãy ghé các khóa học tại codebem.com khi bạn sẵn sàng.
Nguyễn Đình Cường
0 comment