Giới thiệu & Khái niệm RDBMSIntroduction & RDBMS Concepts
Thuộc bộ kiến thức PostgreSQL DBA Roadmap.
Tổng quan
Một RDBMS (Relational Database Management System — hệ quản trị cơ sở dữ liệu quan hệ) lưu trữ dữ liệu dưới dạng tập hợp các bảng (table) — lưới gồm các hàng (row) và cột (column) — và cho phép bạn liên kết bảng này với bảng khác thông qua các giá trị khóa dùng chung, thay vì thông qua con trỏ hay lồng ghép vật lý. Bạn không cần chỉ cho RDBMS biết cách tìm dữ liệu (đi theo con trỏ này, rồi con trỏ kia); bạn chỉ cần nói điều bạn muốn bằng một ngôn ngữ khai báo — SQL (Structured Query Language) — và bộ tối ưu truy vấn (query planner) của database sẽ tự tìm ra cách nhanh nhất để lấy dữ liệu đó. Sự tách biệt giữa “cái gì” và “làm thế nào” này là lý do lớn nhất khiến mô hình quan hệ thống trị việc lưu trữ dữ liệu nghiệp vụ quan trọng suốt hơn năm mươi năm qua: code ứng dụng có thể thay đổi liên tục, nhưng query planner vẫn có thể được viết lại, nâng cấp và tối ưu hóa lại mà không ai phải động vào một câu lệnh SELECT nào.
Database không phải lúc nào cũng hoạt động theo cách này. Trước mô hình quan hệ của Codd (1970), hai cách tiếp cận thống trị là mô hình phân cấp (hierarchical model) (IMS của IBM, 1966) — dữ liệu được tổ chức thành cây nghiêm ngặt, mỗi bản ghi chỉ có đúng một bản ghi cha, giống cấu trúc filesystem — và mô hình mạng (network model) (CODASYL, cuối thập niên 1960) — một đồ thị các bản ghi kết nối bằng con trỏ tường minh, linh hoạt hơn cây nhưng vẫn phải duyệt bằng code đi theo con trỏ. Cả hai đều yêu cầu lập trình viên ứng dụng phải biết bố cục vật lý của dữ liệu và viết code mệnh lệnh (imperative) để đi qua từng liên kết vật lý, từng bản ghi một. Tổ chức lại cấu trúc vật lý — thêm một đường truy cập mới, đổi chuỗi con trỏ — đồng nghĩa phải viết lại mọi chương trình có động đến nó. Điểm mấu chốt trong ý tưởng của Codd là tách biệt cấu trúc logic của dữ liệu (các quan hệ, được định nghĩa bởi cột và ràng buộc) khỏi cách lưu trữ vật lý và đường truy cập, đồng thời để ngôn ngữ truy vấn thể hiện ý định thay vì thao tác điều hướng. Sự tách biệt đó chính là lý do mô hình quan hệ chiến thắng: schema có thể tiến hóa, storage engine có thể được tối ưu, index có thể được thêm vào — tất cả mà không phá vỡ các ứng dụng xây dựng bên trên, miễn là hình dạng logic của dữ liệu (schema) vẫn tương thích.
PostgreSQL là một cách hiện thực hóa mô hình này — nhưng là một cách hiện thực hóa đầy tham vọng. Nó khởi đầu là POSTGRES, một dự án nghiên cứu tại UC Berkeley do Michael Stonebraker dẫn dắt (cũng chính là người đứng sau Ingres, một trong những hệ quản trị quan hệ sớm nhất), được thiết kế rõ ràng để vượt ra ngoài mô hình quan hệ của thập niên 1980 bằng cách hỗ trợ kiểu dữ liệu tự định nghĩa (user-defined type), rule, và khả năng mở rộng. Khi hỗ trợ SQL được thêm vào năm 1995, dự án được đổi tên thành Postgres95, và đến năm 1996 trở thành PostgreSQL dưới sự quản trị cộng đồng mã nguồn mở như hiện nay. Chính “gen” nghiên cứu đó — “để người dùng mở rộng hệ thống kiểu dữ liệu và cả bản thân database” — vẫn là đặc điểm định hình phân biệt PostgreSQL với MySQL, Oracle, và SQL Server ngày nay, và đó cũng là lý do PostgreSQL có thể vừa là một engine quan hệ tuân thủ chuẩn nghiêm ngặt, vừa là nền tảng cho những thứ như lưu trữ document JSON đầy đủ hay tìm kiếm tương đồng vector, mà không cần rời khỏi cùng một hệ thống.
Bài viết này là điểm khởi đầu của roadmap PostgreSQL DBA: nó thiết lập nền tảng khái niệm (mô hình quan hệ, ACID, OLTP/OLAP) mà mọi chủ đề sau này — cài đặt, thiết kế schema, indexing, transaction, replication, bảo mật — đều xây dựng dựa trên đó.
Kiến thức nền tảng
Mô hình quan hệ, một cách chính thức
Mô hình quan hệ của Codd đặt tên chính xác, mang tính toán học cho những gì hầu hết mọi người đã hình dung là “một bảng”:
| Thuật ngữ thông thường | Thuật ngữ chính thức | Định nghĩa |
|---|---|---|
| Bảng (table) | Quan hệ (relation) | Một tập hợp các tuple có tên, tất cả cùng chia sẻ chung các thuộc tính (cột) |
| Hàng / bản ghi | Tuple | Một tập giá trị có thứ tự duy nhất, mỗi giá trị ứng với một thuộc tính, trong một quan hệ |
| Cột / trường | Thuộc tính (attribute) | Một thành phần có tên, có kiểu của tuple (ví dụ: email TEXT) |
| Cấu trúc bảng | Schema (của quan hệ) | Tập tên thuộc tính và miền giá trị (domain/type) của chúng |
| Miền giá trị | Domain | Tập các giá trị hợp lệ mà một thuộc tính có thể nhận (ví dụ: INTEGER, BOOLEAN) |
Hai đảm bảo sau xuất phát trực tiếp từ việc coi một quan hệ như một tập hợp (set) toán học của các tuple, và chúng có ý nghĩa thực tiễn, không chỉ là lý thuyết:
- Không có tuple trùng lặp. Một tập hợp không thể chứa hai phần tử giống hệt nhau, nên trong mô hình quan hệ thuần túy, một quan hệ không thể chứa hai hàng hoàn toàn giống nhau. Trong các engine SQL thực tế — kể cả PostgreSQL — bảng có thể chứa các hàng trùng lặp về mặt vật lý trừ khi bạn thêm ràng buộc
PRIMARY KEYhoặcUNIQUE, bởi vì bảng SQL về mặt kỹ thuật là multiset (bag), không phải quan hệ toán học thuần túy. Đây là một sự nới lỏng thực tiễn có chủ đích, nhưng nó có nghĩa là tính duy nhất là thứ bạn phải khai báo, không tự nhiên mà có — một trong những điều đầu tiên một DBA nên kiểm tra trên bất kỳ bảng nào không có khóa tự nhiên rõ ràng. - Thuộc tính không có thứ tự (và về nguyên tắc, tuple cũng vậy). Một quan hệ không có thứ tự hàng hay thứ tự cột nội tại;
{name, age}và{age, name}mô tả cùng một quan hệ. Đây là lý doSELECT *trả về cột theo thứ tự định nghĩa bảng chỉ là một sự tiện lợi, không phải một đảm bảo, và tại sao dựa vào thứ tự hàng vật lý mà không cóORDER BYlà một lỗi logic tiềm ẩn, chỉ chờ lộ ra sau khiVACUUM FULLhay việc rebuild index tiếp theo làm thay đổi bố cục vật lý.
Các quan hệ kết nối với nhau thông qua khóa (key): một khóa chính (primary key) xác định duy nhất mỗi tuple trong chính quan hệ của nó, và một khóa ngoại (foreign key) trong một quan hệ tham chiếu đến khóa chính (hoặc khóa duy nhất) trong quan hệ khác, mã hóa một mối quan hệ mà không cần nhân bản dữ liệu. Đây là điều cho phép bạn tách orders và customers thành các bảng riêng biệt mà vẫn có thể tái tạo lại “khách hàng nào đặt đơn hàng nào” — thông qua join — thay vì nhân bản dữ liệu khách hàng vào mọi hàng đơn hàng, và cho phép database thực thi tính toàn vẹn tham chiếu (referential integrity) (bạn không thể chèn một đơn hàng cho khách hàng không tồn tại) như một đảm bảo hạng nhất thay vì một quy ước ở tầng ứng dụng.
Lợi ích và hạn chế của RDBMS
Nền tảng toán học của mô hình quan hệ chuyển hóa thành những lợi ích kỹ thuật cụ thể, nhưng nó cũng tạo ra những ràng buộc thực sự — hiểu cả hai mặt là điều giúp một DBA lựa chọn (hoặc bảo vệ) PostgreSQL cho một workload cụ thể thay vì chọn nó chỉ vì thói quen.
| Lợi ích | Hạn chế |
|---|---|
| Đảm bảo consistency mạnh mẽ thông qua transaction ACID | Horizontal scaling (sharding trên nhiều node) khó hơn so với các hệ NoSQL được xây dựng chuyên biệt cho việc đó |
| Ngôn ngữ truy vấn (SQL) trưởng thành, chuẩn hóa, khả chuyển giữa các hệ thống và kỹ năng | Thay đổi schema đòi hỏi migration tường minh; thêm một cột vào bảng khổng lồ có thể gây lock hoặc rewrite toàn bảng |
| Join khai báo (declarative), mạnh mẽ trên các bảng đã chuẩn hóa | Không phù hợp với dữ liệu ghi cực nhiều, ít cấu trúc (ví dụ: telemetry IoT thô, log phi cấu trúc ở quy mô khổng lồ) |
| Query optimizer khai báo giải phóng developer khỏi việc tự viết đường truy cập dữ liệu | Vertical scaling (máy lớn hơn) là con đường mở rộng mặc định trước khi sharding trở nên cần thiết |
| Hệ sinh thái phong phú: hàng chục năm công cụ, ORM, giám sát, DBA đã quen với mô hình này | Kiểu dữ liệu/ràng buộc chặt chẽ làm tăng chi phí thiết kế schema ban đầu so với các kho lưu trữ schema-on-read |
Không hạn chế nào là chí mạng — chúng đều là đánh đổi. Một schema quan hệ được mô hình hóa tốt cùng index phù hợp sẽ vượt trội hơn một kho NoSQL được dùng một cách ngây thơ, đối với hầu hết ứng dụng transactional. Và bản thân PostgreSQL còn chủ động giảm nhẹ nhiều hạn chế kể trên (JSONB cho dữ liệu bán cấu trúc, logical replication và các extension như Citus cho horizontal scaling), đó là một phần lý do nó trở thành lựa chọn mặc định cho rất nhiều loại workload, thay vì chỉ là một RDBMS chuyên biệt hẹp.
Khái niệm chính
PostgreSQL so với các RDBMS khác
Mọi RDBMS lớn đều triển khai cùng một lõi quan hệ, nhưng chúng khác biệt rõ rệt về khả năng mở rộng, mức độ tuân thủ chuẩn, và kiến trúc concurrency — và những khác biệt đó thường mới là thứ thực sự quyết định bạn chọn hệ nào.
| Khía cạnh | PostgreSQL | MySQL | Oracle | SQL Server | SQLite |
|---|---|---|---|---|---|
| Giấy phép / chi phí | Mã nguồn mở, giấy phép PostgreSQL rất khoáng đạt (miễn phí) | Mã nguồn mở (GPL) + gói Enterprise trả phí | Độc quyền, chi phí license đắt đỏ | Độc quyền, license theo core | Public domain, nhúng (embedded), miễn phí |
| Khả năng mở rộng | Rất cao: kiểu dữ liệu tùy chỉnh, toán tử, index access method, ngôn ngữ thủ tục, extension (pgvector, PostGIS, TimescaleDB) | Hạn chế; storage engine có thể thay (InnoDB, MyISAM) nhưng hệ thống kiểu là đóng | Cao nhưng độc quyền (package PL/SQL) | Trung bình (tích hợp CLR, T-SQL) | Tối thiểu; không nhắm đến việc mở rộng |
| Tuân thủ chuẩn SQL | Rất cao — bám sát chuẩn SQL | Trước đây lỏng lẻo hơn (ép kiểu ngầm, cắt bớt giá trị âm thầm) | Cao, kèm nhiều phần mở rộng độc quyền | Cao, kèm phương ngữ mở rộng T-SQL | Một phần; kiểu dữ liệu động đi lệch khỏi chuẩn |
| Mô hình concurrency | MVCC qua các phiên bản tuple trong heap + vacuum | MVCC trong InnoDB (undo log, gần với cách tiếp cận của Oracle) | MVCC qua undo segment/rollback | Mặc định dựa trên lock, tùy chọn MVCC (snapshot isolation) | Ghi đơn (single-writer), khóa ở mức file |
| Tùy chọn indexing | B-tree, Hash, GiST, SP-GiST, GIN, BRIN — index access method có thể cắm thêm | Chủ yếu B-tree (+ full-text, spatial hạn chế) | B-tree, bitmap, function-based, domain index | B-tree (clustered/non-clustered), columnstore | Chỉ B-tree |
| Trường hợp dùng điển hình | OLTP đa dụng, dữ liệu địa lý, JSON, analytics nhờ extension | Ứng dụng web, workload nặng đọc, thiết lập ưu tiên đơn giản | Doanh nghiệp lớn, hệ thống legacy trọng yếu | Hệ sinh thái doanh nghiệp thiên về Windows/.NET | Nhúng, mobile, lưu trữ cục bộ một file |
Hai điểm khác biệt đáng ghi nhớ nhất trong bảng trên là khả năng mở rộng và cách hiện thực MVCC. Cơ chế extension của PostgreSQL (CREATE EXTENSION) cho phép bạn thêm những kiểu dữ liệu và phương thức index thực sự mới mà không cần fork database — đây chính là cách pgvector (tìm kiếm tương đồng vector) và PostGIS (dữ liệu địa lý) tồn tại như những công dân hạng nhất, thay vì các hack chắp vá thêm vào. Và thiết kế MVCC cụ thể của PostgreSQL — lưu nhiều phiên bản của một hàng ngay trong heap của bảng và dựa vào VACUUM để thu hồi các phiên bản đã chết — đánh đổi một chút overhead lưu trữ/bảo trì (bloat, nhu cầu vacuum) để lấy về một đường đọc đơn giản, không blocking, nơi reader không bao giờ chặn writer và writer không bao giờ chặn reader. Hãy so sánh điều này với mô hình lock mặc định của SQL Server, nơi truy cập đồng thời một cách ngây thơ có thể tạo ra chuỗi blocking mà Postgres, MySQL/InnoDB, hay Oracle sẽ không gặp phải.
PostgreSQL so với các database NoSQL
Ranh giới lịch sử “dùng Postgres cho dữ liệu quan hệ có cấu trúc, dùng kho NoSQL cho dữ liệu linh hoạt/quy mô khổng lồ” đã mờ đi khá nhiều, chủ yếu nhờ hai tính năng của PostgreSQL: JSONB, một kiểu cột JSON nhị phân, có thể index, cho phép bạn lưu và truy vấn các document linh hoạt về schema ngay bên trong một bảng quan hệ thông thường, và các extension như pgvector, biến PostgreSQL thành một vector database khả thi cho embedding và tìm kiếm tương đồng. Giờ đây hoàn toàn có thể chạy một workload kiểu document store, một workload full-text search, và một workload semantic search, tất cả trong cùng một instance PostgreSQL.
Tuy vậy, đánh đổi cơ bản được mô tả trong NoSQL Databases vẫn đúng, và JSONB không xóa bỏ nó — nó chỉ tạo ra một lối thoát bên trong một hệ thống quan hệ. Hãy chọn PostgreSQL (mô hình hóa quan hệ, kể cả khi có cột JSONB) khi:
- Dữ liệu có các thực thể và mối quan hệ rõ ràng, hưởng lợi từ foreign key, join, và tính toàn vẹn tham chiếu.
- Transaction cần tuân thủ ACID trên nhiều lần ghi liên quan đến nhau (ví dụ: chuyển tiền giữa hai tài khoản).
- Bạn cần truy vấn ad-hoc, khai báo (kết hợp
WHERE/JOIN/aggregate tùy ý) thay vì một tập nhỏ các access pattern định trước.
Hãy chọn một kho NoSQL chuyên biệt thay vào đó khi:
- Khối lượng dữ liệu và throughput ghi vượt quá những gì một primary duy nhất (cộng thêm read replica) có thể phục vụ, và bạn cần horizontal scaling gần như tuyến tính, minh bạch, như một mục tiêu thiết kế hạng nhất chứ không phải bổ sung sau (ví dụ: Cassandra, DynamoDB).
- Schema thực sự khó đoán trước và việc truy cập gần như luôn theo một khóa duy nhất hoặc một tập nhỏ pattern đã biết (ví dụ: document store theo khóa document ID), nên bạn có được rất ít lợi ích từ việc mô hình hóa quan hệ.
- Bạn cần một mô hình dữ liệu chuyên biệt — graph database cho việc duyệt quan hệ sâu, wide-column store cho time-series ở quy mô khổng lồ — mà một engine quan hệ hoặc document tổng quát xử lý kém.
Quy tắc thực tế mà hầu hết DBA đi đến: mặc định chọn PostgreSQL cho các workload transactional và đa dụng, và chỉ áp dụng một hệ NoSQL chuyên biệt khi có lý do cụ thể, đã được đo lường (một access pattern hay yêu cầu quy mô cụ thể mà Postgres không đáp ứng được), thay vì một cảm giác mơ hồ rằng “NoSQL scale tốt hơn”.
ACID, xem trước
Mọi transaction quan hệ mà PostgreSQL commit đều được kỳ vọng thỏa mãn ACID: Atomicity (Tính nguyên tử — mọi ghi trong một transaction xảy ra hết hoặc không xảy ra gì cả), Consistency (Tính nhất quán — một transaction đưa database từ trạng thái hợp lệ này sang trạng thái hợp lệ khác, tôn trọng các ràng buộc), Isolation (Tính cô lập — các transaction đồng thời không thấy trạng thái trung gian chưa commit của nhau), và Durability (Tính bền vững — một khi đã commit, dữ liệu sống sót qua sự cố crash). Cơ chế đầy đủ — isolation level, các loại anomaly, locking — được trình bày trong Backend — Relational Databases và sẽ được đào sâu ở Transactions & Concurrency Control; bài viết này chỉ xem trước cách PostgreSQL cụ thể hiện thực hai trong bốn chữ cái đó, vì nó giải thích rất nhiều hành vi đặc trưng của PostgreSQL sẽ gặp lại sau này trong roadmap:
- Durability đến từ Write-Ahead Log (WAL). Trước khi PostgreSQL sửa đổi một trang dữ liệu, nó ghi lại thay đổi đó vào WAL và flush xuống đĩa trước tiên. Nếu server crash, việc replay WAL khi khởi động lại sẽ tái tạo mọi thay đổi đã commit nhưng chưa kịp ghi vào file bảng thực tế. Đây cũng chính là cơ chế cung cấp năng lượng cho physical replication và point-in-time recovery, được trình bày trong Replication & High Availability.
- Isolation đến từ MVCC (Multi-Version Concurrency Control). Thay vì để reader lấy lock chặn writer, PostgreSQL giữ nhiều phiên bản của một hàng (được tạo ra bởi
UPDATE/DELETE) và cấp cho mỗi transaction một “snapshot” nhất quán của dữ liệu tại một thời điểm. Một reader nhìn thấy phiên bản của hàng đã được commit trước khi snapshot của nó được chụp, bất kể writer đồng thời đang sửa đổi chính hàng đó. Đây là lý do trong PostgreSQL,SELECTkhông bao giờ bị chặn bởiUPDATEvà ngược lại — một đặc tính mà nhiều DBA quen với các hệ thống nặng về lock ban đầu thấy bất ngờ.
OLTP so với OLAP so với HTAP
Không phải mọi workload database đều giống nhau, và hình dạng của workload nên quyết định schema, chiến lược indexing, thậm chí cả lựa chọn engine. Ba nhóm rộng sau mô tả phổ này:
| Khía cạnh | OLTP | OLAP | HTAP |
|---|---|---|---|
| Tên đầy đủ | Online Transaction Processing | Online Analytical Processing | Hybrid Transactional/Analytical Processing |
| Truy vấn điển hình | Ngắn, đơn giản (tra cứu/cập nhật một hàng) | Dài, phức tạp (aggregation trên hàng triệu hàng) | Cả hai, trên cùng dữ liệu |
| Khối lượng truy vấn | Rất cao (hàng nghìn/giây) | Thấp đến trung bình | Cao cho phần OLTP, trung bình cho phần analytics |
| Phạm vi dữ liệu mỗi truy vấn | Vài hàng | Quét lớn, thường toàn bảng/partition | Hỗn hợp |
| Bố cục lưu trữ | Hướng hàng (row-oriented — nhanh khi đọc/ghi nguyên hàng) | Hướng cột (column-oriented — nhanh khi quét ít cột trên nhiều hàng) | Thường hỗn hợp hoặc bố cục kép |
| Hệ thống điển hình | PostgreSQL, MySQL, Oracle (như engine OLTP) | Snowflake, BigQuery, Redshift, ClickHouse | TiDB, SingleStore, PostgreSQL + extension columnar (ví dụ: Citus, hydra/columnar) |
| Điểm mạnh truyền thống của PostgreSQL | Xuất sắc — đây chính là thế mạnh cốt lõi của Postgres | Yếu theo mặc định (lưu trữ theo hàng gây bất lợi cho quét rộng) | Đang hình thành, thông qua extension |
Storage engine của PostgreSQL về bản chất là hướng hàng (row-oriented), và chính điều đó khiến nó nhanh với OLTP: lấy hoặc cập nhật một hàng đầy đủ (một bản ghi khách hàng, một đơn hàng) chỉ chạm vào một khối liền kề trên đĩa. Nhưng cùng bố cục đó lại là bất lợi cho các truy vấn kiểu OLAP — SELECT AVG(amount) FROM orders chỉ cần một cột, nhưng row store vẫn phải đọc toàn bộ mỗi hàng vào bộ nhớ để lấy được cột đó. Đây là lý do các data warehouse cổ điển dùng lưu trữ hướng cột (column-oriented) thay vào đó: mỗi cột được lưu liền kề, nên quét một cột trên hàng tỷ hàng chỉ đọc dữ liệu của cột đó, và nén tốt hơn nhiều.
HTAP là nỗ lực mới nổi nhằm phục vụ cả hai pattern từ cùng một hệ thống mà không cần duy trì một pipeline ETL riêng biệt sang warehouse. PostgreSQL không hỗ trợ columnar storage nguyên bản, nhưng mô hình extension của nó khiến các thiết lập kiểu HTAP trở nên khả thi: các extension thêm bảng lưu trữ dạng columnar song song với các bảng row-based thông thường trong cùng database, để dữ liệu transactional “nóng” gần đây có thể vẫn ở dạng row-oriented trong khi dữ liệu lịch sử hoặc nặng về báo cáo sống ở định dạng columnar tối ưu cho aggregation — tất cả đều truy vấn được qua cùng giao diện SQL. Đây vẫn là một lĩnh vực còn non trẻ so với hàng chục năm trưởng thành về OLTP của Postgres, và hầu hết hệ thống production vẫn kết hợp PostgreSQL (OLTP) với một warehouse chuyên dụng (OLAP) được nạp dữ liệu qua change-data-capture hoặc batch ETL, nhưng đáng để biết HTAP tồn tại như một hướng đi mà hệ sinh thái đang tiến tới.
Best Practices
- Mô hình hóa các mối quan hệ tường minh bằng khóa và ràng buộc, đừng dựa vào quy ước. Khai báo
PRIMARY KEY,FOREIGN KEY,UNIQUE, vàNOT NULLở bất cứ nơi nào đúng về mặt logic. Ràng buộc không phải là thủ tục giấy tờ — chúng là thứ giúp query planner của PostgreSQL và bản thân DBA khi lý luận về schema có thể tin tưởng rằng dữ liệu thực sự có hình dạng như nó trông có vẻ. - Chọn PostgreSQL một cách có chủ đích, không phải theo mặc định. Đây là một lựa chọn đa dụng xuất sắc, nhưng hãy nói rõ điều bạn thực sự cần (transaction ACID, join phức tạp, truy vấn ad-hoc) để nếu sau này workload cần một thứ mà PostgreSQL thực sự không thể đáp ứng (ví dụ: horizontal write scaling tuyến tính ở quy mô khổng lồ), bạn nhận ra sớm thay vì cố gắng ép công cụ làm việc nó không phù hợp.
- Đừng dùng JSONB như một cách thay thế cho việc thiết kế schema. JSONB là lối thoát mạnh mẽ cho các thuộc tính thực sự biến đổi hoặc thưa, không phải giấy phép để bỏ qua việc mô hình hóa quan hệ cho các thực thể cốt lõi. Một bảng chỉ là một khối JSONB khổng lồ sẽ mất đi hầu hết lợi ích (ràng buộc, join, thống kê cho planner) vốn là lý do chọn PostgreSQL ngay từ đầu.
- Phân loại workload trước khi thiết kế schema. Biết một database (hay một bảng) cụ thể chủ yếu là OLTP hay OLAP trước khi chọn mức độ chuẩn hóa, chiến lược indexing, và bố cục lưu trữ — câu trả lời đúng cho một bảng
ordersđang nhận ghi trực tiếp rất khác với câu trả lời đúng cho một bảng báo cáoorders_history. - Coi ACID là một dải cấu hình được, không phải một thứ nhị phân có sẵn miễn phí. Isolation level mặc định của PostgreSQL (
READ COMMITTED) yếu hơn serializability đầy đủ; hãy biết nó cho phép những anomaly nào trước khi mặc định nghĩ “nó là ACID” nghĩa là “không có gì có thể sai” — điều này sẽ được trình bày đầy đủ ở Transactions & Concurrency Control.
Bản đồ roadmap
Chủ đề này là điểm khởi đầu về mặt khái niệm; phần còn lại của roadmap sẽ mở rộng dần theo thứ tự đại khái sau:
- 02 — Installation, Setup & Connecting: dựng một instance PostgreSQL và kết nối với nó bằng
psqlvà driver. - 03 — Đối tượng schema: database, schema, bảng, view, và cấu trúc phân cấp đối tượng.
- 04–05 — Truy vấn:
SELECT, join, aggregation, subquery, và cách kết hợp truy vấn. - 06 — Thao tác dữ liệu:
INSERT/UPDATE/DELETE, upsert, và mệnh đềRETURNING. - 07 — Procedure & trigger: stored procedure, function, và tự động hóa dựa trên trigger.
- 08 — Indexing: B-tree, GiST, GIN, BRIN, và cách chọn index phù hợp với access pattern.
- 09 — Transactions & Concurrency Control: isolation level, locking, deadlock, và MVCC chi tiết.
- 10–11 — Hiệu năng: lập kế hoạch truy vấn,
EXPLAIN, tinh chỉnh cấu hình. - 12–13 — Mở rộng & Replication and High Availability: read replica, failover, chiến lược sharding.
- 14 — Backup & recovery:
pg_dump, backup vật lý, point-in-time recovery. - 15 — Bảo mật: role, quyền hạn, row-level security, mã hóa.
- 16 — Giám sát: các view
pg_stat, logging, công cụ observability bên ngoài.
Tài liệu tham khảo
- PostgreSQL Official Documentation
- PostgreSQL — Chapter 1: What is PostgreSQL?
- PostgreSQL — History
- Hironobu Suzuki, “The Internals of PostgreSQL” — đặc biệt Phần I (Overview) để nắm kiến trúc nền tảng
- Codd, E.F., “A Relational Model of Data for Large Shared Data Banks” (1970)
- roadmap.sh — PostgreSQL DBA
- Backend — Relational Databases
- Backend — NoSQL Databases
Part of the PostgreSQL DBA Roadmap knowledge base.
Overview
A relational database management system (RDBMS) stores data as a collection of tables — grids of rows and columns — and lets you relate one table to another through shared key values instead of through pointers or physical nesting. You do not tell an RDBMS how to find the data (walk this pointer, then that one); you tell it what you want in a declarative language, SQL (Structured Query Language), and the database’s query planner figures out the fastest way to get it. This separation between “what” and “how” is the single biggest reason the relational model has dominated business-critical data storage for over fifty years: application code can change constantly, but the query planner can be rewritten, upgraded, and re-optimized without anyone touching a single SELECT statement.
This was not always how databases worked. Before Codd’s relational model (1970), the two dominant approaches were the hierarchical model (IBM’s IMS, 1966) — data organized as a strict tree, where every record has exactly one parent, mirroring a filesystem — and the network model (CODASYL, late 1960s) — a graph of records connected by explicit pointers, more flexible than a tree but still navigated by explicit pointer-chasing code. Both required the application programmer to know the physical layout of the data and to write imperative code that walked those physical links one record at a time. Reorganizing the physical structure — adding a new access path, changing a pointer chain — meant rewriting every program that touched it. Codd’s insight was to separate the logical structure of data (relations, defined by their columns and constraints) from its physical storage and access paths, and to let a query language express intent instead of navigation. That decoupling is why the relational model won: schemas could evolve, storage engines could be optimized, and indexes could be added — all without breaking the applications built on top, as long as the logical shape of the data (the schema) stayed compatible.
PostgreSQL is one implementation of this model — but a particularly ambitious one. It began as POSTGRES, a research project at UC Berkeley led by Michael Stonebraker (the same person behind Ingres, one of the earliest relational systems), explicitly designed to go beyond the relational model of the 1980s by supporting user-defined types, rules, and extensibility. When SQL support was added in 1995 the project was renamed Postgres95, and by 1996 it became PostgreSQL under its current open-source community governance. That research DNA — “let users extend the type system and the database itself” — is still the defining trait that separates PostgreSQL from MySQL, Oracle, and SQL Server today, and it is the reason PostgreSQL can serve as both a strict, standards-compliant relational engine and a platform for things like full JSON document storage or vector similarity search without leaving the same system.
This note is the starting point for the PostgreSQL DBA roadmap: it establishes the conceptual foundation (the relational model, ACID, OLTP/OLAP) that every later topic — installation, schema design, indexing, transactions, replication, security — builds on.
Fundamentals
The relational model, formally
Codd’s relational model gives precise, mathematical names to what most people already picture as “a table”:
| Informal term | Formal term | Definition |
|---|---|---|
| Table | Relation | A named set of tuples that all share the same attributes (columns) |
| Row / record | Tuple | A single, ordered set of values, one per attribute, within a relation |
| Column / field | Attribute | A named, typed component of a tuple (e.g., email TEXT) |
| Table structure | Schema (of the relation) | The set of attribute names and their domains (types) |
| Domain | Domain | The set of legal values an attribute can take (e.g., INTEGER, BOOLEAN) |
Two guarantees fall directly out of treating a relation as a mathematical set of tuples, and they matter in practice, not just in theory:
- No duplicate tuples. A set cannot contain the same element twice, so in the pure relational model a relation cannot contain two fully identical rows. In real SQL engines — PostgreSQL included — tables can physically contain duplicate rows unless you add a
PRIMARY KEYorUNIQUEconstraint, because SQL tables are technically multisets (bags), not pure mathematical relations. This is a deliberate practical relaxation, but it means uniqueness is something you must declare, not something you get for free — one of the first things a DBA should check on any table without an obvious natural key. - Unordered attributes (and, in principle, tuples). A relation has no intrinsic row order or column order;
{name, age}and{age, name}describe the same relation. This is whySELECT *returning columns in table-definition order is a convenience, not a guarantee, and why relying on physical row order without anORDER BYclause is a correctness bug waiting to surface after the nextVACUUM FULLor index rebuild changes physical layout.
Relations connect to each other through keys: a primary key uniquely identifies each tuple in its own relation, and a foreign key in one relation references a primary (or unique) key in another, encoding a relationship without duplicating data. This is what lets you split orders and customers into separate tables and still reconstruct “which customer placed which order” — via a join — instead of duplicating customer data into every order row, and lets the database enforce referential integrity (you cannot insert an order for a customer that does not exist) as a first-class guarantee rather than an application-level convention.
RDBMS benefits and limitations
The relational model’s mathematical grounding translates into concrete engineering benefits, but it also creates real constraints — understanding both is what lets a DBA choose (or defend) PostgreSQL for a given workload instead of reaching for it out of habit.
| Benefits | Limitations |
|---|---|
| Strong consistency guarantees via ACID transactions | Horizontal scaling (sharding across many nodes) is harder than in purpose-built distributed NoSQL systems |
| Mature, standardized query language (SQL) portable across systems and skill sets | Schema changes require explicit migrations; adding a column to a huge table can lock or rewrite it |
| Powerful, declarative joins across normalized tables | Not ideal for extremely high-write, loosely-structured data (e.g., raw IoT telemetry, unstructured logs at massive scale) |
| Declarative query optimizer frees developers from hand-written access paths | Vertical scaling (bigger machine) is the default scaling path before sharding becomes necessary |
| Rich ecosystem: decades of tooling, ORMs, monitoring, DBAs who know the model | Rigid typing/constraints add upfront schema design cost compared to schema-on-read stores |
None of the limitations are fatal — they are trade-offs. A well-modeled relational schema with the right indexes will outperform a naively-used NoSQL store for most transactional applications, and PostgreSQL specifically pushes back on several of these limitations (JSONB for semi-structured data, logical replication and extensions like Citus for horizontal scaling), which is part of why it has become the default choice across so many workload types rather than a narrow specialist RDBMS.
Key Concepts
PostgreSQL vs. other RDBMS
All major RDBMS implement the same relational core, but they diverge sharply on extensibility, standards adherence, and concurrency architecture — and those differences are usually what actually decides which one you pick.
| Aspect | PostgreSQL | MySQL | Oracle | SQL Server | SQLite |
|---|---|---|---|---|---|
| License / cost | Open source, permissive PostgreSQL License (no-cost) | Open source (GPL) + paid Enterprise tier | Proprietary, expensive licensing | Proprietary, licensing per core | Public domain, embedded, free |
| Extensibility | Very high: custom types, operators, index access methods, procedural languages, extensions (pgvector, PostGIS, TimescaleDB) | Limited; storage engine pluggable (InnoDB, MyISAM) but type system is closed | High but proprietary (PL/SQL packages) | Moderate (CLR integration, T-SQL) | Minimal; not meant to be extended |
| Standards compliance | Very high — closely tracks SQL standard | Historically looser (implicit type coercion, silent truncation) | High, with many proprietary extensions | High, with T-SQL dialect extensions | Partial; dynamic typing departs from the standard |
| Concurrency model | MVCC via tuple versions in the heap + vacuum | MVCC in InnoDB (undo logs, closer to Oracle’s approach) | MVCC via undo segments/rollback | Lock-based by default, optional MVCC (snapshot isolation) | Single-writer, file-level locking |
| Indexing options | B-tree, Hash, GiST, SP-GiST, GIN, BRIN — pluggable index access methods | Primarily B-tree (+ limited full-text, spatial) | B-tree, bitmap, function-based, domain indexes | B-tree (clustered/non-clustered), columnstore | B-tree only |
| Typical use case | General purpose OLTP, geospatial, JSON workloads, analytics with extensions | Web applications, read-heavy workloads, simplicity-first setups | Large enterprise, legacy mission-critical systems | Windows/.NET-centric enterprise stacks | Embedded, mobile, single-file local storage |
The two differentiators worth internalizing above the rest of the table are extensibility and MVCC implementation. PostgreSQL’s extension framework (CREATE EXTENSION) lets you add genuinely new data types and index methods without forking the database — this is how pgvector (vector similarity search) and PostGIS (geospatial data) exist as first-class citizens rather than bolted-on hacks. And PostgreSQL’s specific MVCC design — storing multiple row versions directly in the table heap and relying on VACUUM to reclaim dead versions — trades some storage/maintenance overhead (bloat, the need to vacuum) for a simpler, non-blocking read path where readers never block writers and writers never block readers. Contrast this with SQL Server’s default locking model, where naive concurrent access can produce blocking chains that Postgres, MySQL/InnoDB, or Oracle would not.
PostgreSQL vs. NoSQL databases
The historical line between “use Postgres for structured relational data, use a NoSQL store for flexible/huge-scale data” has blurred considerably, mostly because of two PostgreSQL features: JSONB, a binary, indexable JSON column type that lets you store and query schema-flexible documents inside a normal relational table, and extensions like pgvector, which turns PostgreSQL into a viable vector database for embeddings and similarity search. It is now genuinely possible to run a document-store-like workload, a full-text search workload, and a semantic-search workload all inside one PostgreSQL instance.
That said, the fundamental trade-off described in NoSQL Databases still holds, and JSONB does not erase it — it just gives you an escape hatch within a relational system. Reach for PostgreSQL (relational modeling, even with JSONB columns) when:
- The data has clear entities and relationships that benefit from foreign keys, joins, and referential integrity.
- Transactions must be ACID-compliant across multiple related writes (e.g., moving money between two accounts).
- You need ad-hoc, declarative querying (arbitrary
WHERE/JOIN/aggregate combinations) rather than a small set of pre-defined access patterns.
Reach for a purpose-built NoSQL store instead when:
- Data volume and write throughput exceed what a single primary (plus read replicas) can serve, and you need transparent, near-linear horizontal scaling as a first-class design goal rather than an add-on (e.g., Cassandra, DynamoDB).
- The schema is genuinely unpredictable and access is almost always by a single key or a small set of known patterns (e.g., a document store keyed by document ID), so you gain little from relational modeling.
- You need a specialized data model — a graph database for deep relationship traversal, a wide-column store for time-series-at-massive-scale — that a generic relational or document engine handles poorly.
The practical rule most DBAs converge on: default to PostgreSQL for transactional and general-purpose workloads, and adopt a specialized NoSQL system only once you have a concrete, measured reason (a specific access pattern or scale requirement Postgres cannot meet) rather than a vague sense that “NoSQL is more scalable.”
ACID, previewed
Every relational transaction that PostgreSQL commits is expected to satisfy ACID: Atomicity (a transaction’s writes all happen or none do), Consistency (a transaction moves the database from one valid state to another, respecting constraints), Isolation (concurrent transactions do not see each other’s uncommitted intermediate state), and Durability (once committed, data survives a crash). The full mechanics — isolation levels, anomalies, locking — are covered in Backend — Relational Databases and will be revisited in depth in Transactions & Concurrency Control; this note only previews how PostgreSQL specifically delivers on two of the four letters, because it explains a lot of PostgreSQL’s distinctive behavior later in the roadmap:
- Durability comes from the Write-Ahead Log (WAL). Before PostgreSQL modifies a data page, it first writes a record of that change to the WAL and flushes it to disk. If the server crashes, replaying the WAL on restart reconstructs any committed changes that had not yet been written back to the actual table files. This is also the same mechanism that powers physical replication and point-in-time recovery, covered in Replication & High Availability.
- Isolation comes from MVCC (Multi-Version Concurrency Control). Instead of readers taking locks that block writers, PostgreSQL keeps multiple versions of a row (created by
UPDATE/DELETE) and gives each transaction a consistent “snapshot” of the data as of a point in time. A reader sees the version of a row that was committed before its snapshot was taken, regardless of concurrent writers modifying that same row. This is why, in PostgreSQL,SELECTnever blocks onUPDATEand vice versa — a property many DBAs coming from lock-heavy systems find surprising at first.
OLTP vs. OLAP vs. HTAP
Not all database workloads look alike, and the shape of the workload should drive schema, indexing, and even engine choice. Three broad categories describe the spectrum:
| Aspect | OLTP | OLAP | HTAP |
|---|---|---|---|
| Full name | Online Transaction Processing | Online Analytical Processing | Hybrid Transactional/Analytical Processing |
| Typical query | Short, simple (single-row lookups/updates) | Long, complex (aggregations across millions of rows) | Both, on the same data |
| Query volume | Very high (thousands/sec) | Low to moderate | High for OLTP-style, moderate for analytics |
| Data scope per query | A few rows | Large scans, often whole tables/partitions | Mixed |
| Storage layout | Row-oriented (fast for whole-row read/write) | Column-oriented (fast for scanning few columns across many rows) | Often mixed or dual-layout |
| Typical systems | PostgreSQL, MySQL, Oracle (as OLTP engines) | Snowflake, BigQuery, Redshift, ClickHouse | TiDB, SingleStore, PostgreSQL + columnar extensions (e.g., Citus, hydra/columnar) |
| PostgreSQL’s traditional fit | Excellent — this is Postgres’s core strength | Weak by default (row storage penalizes wide scans) | Emerging, via extensions |
PostgreSQL’s storage engine is fundamentally row-oriented, which is precisely what makes it fast at OLTP: fetching or updating one full row (a customer record, an order) touches one contiguous chunk of disk. That same layout is a liability for OLAP-style queries — SELECT AVG(amount) FROM orders only needs one column but a row store still has to read every full row into memory to get at it. This is why classic data warehouses use column-oriented storage instead: each column is stored contiguously, so scanning one column across a billion rows reads only that column’s data and compresses far better besides.
HTAP is the emerging attempt to serve both patterns from the same system without maintaining a separate ETL pipeline into a warehouse. PostgreSQL does not natively provide columnar storage, but its extension model makes HTAP-style setups possible: extensions add columnar table storage alongside normal row-based tables in the same database, so recent “hot” transactional data can stay row-oriented while historical or reporting-heavy data lives in a columnar format optimized for aggregation — all queryable through the same SQL interface. This is still a young area compared to Postgres’s decades of OLTP maturity, and most production systems still pair PostgreSQL (OLTP) with a dedicated warehouse (OLAP) fed by change-data-capture or batch ETL, but it is worth knowing HTAP exists as the direction the ecosystem is moving.
Best Practices
- Model relationships explicitly with keys and constraints, don’t rely on convention. Declare
PRIMARY KEY,FOREIGN KEY,UNIQUE, andNOT NULLwherever they are logically true. Constraints are not paperwork — they are what lets PostgreSQL’s planner and the DBA reasoning about the schema trust that the data is actually shaped the way it looks. - Choose PostgreSQL deliberately, not by default. It is an excellent general-purpose choice, but say out loud what you actually need (ACID transactions, complex joins, ad-hoc querying) so that if a workload later needs something PostgreSQL genuinely cannot give you (massive linear horizontal write scaling, for instance) you notice early rather than fighting the tool.
- Don’t reach for JSONB as a substitute for schema design. JSONB is a powerful escape hatch for genuinely variable or sparse attributes, not a license to skip modeling your core entities relationally. A table that is one giant JSONB blob loses most of the benefits (constraints, joins, planner statistics) that justified choosing PostgreSQL in the first place.
- Classify your workload before you design the schema. Know whether a given database (or table) is primarily OLTP or OLAP before choosing normalization level, indexing strategy, and storage layout — the right answer for a
orderstable taking live writes is very different from the right answer for aorders_historyreporting table. - Treat ACID as a spectrum you configure, not a binary you get for free. PostgreSQL’s default isolation level (
READ COMMITTED) is weaker than full serializability; know what anomalies it permits before assuming “it’s ACID” means “nothing can go wrong” — this is developed fully in Transactions & Concurrency Control.
Map of the roadmap
This topic is the conceptual entry point; the rest of the roadmap builds outward from here in roughly this order:
- 02 — Installation, Setup & Connecting: getting a PostgreSQL instance running and connecting to it with
psqland drivers. - 03 — Schema objects: databases, schemas, tables, views, and the object hierarchy.
- 04–05 — Querying:
SELECT, joins, aggregation, subqueries, and query composition. - 06 — Data modification:
INSERT/UPDATE/DELETE, upserts, and returning clauses. - 07 — Procedures & triggers: stored procedures, functions, and trigger-based automation.
- 08 — Indexing: B-tree, GiST, GIN, BRIN, and choosing the right index for the access pattern.
- 09 — Transactions & Concurrency Control: isolation levels, locking, deadlocks, and MVCC in depth.
- 10–11 — Performance: query planning,
EXPLAIN, tuning configuration. - 12–13 — Scaling & Replication and High Availability: read replicas, failover, sharding strategies.
- 14 — Backup & recovery:
pg_dump, physical backups, point-in-time recovery. - 15 — Security: roles, privileges, row-level security, encryption.
- 16 — Monitoring:
pg_statviews, logging, external observability tooling.
References
- PostgreSQL Official Documentation
- PostgreSQL — Chapter 1: What is PostgreSQL?
- PostgreSQL — History
- Hironobu Suzuki, “The Internals of PostgreSQL” — especially Part I (Overview) for architecture grounding
- Codd, E.F., “A Relational Model of Data for Large Shared Data Banks” (1970)
- roadmap.sh — PostgreSQL DBA
- Backend — Relational Databases
- Backend — NoSQL Databases