Luyện Phỏng Vấn IT — 2000+ Câu Hỏi Phỏng Vấn IT Có Đáp Án 2026

Danh mục

Cơ sở dữ liệu iconCơ sở dữ liệu

SQL chia thành 4 nhóm: DDL (CREATE/ALTER/DROP), DML (SELECT/INSERT/UPDATE/DELETE), DCL (GRANT/REVOKE), TCL (COMMIT/ROLLBACK) — developer dùng DML 90% thời gian.

  • SQL (Structured Query Language) là ngôn ngữ chuẩn để tương tác với cơ sở dữ liệu quan hệ như PostgreSQL, MySQL, SQLite.
  • Được chia thành 4 nhóm lệnh: DDL (Data Definition Language) định nghĩa cấu trúc — CREATE TABLE, ALTER TABLE, DROP TABLE; DML (Data Manipulation Language) thao tác dữ liệu — SELECT, INSERT, UPDATE, DELETE; DCL (Data Control Language) phân quyền — GRANT SELECT ON users TO readonly_user; TCL (Transaction Control Language) quản lý giao dịch — BEGIN, COMMIT, ROLLBACK.
  • Trong thực tế hàng ngày, dev dùng DML 90% thời gian, DDL khi migration, còn DCL và TCL do DBA hoặc DevOps quản lý.
  • Hiểu phân loại này giúp debug lỗi quyền truy cập và thiết kế migration scripts đúng cách.

Thứ tự thực thi SQL: FROM→JOIN→WHERE→GROUP BY→HAVING→SELECT→ORDER BY→LIMIT — alias trong SELECT không dùng được trong WHERE vì SELECT chưa chạy; SELECT * trong production là anti-pattern.

  • Thứ tự thực thi SQL (quan trọng để hiểu tại sao không dùng alias trong WHERE): FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT/OFFSET.
  • Câu query thực tế: SELECT u.name, COUNT(o.id) AS order_count FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE u.created_at > '2024-01-01' GROUP BY u.id, u.name HAVING COUNT(o.id) > 5 ORDER BY order_count DESC LIMIT 20.
  • Aliasing: SELECT price * 0.9 AS discounted_price — alias trong SELECT không dùng được trong WHERE (chưa được tính), nhưng dùng được trong ORDER BY (PostgreSQL cho phép).
  • DISTINCT: SELECT DISTINCT city FROM users loại trùng lặp — có sorting overhead, dùng GROUP BY khi cần performance tốt hơn.
  • SELECT *: không dùng production — lấy data thừa, break khi thêm column, không dùng được covering index.

Pitfall: LIMIT không có ORDER BY → kết quả non-deterministic, mỗi query trả về rows khác nhau.

