Giao dịch là cách gói nhiều thay đổi dữ liệu thành một khối duy nhất: hoặc tất cả cùng thành công, hoặc không có thay đổi nào xảy ra. Nó ngăn dữ liệu rơi vào trạng thái nửa vời, kiểu tiền đã trừ mà đơn hàng chưa được tạo. Một giao dịch tốt thì ngắn, và không chứa những việc chậm như gọi tới dịch vụ bên ngoài.
Chào bạn, đây là Nhà Lập Trình. Giao dịch là một trong những ý tưởng đẹp nhất của cơ sở dữ liệu, và cũng là một trong những thứ người mới hay bỏ qua cho tới khi mất dữ liệu lần đầu. Tập này mình kể nó bằng ví dụ chuyển tiền quen thuộc, rồi đi tới những cái bẫy ít người nói.
Chuyển tiền giữa hai tài khoản
Ví dụ kinh điển: chuyển một khoản tiền từ tài khoản của bạn sang tài khoản của một người khác. Trong cơ sở dữ liệu, đó là hai thay đổi: trừ tiền ở tài khoản bạn, cộng tiền vào tài khoản người kia.
Giờ giả sử máy chủ mất điện đúng lúc vừa trừ xong tiền của bạn, chưa kịp cộng cho người kia. Tiền biến mất khỏi thế giới. Hoặc ngược lại, cộng rồi mà chưa trừ, tiền tự sinh ra. Cả hai đều là thảm hoạ.
Giao dịch giải quyết điều này. Bạn nói với cơ sở dữ liệu: bắt đầu giao dịch, trừ tiền, cộng tiền, xác nhận. Cho tới khi bạn xác nhận, mọi thay đổi chỉ là tạm. Nếu có gì hỏng giữa chừng, dù là mất điện, lỗi chương trình hay bạn tự huỷ, cơ sở dữ liệu hoàn tác mọi thay đổi tạm, như thể chưa có gì xảy ra.
Người trong nghề nói giao dịch có tính nguyên tử: không chia nhỏ được. Hoặc trọn vẹn, hoặc không có.
Không chỉ ngân hàng mới cần
Nghe ví dụ chuyển tiền, nhiều người nghĩ giao dịch chỉ dành cho ngân hàng. Thật ra gần như mọi phần mềm có thao tác thay đổi nhiều bảng đều cần.
Tạo một đơn hàng thường gồm: thêm dòng vào bảng đơn, thêm các dòng hàng vào bảng chi tiết đơn, trừ tồn kho từng mặt hàng, ghi nhận khoản thanh toán. Nếu dừng giữa chừng, bạn có đơn không có hàng, hoặc kho bị trừ mà không có đơn nào.
Chuyển một học sinh từ lớp này sang lớp khác: bớt ở lớp cũ, thêm ở lớp mới. Duyệt một phiếu nhập kho: đổi trạng thái phiếu, cộng tồn kho, ghi công nợ nhà cung cấp.
Bất cứ khi nào một thao tác nghiệp vụ gồm nhiều thay đổi dữ liệu phải đi cùng nhau, hãy đặt chúng trong một giao dịch. Câu hỏi để tự kiểm tra: nếu chỉ một nửa số thay đổi này xảy ra, dữ liệu có sai không? Nếu có, cần giao dịch.
Giao dịch cũng che chắn người khác
Ngoài chuyện tất cả hoặc không gì cả, giao dịch còn một vai trò nữa: che các thay đổi đang dở khỏi người khác. Trong khi giao dịch của bạn đang chạy, người khác đọc dữ liệu sẽ không thấy trạng thái nửa vời của bạn.
Mức che chắn này có thể điều chỉnh, và mỗi cơ sở dữ liệu có mặc định khác nhau. Che chắn càng chặt, dữ liệu càng nhất quán, nhưng các giao dịch phải chờ nhau nhiều hơn và hệ thống chậm hơn. Che chắn lỏng thì nhanh, nhưng có thể gặp những hiện tượng lạ như đọc cùng một dòng hai lần trong một giao dịch mà ra hai kết quả.
Với phần lớn ứng dụng thông thường, mức mặc định là đủ. Nhưng khi làm những chức năng nhạy cảm như tồn kho, số dư, đặt chỗ, hãy đọc kỹ cơ sở dữ liệu của bạn che chắn tới đâu, và có cần khoá thêm những dòng đang đọc để sửa không.
Đây là phần khó của giao dịch, và nhiều lập trình viên làm nhiều năm vẫn chưa hiểu hết. Không sao, chỉ cần biết nó tồn tại và biết lúc nào cần tìm hiểu kỹ.
Giao dịch phải ngắn
Một giao dịch đang mở thường giữ khoá trên những dòng nó đã sửa. Các giao dịch khác muốn sửa cùng những dòng đó phải chờ. Giao dịch càng dài, người khác càng phải chờ lâu.
Lỗi hay gặp là đặt vào trong giao dịch những việc chậm. Gọi tới cổng thanh toán, gửi thư, tạo tệp báo cáo, xử lý ảnh. Mỗi việc mất vài giây, có khi treo cả phút nếu dịch vụ bên ngoài không trả lời. Suốt thời gian đó, các dòng dữ liệu bị khoá, người dùng khác bị treo theo.
Tệ hơn, những việc bên ngoài không hoàn tác được. Giao dịch huỷ thì dữ liệu quay lại, nhưng thư đã gửi thì không lấy lại được, tiền đã trừ qua cổng thanh toán thì không tự hoàn.
Nguyên tắc là giao dịch chỉ chứa những thay đổi dữ liệu cần đi cùng nhau, làm thật nhanh rồi xác nhận. Những việc bên ngoài làm trước hoặc sau giao dịch, với cơ chế riêng để xử lý khi thất bại, như ghi lại trạng thái chờ và thử lại sau.
Những lỗi hay gặp với giao dịch
Lỗi thứ nhất là quên giao dịch hoàn toàn, mỗi thay đổi tự xác nhận riêng. Chạy tốt cho tới ngày có lỗi giữa chừng. Lỗi thứ hai là bắt lỗi rồi nuốt đi mà không huỷ giao dịch, khiến một phần thay đổi vẫn được xác nhận.
Lỗi thứ ba là giao dịch lồng nhau ngoài ý muốn. Hàm A mở giao dịch, gọi hàm B, hàm B cũng mở giao dịch. Tuỳ cơ sở dữ liệu và thư viện, điều này có thể báo lỗi, có thể bị bỏ qua, có thể xác nhận sớm phần của A. Hãy hiểu rõ thư viện bạn dùng xử lý thế nào.
Lỗi thứ tư là khoá chéo: giao dịch một khoá dòng A rồi chờ dòng B, giao dịch hai khoá dòng B rồi chờ dòng A. Hai bên chờ nhau mãi. Cơ sở dữ liệu thường phát hiện và huỷ một bên. Cách phòng là luôn khoá các dòng theo cùng một thứ tự.
Giao dịch là lưới an toàn của dữ liệu. Dùng đúng, bạn ngủ ngon ngay cả khi máy chủ mất điện giữa đêm. Dùng sai hoặc không dùng, sớm muộn gì bạn cũng có một buổi sáng ngồi đối chiếu từng dòng dữ liệu bằng tay.
Câu hỏi thường gặp
Giao dịch trong cơ sở dữ liệu là gì?
Là một nhóm thay đổi dữ liệu được thực hiện như một khối: hoặc tất cả cùng thành công, hoặc tất cả bị huỷ. Nó giữ cho dữ liệu không rơi vào trạng thái nửa vời khi có sự cố.
Khi nào cần dùng giao dịch?
Khi một thao tác nghiệp vụ gồm nhiều thay đổi dữ liệu phải đi cùng nhau, như tạo đơn và trừ kho, chuyển tiền giữa hai tài khoản, hay duyệt phiếu và ghi công nợ.
Có nên gọi dịch vụ thanh toán bên trong giao dịch không?
Không nên. Gọi dịch vụ ngoài có thể chậm hoặc treo, khiến dữ liệu bị khoá lâu, và thao tác bên ngoài không hoàn tác được khi giao dịch bị huỷ.
Khoá chéo trong cơ sở dữ liệu là gì?
Là khi hai giao dịch chờ nhau, mỗi bên giữ một thứ mà bên kia cần. Cơ sở dữ liệu thường phát hiện và huỷ một bên. Khoá dữ liệu theo cùng một thứ tự giúp phòng tránh.
Giao dịch có làm hệ thống chậm đi không?
Có thể, nếu giao dịch dài hoặc khoá nhiều dữ liệu. Giao dịch ngắn, chỉ chứa những thay đổi cần thiết, thường không gây ảnh hưởng đáng kể.