Database Locks Là Gì? "Bảo Vệ" Dữ Liệu Hay Kẻ Thù Giấu Mặt Của Hệ Thống?
Chào lại anh em Viblo! 👋
Ở bài trước, chúng ta đã cùng nhau mổ xẻ về ACID và nguyên tắc chữ I (Isolation - Tính độc lập). Hôm nay, mình sẽ đào sâu vào chính thứ "vũ khí" mà các Database dùng để đảm bảo cái chữ I đó: Database Locks (Khóa cơ sở dữ liệu).
Nếu bạn từng làm một hệ thống bán vé concert, flash sale Shopee hay đơn giản là ứng dụng nội bộ mà đến giờ cao điểm nó bỗng dưng... quay đều, quay đều rồi văng ra lỗi Timeout hoặc Deadlock, thì 90% là do bạn đang gặp vấn đề với Lock.
Vậy Lock thực chất là gì và làm sao để thuần phục nó? Cùng mình đi vào chi tiết nhé.
Database Lock là gì? Hiểu theo cách "Bình dân học vụ" Bạn cứ tưởng tượng Database như một cái nhà vệ sinh công cộng chỉ có một buồng, và dữ liệu (data) chính là cái bồn cầu. 🚽
Khi có nhiều người (Transactions) cùng muốn sử dụng bồn cầu đó cùng một lúc (Concurrency), nếu không có chốt cửa (Lock), thảm họa sẽ xảy ra (dữ liệu bị ghi đè, sai lệch).
Vì vậy, Database Lock chính là cái chốt cửa. Khi một Transaction bước vào can thiệp dữ liệu, nó sẽ "chốt cửa" lại. Những Transaction khác đến sau phải xếp hàng đứng đợi bên ngoài cho đến khi người bên trong xong việc và mở chốt.
Hai loại "Chốt cửa" phổ biến nhất (Lock Types) Database không chỉ có một loại khóa cứng nhắc. Tùy vào mục đích của bạn là "vào để xem" hay "vào để sửa", nó chia ra hai loại chính:
1. Shared Lock (S-Lock) - Khóa Đọc (Read Lock)
- Quy tắc: "Anh em cùng nhìn thì được, nhưng cấm ai đụng vào."
- Cách hoạt động: Khi Transaction A đặt S-Lock lên một dòng dữ liệu để ĐỌC. Transaction B, C, D đến sau vẫn được quyền đọc (đặt thêm S-Lock). Nhưng nếu Transaction B muốn SỬA (Update/Delete), nó phải đợi tất cả các S-Lock được gỡ bỏ.
- Mục đích: Đảm bảo khi bạn đang đọc báo cáo, không có ông nào lén lút sửa số liệu.
2. Exclusive Lock (X-Lock) - Khóa Ghi (Write Lock)
- Quy tắc: "Một mình tao bao trọn, cấm ai nhìn, cấm ai đụng."
- Cách hoạt động: Khi Transaction A đang SỬA dữ liệu, nó đặt X-Lock. Bất kỳ Transaction nào khác muốn Đọc hay Sửa đều phải đứng đợi.
- Mục đích: Ngăn chặn hoàn toàn việc dữ liệu bị sai lệch khi đang có thay đổi.
Phạm vi của Lock (Lock Granularity) - Khóa cái gì?
Đây là phần quyết định hệ thống của bạn sẽ chạy nhanh như gió hay chậm như rùa:
- Row-level Lock (Khóa dòng): Chỉ khóa đúng cái record đang thao tác. Ví dụ: Sửa thông tin User_ID = 1 thì chỉ khóa mỗi ông đó, các User khác vẫn truy cập bình thường. Đây là mức khóa tối ưu nhất và được dùng phổ biến nhất.
- Table-level Lock (Khóa bảng): Khóa nguyên cả cái bảng Users. Một ông sửa thông tin, hàng ngàn ông khác muốn đọc/sửa bảng đó đều phải đợi. Mức khóa này gây nghẽn cổ chai (bottleneck) cực nặng.
Câu hỏi đau đáu: Làm sao để DB biết nên dùng Row-level hay Table-level? Trả lời: Phụ thuộc vào Index (Chỉ mục). Nếu câu lệnh UPDATE của bạn tìm kiếm theo một cột KHÔNG có Index, Database sẽ phải quét toàn bộ bảng (Table Scan) để tìm dữ liệu, dẫn đến việc nó "hoảng loạn" và khóa luôn cả cái bảng!
Ác mộng mang tên DEADLOCK (Khóa chết)
Đây là con bug khiến nhiều anh em dev vò đầu bứt tai nhất.
Deadlock là gì? Tưởng tượng hai chiếc xe hơi đi vào một con hẻm hẹp ngược chiều nhau. Xe A đợi xe B lùi lại để đi tiếp, xe B lại đợi xe A lùi lại. Cuối cùng không ai nhường ai, cả hai kẹt cứng mãi mãi.
Trong Database:
- Transaction 1: Sửa dữ liệu bảng
Orders(khóa Order), sau đó cần cập nhật bảngProducts. - Transaction 2: Sửa dữ liệu bảng Products (khóa Product), sau đó cần cập nhật bảng Orders.
👉 Hệ quả: T1 đợi T2 nhả Products, T2 đợi T1 nhả Orders. Cơ sở dữ liệu phát hiện ra "bế tắc" này, nó sẽ tự động "giết" (kill) một trong hai Transaction và ném ra lỗi Deadlock.
Đúc kết kinh nghiệm thực chiến từ những đêm rớt mạng
Để không trở thành nạn nhân của Locks, anh em hãy ghim lại những nguyên tắc sinh tồn sau:
-
- Đánh Index đàng hoàng: Như mình đã nói ở trên, một câu lệnh UPDATE/DELETE thiếu Index có thể leo thang thành Table-level Lock và giết chết toàn bộ hệ thống.
-
- Giữ Transaction càng NGẮN càng tốt: Đừng chèn các xử lý logic tốn thời gian (như gọi API bên thứ 3, xử lý hình ảnh...) vào giữa một Transaction. Mở Transaction, thực hiện các câu query Database một cách chớp nhoáng, và Commit ngay lập tức để nhả Lock cho người khác dùng.
-
- Truy cập các bảng theo CÙNG MỘT THỨ TỰ: Để tránh Deadlock (như ví dụ xe trong hẻm), hãy thống nhất rule trong team code: "Luôn luôn update bảng Orders trước, rồi mới tới bảng Products". Khi tất cả các Transaction đi theo cùng một chiều, Deadlock sẽ không bao giờ xảy ra.
-
- Sử dụng Isolation Level hợp lý: Không phải lúc nào cũng cần khóa chặt mọi thứ. Trong nhiều hệ thống (như hiển thị lượt view, lượt like), việc đọc sai số liệu một chút không chết ai, bạn có thể giảm Isolation Level xuống để tăng tốc độ.
Làm việc với Concurrency và DB Locks chưa bao giờ là dễ dàng, nó đòi hỏi tư duy thiết kế cực kỳ cẩn thận. Hi vọng bài viết này cung cấp cho anh em một lớp giáp phòng thủ vững chắc trước những dự án lớn sắp tới.
Anh em có từng dính con bug Deadlock nào "trầm cảm" chưa? Để lại bình luận anh em mình cùng mổ xẻ nhé! Happy Coding! 💻🔥
All rights reserved