Luôn test UPDATE/DELETE với SELECT trước — UPDATE users SET active = false thiếu WHERE cập nhật TOÀN BỘ bảng; dùng RETURNING để verify rows affected; ON CONFLICT cho atomic upsert.

  • Bulk INSERT: INSERT INTO users (name, email) VALUES ('A', 'a@x.com'), ('B', 'b@x.com'), ('C', 'c@x.com') — một round-trip thay vì N trips, nhanh hơn 10-100x với lượng lớn.
  • RETURNING clause (PostgreSQL): INSERT INTO users (name) VALUES ('Dinh') RETURNING id, created_at — lấy generated values ngay không cần query thêm.
  • ON CONFLICT (upsert): INSERT INTO settings (user_id, key, value) VALUES (1, 'theme', 'dark') ON CONFLICT (user_id, key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW() — atomic upsert không race condition.
  • Safe UPDATE: luôn chạy SELECT với WHERE condition trước để verify rows affected; UPDATE users SET role = 'admin' WHERE id = 5 RETURNING * confirm kết quả.
  • Safe DELETE: BEGIN; DELETE FROM users WHERE created_at < '2020-01-01'; SELECT COUNT(*); ROLLBACK; — dry run trong transaction để kiểm tra trước khi COMMIT.

Pitfall: UPDATE users SET active = false không có WHERE → update toàn bộ bảng, không thể undo nếu không có backup; dùng sql_safe_updates mode hoặc review trước khi execute.

NULL là giá trị không xác định, khác hoàn toàn với 0 hay chuỗi rỗng — NULL = NULL trả về NULL (không phải TRUE), phải dùng IS NULL/IS NOT NULL; aggregate functions bỏ qua NULL tự động.

  • NULL trong SQL đại diện cho giá trị không xác định hoặc thiếu — hoàn toàn khác với số 0, chuỗi rỗng '', hay false.
  • Đây là nguồn gốc của nhiều bug khó phát hiện.
  • Kiểm tra NULL phải dùng IS NULL / IS NOT NULL, tuyệt đối không dùng = NULLNULL = NULL trả về NULL (không phải TRUE), khiến WHERE clause không match gì cả.
  • Aggregate functions như SUM(), AVG(), MAX() tự động bỏ qua NULL — AVG(score) chỉ tính trung bình các hàng có giá trị, không tính NULL là 0.
  • Xử lý NULL khi select: COALESCE(phone, 'N/A') trả về giá trị đầu tiên không NULL.

Pitfall trong JOIN: nếu foreign key có NULL, INNER JOIN sẽ loại bỏ hàng đó — dùng LEFT JOIN và kiểm tra NULL trong kết quả.

NoSQL (Not Only SQL) là nhóm databases không dùng relational model và SQL truyền thống, được thiết kế để giải quyết những bài toán mà SQL databases xử lý kém: schema linh hoạt, horizontal scaling, và throughput cao.

  • Bốn loại chính với use case khác nhau: Document stores (MongoDB, CouchDB) — lưu JSON documents, phù hợp content management, user profiles; Key-Value stores (Redis, DynamoDB) — truy cập O(1) theo key, phù hợp caching và session; Column-family stores (Cassandra, HBase) — write-heavy workloads, time-series data ở quy mô lớn; Graph databases (Neo4j) — dữ liệu có quan hệ phức tạp như mạng xã hội, recommendation engine.

Pitfall khi chọn NoSQL: không phải lúc nào cũng tốt hơn SQL — nếu data có quan hệ rõ ràng và cần ACID transactions, PostgreSQL thường là lựa chọn tốt hơn MongoDB.

MongoDB lưu data dưới dạng BSON (Binary JSON) — superset của JSON hỗ trợ thêm types như Date, ObjectId, Binary.

  • Mỗi document tự động có _id field với ObjectId gồm 12 bytes: 4 bytes timestamp + 3 bytes machine ID + 2 bytes PID + 3 bytes counter, đảm bảo uniqueness mà không cần central coordinator.
  • Thay vì tables dùng collections, thay vì rows dùng documents — mỗi document trong cùng collection có thể có schema khác nhau.
  • Flexible schema giúp khi data structure thay đổi thường xuyên (product catalog với attributes khác nhau per category), nhưng gây hại khi cần consistency — ví dụ field age có thể lưu string '25' hay number 25 tùy document dẫn đến bugs khó phát hiện.

Pitfall phổ biến: thiếu indexes dẫn đến full collection scan.

INNER JOIN chỉ lấy rows có match cả hai bảng; LEFT JOIN lấy tất cả rows bên trái kể cả không có match; WHERE trên cột right table biến LEFT JOIN thành INNER JOIN — đặt filter trong ON clause để tránh.

  • INNER JOIN = phần giao (chỉ rows có match cả 2 bảng).
  • Visualize bằng Venn diagrams: INNER JOIN = phần giao (chỉ rows có match cả 2 bảng); LEFT JOIN = toàn bộ vòng trái + phần giao (right side NULL nếu không match); RIGHT JOIN = ngược lại (hiếm dùng, viết lại thành LEFT JOIN cho dễ đọc); FULL OUTER JOIN = cả 2 vòng (NULL ở bên không có match); CROSS JOIN = Cartesian product M×N rows (nguy hiểm với bảng lớn).
  • Use case thực tế: INNER JOIN khi chỉ muốn users CÓ orders; LEFT JOIN khi muốn TẤT CẢ users kể cả không có orders (NULL check để tìm users chưa mua: WHERE o.id IS NULL).
  • Performance: JOIN trên indexed foreign keys rất nhanh; JOIN trên non-indexed columns → Seq Scan + Hash Join chậm.
  • Interview trap: SELECT * FROM A LEFT JOIN B ON A.id = B.a_id WHERE B.col = 'x' — WHERE filter trên B column biến LEFT JOIN thành INNER JOIN vì NULL rows bị lọc ra; phải dùng AND B.col = 'x' trong ON clause để giữ NULL rows.
  • Self JOIN: SELECT e.name, m.name as manager FROM employees e LEFT JOIN employees m ON e.manager_id = m.id — join bảng với chính nó để resolve hierarchical data.

Execution order là chìa khóa: WHERE chạy TRƯỚC GROUP BY (lọc individual rows), HAVING chạy SAU (lọc groups).

  • WHERE không dùng được aggregate vì aggregation chưa xảy ra — WHERE COUNT(*) > 5 là syntax error.
  • HAVING dùng được aggregate: HAVING COUNT(*) > 5, HAVING SUM(amount) > 1000.

Ví dụ rõ ràng: SELECT dept, COUNT() FROM employees WHERE salary > 50000 GROUP BY dept HAVING COUNT() > 3 — WHERE loại employees lương thấp trước, GROUP BY nhóm, HAVING giữ dept có > 3 employees lương cao.

GROUP BY nhóm rows có cùng giá trị thành groups, mỗi group trả về một row.

  • Quy tắc bắt buộc: mọi column trong SELECT phải hoặc là trong GROUP BY hoặc là aggregate function — SELECT dept, name, COUNT(*) GROUP BY dept lỗi vì name không trong GROUP BY và không phải aggregate (PostgreSQL strict, MySQL lenient).

Ví dụ thực tế: SELECT category, COUNT(*) as total, AVG(price) as avg_price, MAX(price) as max_price FROM products GROUP BY category ORDER BY total DESC.

Pitfall: GROUP BY column không có index → full scan + sort — cân nhắc thêm index.

Aggregate functions gộp nhiều rows thành một giá trị.

  • NULL behavior quan trọng: COUNT(*) đếm tất cả rows kể cả NULL; COUNT(col) bỏ qua NULL — COUNT(phone) cho biết bao nhiêu users có số điện thoại; SUM/AVG/MAX/MIN đều bỏ qua NULL.
  • DISTINCT trong aggregates: COUNT(DISTINCT city) đếm số cities duy nhất, SUM(DISTINCT amount) tổng các giá trị không trùng.
  • STRING_AGG (PostgreSQL): STRING_AGG(tag, ', ' ORDER BY tag) nối values thành string trong group — SELECT post_id, STRING_AGG(tag_name, ',') FROM post_tags GROUP BY post_id.
  • ARRAY_AGG: ARRAY_AGG(email ORDER BY created_at) tạo array từ group values.
  • Conditional aggregation với FILTER: COUNT() FILTER (WHERE status = 'active') AS active_count, COUNT() FILTER (WHERE status = 'inactive') AS inactive_count — pivot-style aggregation trong một query thay vì nhiều subqueries.
  • PERCENTILE: PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) tính median — không bị ảnh hưởng bởi outliers như AVG.
  • B-tree index: cấu trúc cây cân bằng, mỗi node chứa sorted keys và pointers — tìm kiếm O(log n) thay vì O(n) full scan.
  • Bảng 10 triệu rows: full scan ~10s, B-tree index ~1ms.
  • Covering index: index chứa TẤT CẢ columns cần cho query → database chỉ đọc index, không cần đọc table (Index Only Scan) — CREATE INDEX idx_users_email_name ON users(email, name) cover SELECT name FROM users WHERE email = ?.
  • Partial index: index chỉ trên subset rows — CREATE INDEX idx_active_users ON users(email) WHERE deleted_at IS NULL nhỏ hơn, nhanh hơn full index nếu query luôn có WHERE deleted_at IS NULL.
  • Khi KHÔNG nên index: bảng < 1000 rows (full scan nhanh hơn), columns ít cardinality (boolean, status với 2-3 values), bảng write-heavy (mỗi write phải update tất cả indexes).
  • Index maintenance: REINDEX để rebuild index bị bloated; pg_stat_user_indexes xem index usage — drop indexes không được dùng.

