🚀 Database Internals Series – Hiểu Điều Gì Thực Sự Xảy Ra Bên Trong Một Database
Từ SQL Đến Những Gì Thực Sự Xảy Ra Bên Trong Database
Bạn chạy một câu SQL:
SELECT * FROM users WHERE email = ?;
Database trả kết quả sau 10ms.
Nhìn từ phía application, mọi thứ có vẻ rất đơn giản:
Gửi Query → Database xử lý → Nhận Data.
Nhưng 10ms đó thực sự đã xảy ra chuyện gì?
Database tìm record ở đâu?
Nó đọc toàn bộ Table hay đi qua Index?
Tại sao có Index nhưng đôi khi Database vẫn chọn Full Scan?
Ai quyết định Execution Plan?
Điều gì xảy ra khi hàng trăm Transaction cùng đọc và update một record?
Và nếu Database crash ngay sau khi COMMIT, tại sao dữ liệu vẫn có thể được khôi phục?
Càng làm Backend lâu, mình càng thấy một điều:
Biết SQL là chưa đủ.
Muốn debug những vấn đề thực sự xảy ra ở production, chúng ta cần hiểu Database đang làm gì với SQL của mình.
Đó là lý do mình xây dựng series Database Internals.
1. Database không đơn giản là một cái hộp chứa Data
Khi mới học Backend, chúng ta thường nhìn Database như thế này:
Application
↓
SQL
↓
Database
↓
Data
Application cần dữ liệu thì Query.
Cần lưu dữ liệu thì Insert.
Cần thay đổi thì Update.
Nhưng khi hệ thống bắt đầu lớn hơn, hàng loạt câu hỏi xuất hiện.
Ví dụ:
Query chậm → thêm Index?
Chưa chắc.
Có Index không đồng nghĩa Database sẽ sử dụng Index.
API chậm → Database chậm?
Cũng chưa chắc.
Query có thể chỉ mất 10ms nhưng API mất 300ms vì Connection Pool, Network, Queueing, ORM hoặc Application Logic.
Có Transaction → Data an toàn?
Vẫn chưa đủ.
Bạn còn phải quan tâm đến Isolation Level, Lock, MVCC, Deadlock và những race condition xảy ra khi nhiều Transaction chạy đồng thời.
Thêm Replica → Scale được Database?
Đúng, nhưng bạn vừa tạo thêm một bài toán mới:
Replication Lag.
Một request vừa Write vào Primary nhưng request tiếp theo đọc từ Replica có thể nhận về dữ liệu cũ.
Càng đi sâu, Database càng không còn là một cái hộp đơn giản để lưu Data.
2. Database Internals sẽ đi từ đâu đến đâu?
Mình chia series thành 6 nhóm lớn.
STORAGE & INDEX
↓
QUERY ENGINE
↓
TRANSACTION & CONCURRENCY
↓
DURABILITY & RECOVERY
↓
DISTRIBUTED DATABASE
↓
PRODUCTION PERFORMANCE
Mục tiêu không phải biến Backend Developer thành Database Engineer.
Mục tiêu là hiểu đủ sâu để khi production có vấn đề, chúng ta biết:
Bottleneck thực sự đang nằm ở đâu?
3. Storage & Index — Data thực sự được lưu như thế nào?
Trước khi tối ưu Query, có một câu hỏi cơ bản hơn:
Database thực sự lưu hàng triệu record ở đâu và tìm chúng lại bằng cách nào?
Đây là điểm bắt đầu của series.
P1 — Database Thực Sự Lưu Data Như Thế Nào?
Một câu INSERT không đơn giản là "thêm một dòng vào Table".
Phía dưới còn có Page, Block, Row và cách Storage Engine tổ chức dữ liệu.
👉 https://www.patreon.com/posts/155837694?collection=2115129
P2 — Index: Vì Sao Query Không Cần Scan Toàn Bộ Table
Nếu có 10 triệu User, Database không thể mỗi lần tìm email lại đọc 10 triệu record.
Index tồn tại để giải quyết vấn đề đó.
Nhưng Index không miễn phí.
Read nhanh hơn thường đồng nghĩa với việc Write phải trả thêm chi phí.
👉 https://www.patreon.com/posts/155837935?collection=2115129
P3 — Vì Sao Query Không Dùng Index Trên Table?
Đây mới là phần thú vị.
Bạn đã tạo Index.
Query vẫn Full Scan.
Tại sao?
Bởi vì Database không đơn giản nhìn thấy Index rồi sử dụng nó.
👉 https://www.patreon.com/posts/155838267?collection=2115129
4. Query Engine — SQL bạn viết chưa chắc là thứ Database thực sự chạy
SQL là Declarative.
Bạn nói:
Tôi muốn dữ liệu này.
Nhưng bạn không nói:
Hãy đọc Table A trước, dùng Index B, sau đó Nested Loop với Table C.
Database phải tự quyết định những việc đó.
P4 — Query Execution Plan: Database Quyết Định Chạy Query Ra Sao
Database có thể phải lựa chọn giữa:
Sequential Scan
Index Scan
Nested Loop
Hash Join
Merge Join
...
Một SQL giống nhau nhưng Execution Plan khác nhau có thể tạo ra performance hoàn toàn khác.
👉 https://www.patreon.com/posts/155838491?collection=2115129
P5 — Statistics: Vì Sao Database Đoán Sai Và Query Chậm
Có một vấn đề:
Database chưa chạy Query thì làm sao biết có bao nhiêu record sẽ match?
Nó phải ước lượng.
Statistics giúp Query Optimizer thực hiện việc đó.
Nếu estimate sai, Optimizer có thể chọn một Execution Plan rất tệ.
Đây cũng là lý do có những Query:
Hôm qua chạy rất nhanh.
Hôm nay chẳng sửa code gì nhưng lại chậm.
👉 https://www.patreon.com/posts/155838608?collection=2115129
5. Transaction & Concurrency — Khi nhiều Request cùng chạm vào Data
Hãy tưởng tượng 500 request cùng đọc và ghi Database.
Nếu mọi operation đều khóa nhau:
Throughput sẽ rất thấp.
Nhưng nếu cho tất cả chạy tự do:
Data có thể sai.
Database phải tìm cách cân bằng hai thứ:
Consistency ↔ Concurrency
Đây là lúc MVCC, Transaction, Isolation Level và Lock xuất hiện.
P6 — MVCC: Vì Sao Read Không Block Write
MVCC là một trong những cơ chế quan trọng giúp nhiều Database hiện đại xử lý concurrency.
Reader không nhất thiết phải nhìn thấy đúng cùng một version dữ liệu mà Writer đang thay đổi.
👉 https://www.patreon.com/posts/155838864?collection=2115129
P7 — Transactions: Vì Sao Database Phải Rollback
BEGIN, COMMIT, ROLLBACK rất dễ sử dụng.
Phần khó nằm ở những gì Database phải làm để đảm bảo một business operation không rơi vào trạng thái:
Nửa thành công, nửa thất bại.
👉 https://www.patreon.com/posts/155839020?collection=2115129
P8 — Isolation Levels: Vì Sao Hai Query Nhìn Thấy Hai Thế Giới Khác Nhau
READ COMMITTED.
REPEATABLE READ.
SERIALIZABLE.
Đây không chỉ là ba keyword để học khi phỏng vấn.
Chúng quyết định một Transaction được phép nhìn thấy điều gì khi những Transaction khác đang chạy.
👉 https://www.patreon.com/posts/155839122?collection=2115129
P9 — Deadlock: Vì Sao Database Tự Kill Query
Một case kinh điển:
Transaction A
Lock Row 1
↓
Wait Row 2
Transaction B
Lock Row 2
↓
Wait Row 1
Không ai đi tiếp được.
Database buộc phải phá vòng chờ bằng cách chọn một Transaction làm victim.
👉 https://www.patreon.com/posts/155839233?collection=2115129
6. Durability — COMMIT rồi mất điện thì sao?
Đây là một câu hỏi rất đáng để suy nghĩ:
COMMIT SUCCESS
↓
Server mất điện
Nếu dữ liệu chưa thực sự được ghi hết xuống Disk thì chuyện gì xảy ra?
Database không thể trả SUCCESS rồi sau khi restart lại nói:
Xin lỗi, Data vừa rồi mất rồi.
Đó là lúc Write-Ahead Logging xuất hiện.
P10 — WAL: Vì Sao Database Không Mất Data Khi Crash
WAL là một phần quan trọng trong cách Database đảm bảo Durability và Recovery.
Thay vì chỉ nghĩ:
Database ghi record xuống Disk.
Chúng ta bắt đầu hiểu:
Database phải thiết kế cả một cơ chế để có thể phục hồi trạng thái sau crash.
👉 https://www.patreon.com/posts/155839360?collection=2115129
7. Distributed Database — Khi một Database không còn đủ
Hệ thống nhỏ thường bắt đầu rất đơn giản:
Application
↓
Database
Rồi User tăng.
Traffic tăng.
Data tăng.
Database bắt đầu trở thành bottleneck.
Chúng ta thêm Replica.
Sau đó Sharding.
Và từ thời điểm này:
Database trở thành một Distributed System.
P11 — Replication & Replication Lag: Tại Sao Một Database Là Không Đủ?
Replication giúp scale và tăng availability.
Nhưng đổi lại, các bản sao không phải lúc nào cũng đồng bộ ngay lập tức.
Bạn vừa giải quyết một bài toán và tạo ra một bài toán mới:
Stale Data.
👉 https://www.patreon.com/posts/156827740?collection=2115129
P12 — Sharding: Khi Một Database Không Còn Đủ Gánh Tải
Nếu một Database không thể chứa hoặc xử lý toàn bộ workload:
Chia Data ra nhiều Database.
Nghe đơn giản.
Nhưng ngay sau đó xuất hiện:
- Routing
- Cross-shard Query
- Distributed Transaction
- Rebalancing
- Hot Shard
👉 https://www.patreon.com/posts/157176998?collection=2115129
P13 — Consistency: Khi Dữ Liệu Không Đồng Bộ Trong Hệ Thống Phân Tán
Một record có nhiều bản sao.
Node A nói balance = 100.
Node B nói balance = 90.
Đâu mới là dữ liệu đúng?
👉 https://www.patreon.com/posts/157178231?collection=2115129
P14 — CAP Theorem: Tại Sao Hệ Thống Hoàn Hảo Là Một Cú Lừa?
CAP thường được học dưới dạng:
C — Consistency
A — Availability
P — Partition Tolerance
Nhưng giá trị thực sự của CAP không nằm ở việc nhớ ba chữ này.
Nó nằm ở câu hỏi:
Khi Network Partition thực sự xảy ra, hệ thống của bạn sẽ chọn behavior nào?
👉 https://www.patreon.com/posts/157178609?collection=2115129
P15 — Eventual Consistency: Khi Data Tự Đồng Bộ Sau Một Thời Gian
Không phải dữ liệu nào cũng cần Strong Consistency.
Đôi khi hệ thống chấp nhận:
T0: Node A ≠ Node B
...
T1: Node A = Node B
Sự không đồng bộ tạm thời đó có thể là trade-off để đổi lấy scalability và availability.
👉 https://www.patreon.com/posts/157178926?collection=2115129
8. B-Tree, LSM Tree và câu chuyện phía dưới Storage Engine
Không phải mọi Database đều tổ chức Data giống nhau.
Workload thiên về Read và workload có Write throughput cực lớn có thể cần những thiết kế rất khác.
P16 — B-Tree vs LSM Tree
B-Tree xuất hiện ở rất nhiều relational database.
LSM Tree lại trở thành nền tảng quan trọng của nhiều hệ thống storage hiện đại.
Điểm quan trọng không phải:
Cái nào tốt hơn?
Mà là:
Mỗi cấu trúc đang tối ưu cho workload nào và phải trả trade-off gì?
👉 https://www.patreon.com/posts/157179566?collection=2115129
P17 — Compaction & Vacuum: Vì Sao Database Phải "Dọn Rác"
DELETE FROM users ...
không nhất thiết đồng nghĩa với:
Bytes lập tức biến mất khỏi Disk.
Database cũng phải dọn dẹp dữ liệu cũ.
Và nếu quá trình này không theo kịp workload, nó có thể trở thành một vấn đề production thực sự.
👉 https://www.patreon.com/posts/158507369?collection=2115129
9. Production Performance — Query nhanh chưa chắc API nhanh
Đây là phần mình nghĩ Backend Developer sẽ gặp nhiều nhất.
Bạn mở monitoring:
Database Query: 10ms
API Response: 300ms
290ms còn lại đi đâu?
Đây là lúc phải nhìn rộng hơn câu SQL.
P18 — Connection Pool: Vì Sao Database Không Cho Bạn Kết Nối Tùy Ý?
Nếu Server nhận 5.000 Request cùng lúc:
Có nên mở 5.000 Database Connection?
Không.
Connection cũng là resource hữu hạn.
Và Connection Pool vừa giúp bảo vệ Database, vừa có thể trở thành bottleneck nếu cấu hình hoặc sử dụng sai.
👉 https://www.patreon.com/posts/158511552?collection=2115129
P19 — N+1 Query: Query 2ms Nhưng Gọi 500 Lần
Một Query mất 2ms rất nhanh.
Nhưng:
2ms × 500 queries
lại là một câu chuyện khác.
Đây là kiểu performance issue rất dễ bị che giấu phía sau ORM và application code.
👉 https://www.patreon.com/posts/158512720?collection=2115129
P20 — Vì Sao Query 10ms Lại Khiến API Mất 300ms?
Database latency không chỉ có Query Execution Time.
Request có thể phải đi qua:
Wait Connection
↓
Network
↓
Database
↓
Result Transfer
↓
ORM Mapping
↓
Application Logic
Chỉ nhìn vào thời gian chạy SQL có thể khiến chúng ta tối ưu sai chỗ.
👉 https://www.patreon.com/posts/158577509?collection=2115129
10. Nếu chưa biết bắt đầu từ đâu
Không nhất thiết phải đọc P1 → P20 một mạch.
Nếu muốn hiểu Database từ nền tảng, bắt đầu:
P1 → P5
Storage → Index → Query Optimizer.
Nếu đang gặp Transaction, Lock, Deadlock, Concurrency Bug:
P6 → P10
Nếu đang học System Design / Distributed Systems:
P11 → P16
Nếu công việc hiện tại chủ yếu là debug Database Performance và API Latency:
P17 → P20
Bạn có thể lưu roadmap lại.
Khi production gặp đúng một vấn đề, quay lại đọc đúng phần đó.
11. Điều mình muốn đạt được sau series này
Không phải:
Query chậm → thêm Index.
Mà là:
Query chậm → vì sao chậm?
Không phải:
Thêm Replica để scale.
Mà là:
Nếu có Replication Lag, business flow nào có thể đọc Stale Data?
Không phải:
Dùng Transaction là an toàn.
Mà là:
Transaction đang chạy với Isolation Level nào và concurrency scenario này có thực sự đúng?
Không phải:
Database trả Query trong 10ms nên Database không có vấn đề.
Mà là:
Request đã mất bao nhiêu thời gian để chờ Connection trước khi Query được chạy?
Đó là sự khác biệt giữa:
Biết sử dụng Database
và
Hiểu Database vận hành như thế nào.
Nếu Backend Internals giúp chúng ta hiểu một Server hoạt động ra sao,
và Distributed Systems giúp chúng ta hiểu nhiều Server phối hợp với nhau như thế nào,
thì Database Internals đi sâu vào thứ gần như mọi Backend System cuối cùng đều phải bảo vệ:
Data.
Không chỉ biết viết SQL.
Hãy hiểu Database đang làm gì với SQL của bạn.
#Database #DatabaseInternals #Backend #SystemDesign #PostgreSQL #MySQL #SQL #SoftwareEngineering #TechCraft
All rights reserved