Pitfall: function trên indexed column bypass index — WHERE LOWER(email) = ? không dùng index trên email; tạo functional index CREATE INDEX ON users(LOWER(email)) thay thế.

Uncorrelated subquery chạy một lần, kết quả dùng cho outer query: SELECT * FROM products WHERE price > (SELECT AVG(price) FROM products).

  • Correlated subquery tham chiếu outer query và chạy lại cho MỖI ROW của outer query — O(n²) complexity, rất chậm với bảng lớn: SELECT * FROM employees e WHERE salary > (SELECT AVG(salary) FROM employees WHERE dept = e.dept).
  • EXISTS vs IN: EXISTS (SELECT 1 FROM orders WHERE user_id = u.id) dừng ngay khi tìm thấy match đầu tiên (short-circuit), tốt cho large subsets; IN (SELECT user_id FROM orders) load toàn bộ list vào memory — NOT IN với NULL trong subquery trả về empty result set (NULL pitfall).
  • Scalar subquery trong SELECT: SELECT name, (SELECT COUNT(*) FROM orders WHERE user_id = u.id) AS order_count FROM users u — tương đương LEFT JOIN nhưng thường chậm hơn vì chạy N lần.
  • Lateral join (PostgreSQL): FROM users u, LATERAL (SELECT * FROM orders WHERE user_id = u.id ORDER BY created_at DESC LIMIT 3) o — như correlated subquery nhưng có thể reference multiple columns và return multiple rows, optimizer xử lý tốt hơn.
  • Khi subquery tốt hơn JOIN: EXISTS checks, khi JOIN tạo fan-out (duplicate rows do one-to-many), scalar aggregates.

UNION ALL luôn nhanh hơn UNION vì không cần sort+deduplicate — chỉ dùng UNION khi thực sự cần loại bỏ duplicates; ORDER BY chỉ đặt ở cuối toàn bộ UNION, không trong từng SELECT.

  • UNION deduplicate bằng cách sort + compare toàn bộ result set — O(n log n) overhead, chậm với large results.
  • UNION ALL giữ tất cả rows kể cả duplicates, không sort — luôn nhanh hơn, dùng khi biết không có duplicates hoặc duplicates là intentional.
  • Column compatibility: cùng số columns và compatible (không nhất thiết identical) types — SELECT id, name FROM users UNION SELECT order_id, product_name FROM orders hợp lệ nếu types compatible.
  • Column names lấy từ SELECT đầu tiên.
  • ORDER BY chỉ được đặt ở cuối toàn bộ UNION, không được đặt trong từng SELECT — (SELECT ... ORDER BY x) UNION (SELECT ...) là error trong nhiều databases.
  • INTERSECT: rows xuất hiện trong CẢ HAI queries — SELECT user_id FROM premium_users INTERSECT SELECT user_id FROM active_users.
  • EXCEPT (MINUS trong Oracle): rows trong query đầu nhưng không có trong query thứ hai — SELECT email FROM all_users EXCEPT SELECT email FROM unsubscribed.
  • Practical use case: SELECT 'income' as type, amount FROM income UNION ALL SELECT 'expense', -amount FROM expenses ORDER BY date — combine two different tables for unified report.

CTE (WITH clause) đặt tên cho subquery phức tạp để tái sử dụng và dễ đọc — Recursive CTE giải quyết hierarchical data (org tree, categories); PostgreSQL 12+ tự chọn materialized hay inline tùy cost.

  • CTE (WITH clause) là named temporary result set tồn tại trong scope của query — WITH active_users AS (SELECT FROM users WHERE deleted_at IS NULL), premium AS (SELECT user_id FROM subscriptions WHERE plan = 'premium') SELECT u. FROM active_users u JOIN premium p ON u.id = p.user_id.
  • Lợi ích: dễ đọc hơn nested subqueries, có thể reference nhiều lần trong query chính, hỗ trợ chaining CTEs.
  • Materialization behavior (PostgreSQL): mặc định CTEs được materialized (tính một lần, lưu temp) — optimizer không thể push predicates vào CTE, đôi khi chậm hơn subquery; thêm NOT MATERIALIZED để optimizer inline: WITH cte AS NOT MATERIALIZED (...).
  • Recursive CTE — org hierarchy example: WITH RECURSIVE org AS (SELECT id, name, manager_id, 0 AS depth FROM employees WHERE manager_id IS NULL UNION ALL SELECT e.id, e.name, e.manager_id, o.depth + 1 FROM employees e JOIN org o ON e.manager_id = o.id) SELECT * FROM org ORDER BY depth — tìm toàn bộ cây tổ chức.
  • CTE vs subquery performance: trong PostgreSQL < 12, CTE luôn materialized (optimization fence); PostgreSQL 12+ thông minh hơn.
  • CTE trong DML: WITH deleted AS (DELETE FROM logs WHERE created_at < NOW() - INTERVAL '90 days' RETURNING ) INSERT INTO archive SELECT FROM deleted — atomic delete + archive.

Normalization loại bỏ redundancy và update anomalies. 1NF: mỗi cell atomic, không repeating groups — vi phạm: tags: 'nodejs,react' một cell nhiều values → tách thành bảng user_tags(user_id, tag). 2NF (composite PK): mọi non-key column depend vào TOÀN BỘ PK — vi phạm: order_items(order_id, product_id, product_name) — product_name chỉ depend vào product_id → tách ra bảng products. 3NF: không có transitive dependency — vi phạm: employees(id, dept_id, dept_name) — dept_name depend vào dept_id chứ không phải id → tách thành bảng departments.

  • Tại sao quan trọng: update anomaly — đổi tên department phải update N rows, dễ inconsistency; insert anomaly — không thể thêm department nếu chưa có employee; delete anomaly — xóa employee cuối xóa luôn thông tin dept.
  • Thực tế: normalize đến 3NF là standard; BCNF/4NF cho edge cases; denormalize có chủ đích khi có profiling evidence.
  • Primary key: uniquely identifies mỗi row, NOT NULL, UNIQUE, immutable — thường là surrogate key (serial/UUID).
  • Composite PK: PRIMARY KEY (order_id, product_id) trong junction tables.
  • Foreign key: đảm bảo referential integrity — không thể insert orders.user_id = 999 nếu users.id = 999 không tồn tại; không thể delete user nếu còn orders tham chiếu (trừ khi có CASCADE).
  • FK constraint options: RESTRICT (default, block delete/update nếu có children), CASCADE (propagate delete/update xuống children), SET NULL (set FK = NULL khi parent deleted — cần FK column nullable), SET DEFAULT (set FK = default value), NO ACTION (like RESTRICT nhưng deferred).
  • Indexing: PK tự động có index; FK column nên index để JOIN và DELETE parent nhanh — thiếu index trên FK gây full scan child table mỗi khi delete/update parent.

Pitfall: FK constraints enforce data integrity nhưng có overhead mỗi INSERT/UPDATE/DELETE — một số teams disable FK trong high-throughput systems và enforce integrity ở application layer (trade-off consistency vs performance).

One-to-One: mỗi record A liên kết đúng một record B — implement bằng UNIQUE FK: user_profiles.user_id REFERENCES users(id) UNIQUE.

  • Dùng để: tách optional/sensitive data (user + user_payment_info), tách heavy columns ít dùng, polymorphic extension tables.
  • One-to-Many: một A có nhiều B — FK trên bảng Many: posts.user_id REFERENCES users(id).
  • Phổ biến nhất trong relational design.
  • Many-to-Many: cần junction table với composite PK hoặc surrogate PK: enrollments(student_id, course_id, enrolled_at, grade) — junction table nên có thêm attributes liên quan đến relationship.
  • Self-referential: employees.manager_id REFERENCES employees(id) — org hierarchy, category tree, comment threads.
  • Polymorphic associations (anti-pattern trong SQL): comments(id, content, commentable_type, commentable_id) — commentable_id có thể tham chiếu posts hoặc videos — không có FK constraint, hard to maintain; prefer multiple FK columns với CHECK constraint, hoặc separate junction tables per type.
  • Pattern chọn: xác định cardinality trước, sau đó FK đặt ở phía Many, junction table cho M:N.

ER Diagram trực quan hóa database schema trước khi implement — bắt buộc với schema phức tạp để tránh costly refactoring sau.

Thành phần: Entities (bảng, hình chữ nhật), Attributes (columns, hình oval — key attribute gạch chân), Relationships (hình thoi nối entities), Cardinality notation.

Crow's Foot notation phổ biến hơn Chen notation trong practice: ký hiệu trên đường relationship — | (one), O (zero), < hoặc > (many).

Ví dụ đọc Crow's Foot: users ||--o{ orders = một user có không hoặc nhiều orders; orders }|--|{ products = many-to-many với mandatory participation.

Tools: dbdiagram.io (code-based, version control friendly), draw.io, Lucidchart, ERDPlus.

Prisma schema → auto-generate ERD với Prisma Studio.

Bước thiết kế:

  1. identify entities từ business requirements,
  2. xác định attributes và PK cho mỗi entity,
  3. xác định relationships và cardinality,
  4. resolve M:N thành junction tables,
  5. review với team trước khi viết DDL

ON DELETE CASCADE tự xóa child rows khi parent bị xóa — tiện nhưng nguy hiểm nếu dùng nhầm; RESTRICT (default) an toàn nhất; chỉ dùng CASCADE khi child không có nghĩa gì nếu thiếu parent (order_items, sessions).

  • ON DELETE CASCADE: xóa user → tự xóa tất cả orders, comments, sessions — tiện nhưng nguy hiểm nếu dùng nhầm.
  • Cascade propagate action từ parent xuống child table tự động.
  • ON DELETE CASCADE: xóa user → tự xóa tất cả orders, comments, sessions — tiện nhưng nguy hiểm nếu dùng nhầm.
  • ON DELETE SET NULL: xóa category → products.category_id = NULL, products vẫn tồn tại — dùng khi child có thể tồn tại độc lập.
  • ON DELETE RESTRICT (default): block delete parent nếu còn child — safe nhất, application phải xử lý explicitly.
  • ON DELETE SET DEFAULT: set FK về default value.
  • ON UPDATE CASCADE: hữu ích khi PK có thể thay đổi (natural keys) — ít dùng với surrogate keys vì chúng immutable.
  • Cascade chains: A → B → C với CASCADE, xóa A tự xóa B rồi C — có thể không nhận ra.
  • Kiểm tra cascade impact: SELECT * FROM information_schema.referential_constraints WHERE unique_constraint_schema = 'public'.
  • Khi nào dùng CASCADE: ownership relationship rõ ràng (user → sessions, order → order_items — items không có nghĩa gì nếu không có order).
  • Khi không nên: shared resources (xóa author không nên xóa books nếu books có value độc lập).
  • Soft delete + CASCADE: khi dùng soft delete thì CASCADE không kích hoạt — phải implement cascade logic trong application.

Luôn dùng surrogate key (BIGSERIAL/UUID) làm PK và đặt UNIQUE constraint trên natural key — natural key có thể thay đổi hoặc duplicate trong thực tế; UUID v7 là lựa chọn tốt nhất: globally unique và B-tree friendly.

  • Natural key: dữ liệu thực có business meaning — email, SSN, ISBN, phone.
  • Ưu: không cần join thêm để lấy giá trị, tự nhiên unique.
  • Nhược: có thể thay đổi (user đổi email → phải update tất cả FKs), có thể không unique thực tế (SSN có thể duplicate do lỗi), lộ business data trong URLs.
  • Surrogate key: ID nhân tạo không có business meaning — SERIAL/BIGSERIAL (auto-increment integer), UUID.
  • Ưu: immutable (không bao giờ thay đổi), stable FK references, không lộ business info, đơn giản.
  • Nhược: cần thêm UNIQUE constraint trên natural key nếu cần enforce uniqueness.
  • Best practice: luôn dùng surrogate PK + UNIQUE constraint trên natural key nếu cần — id BIGSERIAL PRIMARY KEY, email TEXT UNIQUE NOT NULL.
  • Candidate key: bất kỳ column/set nào có thể làm PK (unique + not null) — chọn một làm PK, còn lại add UNIQUE constraint.

Auto-increment (SERIAL/BIGSERIAL): 4/8 bytes, sequential → tốt cho B-tree index locality, dễ debug, nhưng lộ business info (competitors đoán số records), không an toàn cho distributed systems (cần central counter).

  • UUID v4: 128-bit random, globally unique không cần coordination — perfect cho microservices/distributed; nhưng random → B-tree index fragmentation (random inserts vào random positions → page splits, bloat), 16 bytes (4x lớn hơn int), khó đọc/debug.
  • UUID v7 (RFC 9562, 2024): time-ordered UUID — 48-bit Unix timestamp milliseconds prefix + random suffix → sequential như auto-increment nhưng globally unique.
  • Lợi ích v7: B-tree friendly (sequential inserts → không fragmentation), globally unique, có thể sort by creation time, không lộ total count.
  • PostgreSQL 17+ có gen_random_uuid() v4 built-in; v7 cần extension pg_uuidv7 hoặc generate ở app layer (uuidv7 npm package).
  • CUID2/NanoID: alternatives — shorter, URL-safe, random nhưng không có timestamp prefix.
  • Khi nào dùng UUID: public-facing IDs (URLs, APIs — tránh enumeration attacks), distributed systems, IDs cần generate ở client/app trước khi DB insert.
  • Khi dùng BIGINT: internal tables không expose ra ngoài, performance-critical tables với many JOINs.

Chọn PostgreSQL khi: data có quan hệ rõ ràng, cần ACID transactions, schema cố định, complex queries với JOINs.

  • PostgreSQL còn có JSONB type — lưu JSON với GIN index, query JSON fields nhanh — là middle ground khi cần vừa relational vừa flexible.
  • Chọn MongoDB khi: data có cấu trúc variable/nested, cần horizontal scaling đơn giản, document-centric (content, catalog, user profiles).
  • Performance: MongoDB nhanh hơn cho document retrieval không cần JOINs; PostgreSQL mạnh hơn cho complex aggregation nhiều bảng.
  • Ecosystem: Mongoose (ODM) cho MongoDB vs Prisma/TypeORM cho PostgreSQL.
  • Decision criteria thực tế: nếu team đã quen SQL, cần transactions phức tạp, hoặc data có many-to-many relations — chọn PostgreSQL; nếu cần schema evolution nhanh, team Node.js, data hierarchy sâu — chọn MongoDB.

Pitfall: nhiều team chọn MongoDB vì 'flexible' rồi implement manual joins trong code — tệ hơn cả SQL.

ORM là layer ánh xạ database tables thành objects trong code, cho phép thao tác database bằng programming language thay vì SQL.

  • Hai patterns chính: Active Record (model tự chứa DB logic, ví dụ Rails, Sequelize) và Data Mapper (tách domain object khỏi DB logic, ví dụ TypeORM DataMapper mode, Prisma).
  • Lợi ích: type-safety, migration management (version-controlled schema changes), database abstraction (switch DB dễ hơn), boilerplate reduction.
  • Nhược điểm: abstraction leaky — ORM generate SQL không optimal, ví dụ Prisma đôi khi tạo subquery thay vì JOIN hiệu quả hơn; N+1 query problem nếu không cẩn thận; khó debug khi cần hiểu SQL được generate; overhead runtime.
  • Raw query escape hatch quan trọng: Prisma có $queryRaw, Sequelize có sequelize.query() — nên dùng cho queries phức tạp cần kiểm soát.
  • Migration management: Prisma Migrate, Flyway, Liquibase giúp track và apply schema changes theo đúng thứ tự.

Prisma là ORM được khuyến nghị cho Node.js/TypeScript — auto-generated type-safe client từ schema, Rust query engine, Prisma Migrate cho versioned migrations; prisma generate bắt buộc sau mỗi schema change.

  • Prisma là ORM thế hệ mới cho Node.js/TypeScript với schema-first approach.
  • Điểm mạnh: Prisma Client auto-generated từ schema.prisma — mỗi lần chạy prisma generate tạo ra TypeScript types hoàn toàn accurate với database schema; Prisma Migrate cho migrations với SQL migration files có thể review; Prisma Studio là GUI để browse data.
  • Internals quan trọng: Prisma dùng query engine viết bằng Rust (binary được download khi install) — giúp performance tốt và type-safe query builder.
  • Preview features như Prisma Accelerate (connection pooling + query caching ở edge) và Prisma Pulse (real-time database events).
  • So với Sequelize/TypeORM: Prisma có DX tốt hơn, type safety mạnh hơn (không cần decorators), nhưng không hỗ trợ tốt complex inheritance patterns.
  • Khi schema thay đổi, phải chạy prisma generate để sync TypeScript types — bước quan trọng trong CI/CD pipeline.

schema.prisma là single source of truth cho Prisma — ba sections: datasource, generator, models; @relation cho FK, @map cho tên column khác, prisma migrate dev tạo SQL migration reviewable. schema.prisma là single source of truth với ba sections: datasource (kết nối DB), generator (config Prisma Client), và models.

  • Relations syntax: @relation('PostToUser', fields: [authorId], references: [id]) cho foreign key; implicit M2M relations dùng junction table tự động.
  • Enums được định nghĩa trực tiếp và map đến DB enums hoặc strings. @map/@db directives quan trọng: @map('user_name') đổi field name trong DB trong khi giữ camelCase trong code; @db.VarChar(255) chỉ định DB-specific type.
  • Multi-schema support (preview): datasource db { schemas: ['public', 'auth'] } cho multi-tenant apps. prisma format tự động format và sort fields. prisma migrate dev tạo SQL migration files có thể review và edit trước khi apply — quan trọng khi cần migration phức tạp như data migration.

N+1 problem là bug hiệu năng phổ biến nhất với ORM: query danh sách N items rồi loop lấy related data = N+1 queries — giải quyết bằng include (eager load) trong Prisma hoặc DataLoader pattern cho GraphQL.

  • N+1 problem: query 1 lần lấy list N items (1 query), rồi loop qua từng item để fetch related data (N queries) — tổng N+1 queries.

Ví dụ: lấy 100 users rồi lấy posts của từng user = 101 queries thay vì 1.

Sequelize là ORM legacy cho Node.js — DX và type safety kém hơn Prisma; chỉ dùng khi maintain legacy codebase hoặc cần polymorphic associations mà Prisma chưa hỗ trợ.

  • Sequelize là ORM lâu đời cho Node.js, hỗ trợ MySQL, PostgreSQL, SQLite, MSSQL, MariaDB.
  • Migration system: sequelize-cli tạo migration files với up/down functions — mạnh nhưng verbose.
  • Hooks (lifecycle events): beforeCreate, afterCreate, beforeUpdate, beforeDestroy — dùng cho audit logging, password hashing.
  • Scopes: defaultScope (applied to all queries), named scopes như User.scope('active') — tái sử dụng query conditions.
  • Transaction support: sequelize.transaction(async (t) => { await Model.create({...}, { transaction: t }) }).
  • Tại sao teams migrate away: TypeScript types không accurate (cần sequelize-typescript), verbose model definitions với decorators, runtime errors khó debug, DX kém so với Prisma.
  • Nên dùng Sequelize khi maintain legacy codebase hoặc cần tính năng như polymorphic associations mà Prisma chưa hỗ trợ.

Mongoose thêm schema, validation, và middleware (hooks) lên MongoDB — dùng lean() cho read-only queries (2-3x nhanh hơn), populate() cho references nhưng cẩn thận N+1.

  • Mongoose là ODM (Object Document Mapper) cho MongoDB trong Node.js, giúp thêm structure vào schemaless MongoDB.
  • Middleware (hooks) có 4 loại: document middleware (pre/post save, validate, remove — this là document), query middleware (pre/post find, findOne, update — this là query), aggregate middleware (pre/post aggregate), model middleware.
  • Virtuals: computed properties không lưu trong DB — ví dụ fullName computed từ firstName + lastName, hay URL computed từ slug; có thể include trong JSON với toJSON({ virtuals: true }).
  • Populate: thay thế ObjectId references bằng actual documents — populate('author') fetch User document; nhưng cẩn thận N+1, dùng populate với select để limit fields. lean(): trả về plain JavaScript objects thay vì Mongoose documents — nhanh hơn 2-3x vì bỏ overhead của Mongoose document methods; dùng khi chỉ cần read-only data.

Pitfall: Mongoose buffering commands trước khi kết nối — cần handle connection errors properly.

Redis là in-memory data store đa dụng — Sorted Set cho leaderboard, List cho queue, Pub/Sub cho real-time, Streams cho event sourcing; RDB+AOF kết hợp cho persistence tốt nhất.

  • Redis là in-memory data store với rich data structures và persistence tùy chọn.
  • Data structure use cases cụ thể: Sorted Set (ZADD, ZRANGE) cho leaderboard — score tự động sorted, query top-N O(log N); Pub/Sub (SUBSCRIBE, PUBLISH) cho real-time notifications và chat; List (LPUSH/RPOP) cho job queue hoặc activity feed; HyperLogLog cho approximate unique count với fixed memory; Streams (XADD, XREAD) cho event sourcing, message queue có consumer groups.
  • Persistence: RDB (snapshot tại intervals — nhanh hơn cho restart, có thể mất data nếu crash giữa snapshots); AOF (Append-Only File — log mọi write operation, recovery tốt hơn nhưng file lớn hơn); RDB+AOF kết hợp cho best of both worlds.
  • Redis Cluster: horizontal sharding tự động với 16384 hash slots, minimum 3 master nodes — đảm bảo availability khi node fail.
  • Redis Sentinel: high availability cho single-master setup với automatic failover.

Cache-aside là pattern phổ biến nhất; cache stampede giải quyết bằng mutex lock; maxmemory-policy allkeys-lru cho pure cache; target hit rate >80%.

  • Dùng Redis cache khi: data đọc nhiều hơn ghi, computation expensive (complex DB queries, API calls), cần reduce database load, data có thể stale trong thời gian ngắn.
  • Chiến lược: Cache-aside/Lazy loading (check cache → miss → fetch DB → write cache); Write-through (write cache + DB đồng thời — consistent nhưng write latency cao); Write-behind/Write-back (write cache → async write DB — nhanh nhưng risk data loss).
  • Cache stampede problem: nhiều requests hit cache cùng lúc khi cache expire, all go to DB — giải quyết bằng mutex lock (SET key value NX EX 30 để lock), hoặc probabilistic early expiration (expire sớm với xác suất nhỏ trước TTL thật).
  • Memory management: maxmemory-policy quan trọng — allkeys-lru (evict least-recently-used) cho cache, volatile-lru (chỉ evict keys có TTL), noeviction (trả error khi full — dùng cho session storage).
  • Monitoring: redis-cli INFO stats để xem hit rate, connected clients, memory usage; target cache hit rate >80% là healthy.

Drizzle là TypeScript ORM nhẹ với SQL-like query builder — viết queries gần với SQL nhưng type-safe: db.select().from(users).where(eq(users.age, 25)).orderBy(desc(users.createdAt)).

  • Schema định nghĩa bằng TypeScript với type inference tự động: const users = pgTable('users', { id: serial('id').primaryKey(), name: text('name').notNull() }).
  • Prepared statements: db.select().from(users).where(eq(users.id, sql.placeholder('id'))).prepare('getUserById') — compile một lần, execute nhiều lần với different params, nhanh hơn.
  • Migration generation: drizzle-kit generate tạo SQL migration từ schema changes, drizzle-kit push để apply.
  • Edge runtime compatibility: không có native binary như Prisma engine — bundle size nhỏ hơn nhiều (~100KB vs Prisma ~40MB), chạy tốt trên Cloudflare Workers, Vercel Edge.
  • Performance: benchmark cho thấy Drizzle nhanh hơn Prisma 2-5x cho simple queries do ít abstraction layers.
  • Hạn chế: DX kém hơn Prisma (verbose hơn), community nhỏ hơn, một số advanced features còn thiếu.

Embedding cho 1:1/1:few data truy cập cùng nhau (1 query, nhanh); referencing cho M:N, data update độc lập; giới hạn 16MB/document — embed quá nhiều sẽ hit limit.

  • Embedding (nested document): dùng khi data được truy cập cùng nhau thường xuyên (one query, no joins), quan hệ 1:1 hoặc 1:few, data ít thay đổi, subdocuments không cần exist độc lập.
  • Giới hạn quan trọng: MongoDB document size limit là 16MB — embedding quá nhiều dẫn đến hit limit.
  • Referencing (separate documents với ObjectId): dùng khi data lớn, quan hệ many-to-many, data được truy cập/update độc lập — nhưng cần $lookup (LEFT JOIN) hoặc application-side join, tốn thêm round trip.
  • Data duplication trade-off: embedding duplicate data (ví dụ user info trong mỗi order document) — read nhanh hơn nhưng update tốn công (phải update nhiều documents).
  • Schema design patterns: Bucket pattern — nhóm time-series data vào buckets (ví dụ nhóm sensor readings theo giờ thay vì một document per reading) để giảm document count; Outlier pattern — handle edge cases riêng khi 90% documents nhỏ nhưng 10% có thể rất lớn (ví dụ celebrity posts có millions of likes). $lookup performance: thêm index trên lookup field, tránh $lookup trong large collections không có index.

Type-safe ORM cho Node.js/TypeScript. Schema-first: định nghĩa models trong schema.prisma → generate client. Auto-complete, type checking cho queries.

Migration tự động. Hỗ trợ PostgreSQL, MySQL, SQLite, MongoDB. Dự án enterprise mới thường chọn Prisma thay Sequelize.