MySQL

Câu hỏi phỏng vấn MySQL

65+ câu hỏi phỏng vấn MySQL cho dev backend / fullstack — InnoDB, gap lock, replication, optimizer, MySQL 8.4 & 9.7 LTS.

A. Tổng quan

1. MySQL 8.4 và 9.7 LTS có gì mới?

8.4 LTS (2024):

  • Hypergraph Optimizer (DPhyp) — JOIN-heavy nhanh 2-5× (Enterprise only).
  • mysql_native_password disabled mặc định.
  • SOURCE/REPLICA terminology thay MASTER/SLAVE.
  • MySQL Operator for Kubernetes production-ready.

9.7 LTS (2025):

  • Hypergraph Optimizer vào Community Edition.
  • PBKDF2 + SHA-512 authentication, downward replication.
  • In-database JavaScript, cpuset cgroup support trong InnoDB.
  • DML trên JSON Duality Views trong Community.
  • Dynamic Data Masking, OpenID Connect native, MySQL REST Service.
  • Nhiều tính năng từ Enterprise chuyển xuống Community (xem câu 2).

2. 9.7 LTS có thêm gì chi tiết?

  • Hypergraph Optimizer vào Community: optimizer DPhyp — JOIN-heavy query 2-5× nhanh hơn, trước chỉ có trong Enterprise.
  • PBKDF2 + SHA-512 authentication: hashing mạnh hơn caching_sha2_password — configurable iteration count, chống brute-force và GPU attack hiệu quả hơn.
  • Downward replication: replica từ version cao hơn về version thấp hơn (VD: 9.7 → 8.4) — rolling upgrade an toàn hơn, hỗ trợ topology hỗn hợp.
  • In-database JavaScript: viết stored procedure/function bằng JavaScript (GraalVM engine) — business logic phức tạp, JSON manipulation không cần SQL/PSM.
  • DML trên JSON Duality Views trong Community: INSERT/UPDATE/DELETE qua JSON document view — trước chỉ có trong Enterprise.
  • cpuset cgroup support trong InnoDB: bind thread vào CPU core cụ thể — CPU isolation, NUMA-aware, multi-instance MySQL trên cùng máy.
  • InnoDB FULLTEXT optimization: cải thiện performance FULLTEXT search.
  • Dynamic Data Masking: ẩn dữ liệu nhạy cảm (PII) theo role — trước chỉ Enterprise.
  • OpenID Connect (OIDC) native: login qua Google, Azure AD, Okta không cần plugin ngoài.
  • MySQL REST Service (MRS): expose database qua REST API.

3. LTS vs Innovation release?

  • LTS: hỗ trợ 8 năm, fix bug + security.
  • Innovation: quý cập nhật feature mới, support ngắn.

Production → LTS. Dev/test → Innovation OK.

4. Storage engine — chọn cái nào?

InnoDB mặc định. MyISAM legacy, không transaction, không FK, table-level lock. Luôn dùng InnoDB.

5. utf8 vs utf8mb4?

utf8 cũ chỉ 3 byte (không emoji, một số ký tự CJK). utf8mb4 đủ 4 byte chuẩn UTF-8. Luôn dùng utf8mb4.

B. Kiểu dữ liệu

6. DATETIME vs TIMESTAMP?

  • DATETIME: 5-8 byte, range 1000-9999, không convert timezone.
  • TIMESTAMP: 4-7 byte, range 1970-2038 (Y2038!), convert UTC ↔ session.

DATETIME cho ngày sinh, sự kiện. TIMESTAMP cho created_at cần timezone aware.

7. VARCHAR vs TEXT trong MySQL?

  • VARCHAR(n): lưu inline trong row, index full, fast.
  • TEXT: lưu out-of-row (con trỏ), index chỉ prefix.

VARCHAR(n) cho data < 1KB. TEXT cho bài viết dài.

8. INT(11) có giới hạn 11 chữ số không?

Không. Đó là "display width", không affect storage hay range. INT luôn 4 byte. Deprecated từ 8.0 — bỏ qua.

9. ENUM nên dùng không?

Hữu ích cho giá trị cố định nhỏ (status, type). Trade-off: thay đổi list cần ALTER TABLE (lock). Alternative: lookup table.

10. JSON type — index như nào?

Qua generated column + index trên nó, hoặc functional index (8.0.13+) trên expression.

C. DDL

11. AUTO_INCREMENT khác SERIAL Postgres thế nào?

Tương đương về mục đích. MySQL AUTO_INCREMENT chỉ áp được cho 1 cột/bảng, và cột phải có index.

12. CHECK constraint trước 8.0?

Trước 8.0 MySQL parse CHECK nhưng không enforce (chỉ ignore). Từ 8.0 mới enforce thực sự.

13. ON UPDATE CURRENT_TIMESTAMP?

updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

Auto-update timestamp khi row UPDATE. Đặc thù MySQL — Postgres/SQL Server cần trigger.

14. Generated column VIRTUAL vs STORED?

  • VIRTUAL: tính khi đọc, không tốn space.
  • STORED: lưu vật lý, index trực tiếp tốt.

Index trên VIRTUAL được nhưng phải re-evaluate expression mỗi lần — STORED nhanh hơn.

15. Invisible index?

Index tồn tại nhưng optimizer không dùng. Dùng để test trước khi DROP — đỡ overhead drop/recreate.

D. DML

16. UPSERT trong MySQL?

INSERT ... ON DUPLICATE KEY UPDATE. Hoặc REPLACE INTO (= DELETE + INSERT, không khuyến nghị vì xoá row gốc).

17. INSERT IGNORE làm gì?

Skip row vi phạm UNIQUE constraint thay vì error. Có thể giấu lỗi khác — dùng cẩn thận.

18. LAST_INSERT_ID() vs LAST_INSERT_ID(value)?

  • LAST_INSERT_ID(): return ID vừa insert (per-connection).
  • LAST_INSERT_ID(value): set + return giá trị (trick để return value từ UPDATE).

19. UPDATE/DELETE từ JOIN — cú pháp?

UPDATE a JOIN b ON ... SET a.col = b.col;
DELETE a FROM a JOIN b ON ... WHERE ...;

20. TRUNCATE có CASCADE không?

Không như Postgres. MySQL TRUNCATE không trigger CASCADE và bị block nếu có FK reference.

E. JOIN & query

21. MySQL có FULL OUTER JOIN không?

Không. Phải mô phỏng bằng LEFT JOIN UNION RIGHT JOIN.

22. STRAIGHT_JOIN dùng làm gì?

Ép optimizer giữ thứ tự JOIN theo cách viết, không reorder.

23. Derived table có cần alias?

Có. MySQL bắt buộc. SELECT * FROM (SELECT ...) AS t.

24. CTE từ version nào?

8.0+. Trước phải nested subquery (xấu xí).

25. Window function từ version nào?

8.0+. Trước phải user variable trick — bug-prone.

26. NULL-safe equal (<=>)?

a <=> b trả TRUE nếu cả 2 NULL. Khác = (NULL = NULL → NULL/UNKNOWN).

27. REGEXP vs LIKE?

  • LIKE: wildcard %, _. Đơn giản.
  • REGEXP / RLIKE: regex Perl-like.

REGEXP chậm hơn, không dùng index trừ khi anchor ^.

F. Index

28. InnoDB secondary index — chứa gì ở leaf?

Chứa secondary key + primary key value (chứ không phải row pointer). Hệ quả: query qua secondary → lookup PK → row. Đó là vì sao PK nên nhỏ (BIGINT thay vì UUID dài).

29. Composite index left-most prefix?

INDEX(A, B, C) dùng cho WHERE A, A+B, A+B+C. Không dùng cho B mình hoặc B+C.

30. Covering index?

Index chứa đủ cột query cần — engine không cần lookup PK/row. Quan trọng đặc biệt với InnoDB vì cấu trúc secondary index.

31. Functional index từ version nào?

8.0.13+. Cho phép index trên expression. Trước phải tạo generated column.

32. Descending index?

8.0+ — index lưu DESC. Query ORDER BY DESC dùng index thẳng, tránh SORT.

33. Khi nào KHÔNG nên tạo index?

Bảng nhỏ, cột thay đổi nhiều, selectivity thấp, bảng heavy write.

G. Transaction & Locking

34. Default isolation MySQL?

REPEATABLE READ — cao hơn SQL Server / Postgres (cả 2 mặc định READ COMMITTED). Chọn vậy do replication: statement-based binlog cần consistent read.

35. Gap lock là gì?

InnoDB ở REPEATABLE READ lock cả khoảng giữa các index value, ngăn INSERT phantom. Hiệu ứng phụ: dễ deadlock hơn.

36. Next-key lock?

Combination của record lock + gap lock — lock cả row và gap trước nó.

37. SELECT FOR UPDATE vs FOR SHARE?

  • FOR UPDATE: exclusive lock, chặn UPDATE + FOR UPDATE khác.
  • FOR SHARE (LOCK IN SHARE MODE cũ): shared lock, cho FOR SHARE khác đọc, chặn UPDATE.

38. Deadlock — InnoDB xử lý?

Tự detect, rollback transaction có ít work nhất. App phải retry. Tránh: lock cùng thứ tự, transaction ngắn.

39. Auto-commit?

Mặc định ON — mỗi statement = 1 transaction. SET AUTOCOMMIT = 0 để control thủ công.

40. Phantom read trong MySQL REPEATABLE READ?

Lý thuyết: REPEATABLE READ vẫn cho phantom. Thực tế: InnoDB dùng gap lock trong RR → ngăn phantom. → MySQL RR ≈ Postgres RR ≈ Serializable phần phantom.

H. Performance

41. Buffer Pool là gì?

Vùng RAM InnoDB cache page (data + index). Tham số innodb_buffer_pool_size — quan trọng nhất, set 50-70% RAM.

42. Redo log + Undo log?

  • Redo log: ghi mọi thay đổi trước khi flush data → crash recovery.
  • Undo log: lưu version cũ cho MVCC + ROLLBACK.

43. EXPLAIN — type cột nào quan trọng?

type cho thấy access pattern. Order tốt → tệ: system > const > eq_ref > ref > range > index > ALL (full scan, đáng sợ).

44. Slow query log — cách bật?

slow_query_log = 1
long_query_time = 1
log_queries_not_using_indexes = 1

Phân tích bằng mysqldumpslow hoặc pt-query-digest.

45. Hypergraph Optimizer?

Optimizer mới dùng DPhyp algorithm — JOIN-heavy analytics 2-5× faster. Từ 8.4 chỉ có Enterprise, từ 9.7 đã có trong Community. Bật qua optimizer_switch='hypergraph_optimizer=on'.

Xem chi tiết tại câu 58.

46. Pagination với OFFSET lớn chậm?

LIMIT 1000000, 10 phải scan + skip 1M row. Fix bằng keyset pagination:

SELECT * FROM orders WHERE id > @last_id ORDER BY id LIMIT 10;

I. Replication & HA

47. Source-Replica replication async vs semi-sync?

  • Async (default): source commit ngay, không chờ → có thể mất data nếu source chết.
  • Semi-sync: chờ ít nhất 1 replica ack.

48. Binlog format ROW vs STATEMENT vs MIXED?

  • ROW: log từng row thay đổi. Safest, lớn nhất. Khuyến nghị.
  • STATEMENT: log statement. Nhỏ nhưng nondeterministic statement (RAND, NOW) bug.
  • MIXED: tự chọn.

49. Group Replication / InnoDB Cluster?

Multi-master synchronous, dùng Paxos. Auto-failover. Phức tạp hơn nhưng HA tốt hơn.

50. Read-after-write consistency với read replica?

Replica có lag → read ngay sau write có thể stale. Fix: route critical read về source, hoặc dùng semi-sync, hoặc proxy như ProxySQL có read consistency hint.

J. Trick & scenario

51. Bảng 100M row, query slow. Bước đầu tiên?

EXPLAIN xem type. Tìm ALL → thiếu index. Check key — index nào đang dùng? rows — estimate. Sau đó check Extra: Using filesort / Using temporary đáng ngờ.

52. ALTER TABLE bảng lớn không downtime?

  • Online DDL (ALGORITHM=INPLACE, LOCK=NONE) cho operation support.
  • pt-online-schema-change (Percona Toolkit) — tạo bảng mới, copy data, swap.
  • gh-ost (GitHub) — không trigger, ít load hơn.

53. Bảng InnoDB to nhưng SELECT COUNT(*) chậm?

InnoDB không cache count chính xác (do MVCC). Mỗi COUNT(*) phải scan. Cách:

  • Cache count trong app/Redis.
  • Counter table riêng + trigger.
  • information_schema.tables.TABLE_ROWS (estimate, không chính xác).

54. PK là UUID — có sao không?

InnoDB tệ cho UUIDv4 random:

  • Insert vào B-tree random → page split nhiều.
  • Mọi secondary index chứa UUID dài → index phình to.

Khuyến nghị: UUIDv7 (sortable), hoặc BIGINT AUTO_INCREMENT + UUID là column phụ.

55. Replication lag tăng — debug?

  • Check SHOW REPLICA STATUS\GSeconds_Behind_Source.
  • Replica single-threaded apply mặc định → cấu hình parallel applier.
  • Long transaction trên source → tách nhỏ.

56. Connection pool — bao nhiêu là vừa?

max_connections default 151. Mỗi connection ~256KB RAM. App pool size = (cpu_count × 2) + effective_spindle_count thường đủ. Hơn nữa → tốn RAM, contention.

57. Bảo mật — checklist?

  • mysql_secure_installation.
  • Không expose port 3306 ra internet.
  • User ít quyền nhất.
  • TLS cho connection.
  • Audit log cho production.
  • caching_sha2_password (8.0+ default) — không revert về native.

K. MySQL 9.7 LTS — Tính năng mới

58. Hypergraph Optimizer là gì? Làm sao bật? Khác optimizer cũ ra sao?

Hypergraph Optimizer là optimizer mới của MySQL, dùng thuật toán DPhyp (Dynamic Programming Hypergraph) thay cho thuật toán greedy truyền thống.

Khác biệt chính:

  • Optimizer cũ: tìm kiếm greedy, chỉ sinh left-deep join tree. Nhanh với query đơn giản nhưng dễ bỏ lỡ join order tối ưu cho query phức tạp (>4 bảng).
  • Hypergraph: exhaustive DP search, hỗ trợ bushy join tree. Tìm được join order tối ưu cho JOIN-heavy query, đặc biệt analytical workload.

Cách bật:

-- Per-session
SET optimizer_switch='hypergraph_optimizer=on';

-- Hint per-query
SELECT /*+ SET_VAR(optimizer_switch='hypergraph_optimizer=on') */ ...
FROM a JOIN b JOIN c JOIN d ...;

Performance: JOIN-heavy query 2-5x nhanh hơn, nhưng compile time dài hơn (search space lớn hơn). Query đơn giản không thấy khác biệt — không nên bật global cho OLTP.

Lưu ý: Từ 8.4 chỉ có Enterprise, từ 9.7 đã có trong Community Edition.

59. MySQL 9.7 Community được thêm những tính năng gì từ Enterprise?

Từ 9.7 LTS, Oracle chuyển nhiều tính năng từ Enterprise Edition sang Community Edition miễn phí:

  • Hypergraph Optimizer: optimizer mới dùng DPhyp — JOIN-heavy query 2-5x nhanh hơn.
  • Dynamic Data Masking: ẩn dữ liệu nhạy cảm (PII) theo role mà không cần thay đổi application code.
  • MySQL REST Service (MRS): expose database schema qua REST API tự động.
  • OpenID Connect (OIDC) authentication: login qua Google, Azure AD, Okta không cần plugin ngoài.
  • In-database JavaScript: viết stored procedure/function bằng JavaScript (GraalVM).
  • DML trên JSON Duality Views: INSERT/UPDATE/DELETE qua JSON document view — full document-model API.
  • PBKDF2 + SHA-512 authentication plugin: password hashing mạnh hơn caching_sha2_password.
  • Downward Replication: replica từ version cao xuống version thấp.

Đây là chiến lược lớn của Oracle — đưa MySQL Community cạnh tranh trực tiếp với PostgreSQL về mặt tính năng miễn phí.

60. Downward replication trong MySQL 9.7 là gì?

Downward replication cho phép replica từ MySQL version cao hơn về version thấp hơn — ví dụ source 9.7 → replica 8.4.

Trước 9.7, replication chỉ hoạt động cùng version hoặc upward (cũ → mới). Downward replication giải quyết các scenario thực tế:

  • Rolling upgrade an toàn: upgrade replica lên version mới trước, nếu có vấn đề vẫn replicating từ source cũ về — không downtime.
  • Topology hỗn hợp: source chạy Innovation release (feature mới nhất), replica chạy LTS cũ hơn cho ổn định production.
  • Migration dần: chuyển từng phần hệ thống lên version mới, không cần big-bang upgrade.

Cơ chế: bắt buộc binlog_format=ROW. Replica hiểu các event từ source qua row-based format — statement-based không dùng được vì syntax khác version.

Hạn chế: Không replicate nếu source dùng feature không tồn tại ở version cũ (VD: JavaScript stored procedure từ 9.7 → 8.4 sẽ lỗi).

61. PBKDF2 + SHA-512 authentication có gì hơn caching_sha2_password?

So sánh hai plugin authentication:

caching_sha2_passwordPBKDF2 + SHA-512
Default từMySQL 8.0MySQL 9.7
Hash algorithmSHA-256 (2 rounds)PBKDF2 với SHA-512
Iterations5000 rounds cố địnhConfigurable (mặc định 10000)
Output length256-bit512-bit
Chống brute-forceTốtTốt hơn (nhiều iteration × SHA-512)
Chống GPU/ASICTrung bìnhTốt (memory-hard hơn)
CachingCó cache sessionKhông cache (luôn verify)

PBKDF2 (Password-Based Key Derivation Function 2 — chuẩn NIST) tốt hơn vì:

  • Mỗi iteration nhân chi phí cho attacker — 10000 vòng SHA-512 nghĩa là attacker cần GPU/ASIC mạnh gấp nhiều lần để crack.
  • SHA-512 output dài hơn → collision resistance tốt hơn SHA-256.
  • Có thể tăng iteration count theo thời gian (Moore's law).

Cấu hình:

CREATE USER 'app_user'@'%' IDENTIFIED WITH authentication_pbkdf2 BY 'strong-password';

-- Tăng iteration count cho high-security system
SET GLOBAL authentication_pbkdf2_iterations = 20000;

Khuyến nghị: Dùng PBKDF2 cho application user cần high security (admin, finance). caching_sha2_password vẫn đủ dùng cho hầu hết web app thông thường — nhưng không revert về mysql_native_password.

62. In-database JavaScript trong MySQL 9.7 dùng làm gì?

MySQL 9.7 tích hợp GraalVM JavaScript engine, cho phép viết stored program bằng JavaScript thay vì SQL/PSM truyền thống.

Cú pháp:

CREATE FUNCTION calculate_discount(price DECIMAL(10,2), tier VARCHAR(20))
RETURNS DECIMAL(10,2) LANGUAGE JAVASCRIPT AS $$
  const rates = { bronze: 0.05, silver: 0.10, gold: 0.15 };
  const rate = rates[tier.toLowerCase()] || 0;
  return price * (1 - rate);
$$;

SELECT calculate_discount(100, 'gold'); -- 85.00

Stored procedure với JavaScript:

CREATE PROCEDURE classify_customers()
LANGUAGE JAVASCRIPT AS $$
  // Logic phân loại khách hàng dựa trên lịch sử mua hàng
  // Access SQL data thông qua built-in API
$$;

Use case thực tế:

  • Business logic phức tạp: tính thuế theo vùng, chiết khấu theo bậc, phân hạng khách hàng — logic dạng cây/đồ thị khó viết bằng SQL.
  • JSON manipulation: parse, transform, validate JSON document ngay trong DB — tận dụng native JSON của JS.
  • Data validation: validate format email, số điện thoại, mã số thuế theo regex phức tạp.
  • Custom aggregation: logic tổng hợp không có sẵn trong SQL (VD: median, percentile phức tạp).

Lợi ích: Developer dùng JS không cần học SQL/PSM. Tận dụng ecosystem JS cho data processing.

Hạn chế: Không hỗ trợ chạy SQL query từ bên trong JS code — chỉ compute logic trên tham số. Overhead khởi tạo GraalVM context (có cache nhưng lần đầu chậm hơn SQL function).

63. cpuset cgroup support trong InnoDB có ý nghĩa gì?

cpuset cgroup cho phép bind InnoDB thread vào CPU core cụ thể — quan trọng trong môi trường container, VM, hoặc multi-tenant.

Ý nghĩa thực tế:

  • CPU isolation: InnoDB thread không bị OS scheduler đẩy sang core khác → giảm cache miss, context switch.
  • NUMA-aware: trên server multi-socket, bind thread vào core cùng socket với RAM → giảm latency remote memory access đến 40-60%.
  • Predictable performance: trong container share CPU, đảm bảo MySQL không bị noisy neighbor ảnh hưởng.
  • Multi-instance MySQL: chạy nhiều instance trên cùng máy, mỗi instance bind vào core riêng → không đụng độ CPU.

Cấu hình (Linux):

# Tạo cgroup với CPU 0-7 cho MySQL
cgcreate -g cpuset:/mysql
echo "0-7" > /sys/fs/cgroup/cpuset/mysql/cpuset.cpus
echo "0" > /sys/fs/cgroup/cpuset/mysql/cpuset.mems

# Gán MySQL process vào cgroup
cgclassify -g cpuset:/mysql $(pidof mysqld)

Trong MySQL 9.7, InnoDB tự detect và tối ưu buffer pool allocation theo cgroup assignment. Không cần cấu hình thủ công innodb_numa_interleave như trước đây.

64. JSON Duality Views là gì? DML support trong Community có ý nghĩa gì?

JSON Duality Views cho phép truy cập cùng một bộ dữ liệu dưới hai dạng: relational (SQL table) và document (JSON) — không cần duplication.

-- Tạo Duality View từ bảng quan hệ
CREATE JSON DUALITY VIEW customer_dv AS
  customer {
    _id: id,
    name: name,
    orders: orders {
      _id: id,
      total: amount,
      date: created_at
    }
  };

-- Đọc như JSON document
SELECT json_doc FROM customer_dv WHERE json_doc.name = 'An';

-- INSERT qua JSON view (MỚI trong Community 9.7)
INSERT INTO customer_dv VALUES ('{
  "_id": 101,
  "name": "Vinh",
  "orders": [{"_id": 1, "total": 500, "date": "2026-01-01"}]
}');

-- UPDATE qua JSON view
UPDATE customer_dv d SET d.json_doc = JSON_SET(d.json_doc, '$.name', 'Vinh Updated')
WHERE d.json_doc._id = 101;

DML support trong Community có ý nghĩa lớn:

  • Trước 9.7, DML trên JSON Duality View chỉ có trong Enterprise Edition (tính năng thương mại, phải trả phí).
  • Từ 9.7, INSERT/UPDATE/DELETE qua Duality View có trong Community miễn phí — full document-model API không tốn tiền.
  • Cạnh tranh trực tiếp với MongoDB (document model) và PostgreSQL JSONB — MySQL giờ có cả SQL và NoSQL trong cùng một engine, không cần add-on.
  • App có thể: CRUD nhanh qua document API cho frontend + SQL query phức tạp cho report/analytics — cùng một data.

L. MySQL Index & Performance nâng cao

65. Các loại index trong MySQL?

MySQL hỗ trợ nhiều loại index, mỗi loại cho mục đích khác nhau:

Loại indexEngineUse case chính
B-treeInnoDB (mặc định)=, >, <, BETWEEN, LIKE 'abc%', ORDER BY
FULLTEXTInnoDB (5.6+)Tìm kiếm văn bản MATCH(...) AGAINST(...)
SPATIAL (R-tree)InnoDB (5.7+)Dữ liệu địa lý (GIS, POINT, POLYGON)
HashMEMORY, NDBChỉ =, <=> (không range scan)
DescendingInnoDB (8.0+)ORDER BY col DESC dùng index thẳng
FunctionalInnoDB (8.0.13+)Index trên expression/function
InvisibleInnoDB (8.0+)Test trước khi DROP index an toàn
PrefixInnoDBIndex N ký tự đầu của cột dài (VARCHAR/TEXT)
CompositeTất cảIndex nhiều cột — left-most prefix rule
UniqueTất cảEnforce uniqueness + index
Primary KeyInnoDBClustered index — data lưu vật lý theo PK
Adaptive HashInnoDB (tự động)Tự sinh hash index cho hot page trong Buffer Pool

Thực tế: 95% trường hợp cần B-tree composite index là đủ. FULLTEXT cho search engine trong app. SPATIAL cho map/location app.

66. FULLTEXT index với ngram parser — dùng cho tiếng Việt thế nào?

FULLTEXT index mặc định dùng whitespace để tách từ — chỉ hoạt động tốt với ngôn ngữ Latin có khoảng trắng giữa các từ (Anh, Pháp). Không hoạt động đúng với tiếng Việt, Trung, Nhật, Hàn vì parser không hiểu cách tách từ.

Giải pháp: ngram parser — tokenizer theo n-gram (cặp ký tự liên tiếp), không phụ thuộc vào whitespace.

-- Tạo FULLTEXT index với ngram parser
ALTER TABLE articles
  ADD FULLTEXT INDEX ft_title_content (title, content) WITH PARSER ngram;

-- Tìm kiếm tiếng Việt
SELECT *, MATCH(title, content) AGAINST('lập trình viên' IN BOOLEAN MODE) AS score
FROM articles
WHERE MATCH(title, content) AGAINST('lập trình viên' IN BOOLEAN MODE)
ORDER BY score DESC;

Cấu hình ngram_token_size:

# my.cnf
ngram_token_size = 2  # mặc định. 2 = bigram. Giá trị nhỏ → index to hơn, tìm chi tiết hơn.

So với LIKE:

LIKE '%keyword%'FULLTEXT + ngram
IndexKhông dùngDùng index
Tốc độFull scan — chậmNhanh
RelevanceKhôngScore ranking
BooleanKhông+required -excluded

Hạn chế: Index lớn hơn B-tree (lưu tất cả bigram). Boolean mode có thể không chính xác bằng dedicated search engine (Elasticsearch). Stopword vẫn áp dụng — tắt bằng innodb_ft_enable_stopword = OFF.

67. Prefix index — khi nào dùng?

Prefix index chỉ index N ký tự đầu của cột, thay vì toàn bộ giá trị. Dùng cho cột dài (VARCHAR > 255, TEXT, BLOB) để tiết kiệm không gian.

-- Index 20 ký tự đầu của title
CREATE INDEX idx_title_prefix ON articles(title(20));

-- Tìm độ dài prefix tối ưu (trade-off size vs selectivity)
SELECT
  COUNT(DISTINCT LEFT(title, 10)) / COUNT(*) AS sel_10,
  COUNT(DISTINCT LEFT(title, 20)) / COUNT(*) AS sel_20,
  COUNT(DISTINCT LEFT(title, 30)) / COUNT(*) AS sel_30
FROM articles;
-- Chọn N nhỏ nhất mà selectivity ≈ 1

Khi nào dùng:

  • Cột VARCHAR/TEXT dài (> 255 ký tự) nhưng query thường chỉ tìm prefix (VD: tìm theo đầu email, URL).
  • Cần index nhưng không muốn index phình to (index lớn → RAM, disk, write performance).
  • Cột BLOB — bắt buộc phải dùng prefix (BLOB không index full được).

Hạn chế:

  • Không dùng cho ORDER BY, GROUP BY.
  • Không dùng làm covering index.
  • Phải chọn độ dài prefix đủ selectivity — prefix quá ngắn → nhiều row match → index ít hiệu quả.

M. MySQL Stored Programs

68. Stored procedure vs function trong MySQL — khác nhau thế nào?

Đặc điểmProcedureFunction
Cách gọiCALL sp_name(params)SELECT fn_name(params)
Giá trị trả vềKhông bắt buộc (qua OUT param)Bắt buộc RETURN 1 giá trị
Tham sốIN / OUT / INOUTChỉ IN
TransactionCOMMIT, ROLLBACK đượcKhông được
Dùng trong SQLKhông (chỉ CALL)Được (SELECT, WHERE, JOIN...)
Side effectĐược phép (INSERT, UPDATE...)Khuyến nghị hạn chế
Use caseBatch process, business transactionTính toán, computed column

Ví dụ thực tế:

-- Procedure: xử lý đơn hàng (nhiều bước, có transaction)
DELIMITER $$
CREATE PROCEDURE process_order(IN p_user_id INT, IN p_amount DECIMAL(10,2))
BEGIN
  DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK;
  START TRANSACTION;
    INSERT INTO orders(user_id, amount) VALUES(p_user_id, p_amount);
    UPDATE users SET balance = balance - p_amount WHERE id = p_user_id;
  COMMIT;
END$$
DELIMITER ;

CALL process_order(1, 500.00);

-- Function: tính tổng đơn hàng (compute, dùng trong SELECT)
DELIMITER $$
CREATE FUNCTION order_total(p_user_id INT) RETURNS DECIMAL(10,2)
DETERMINISTIC READS SQL DATA
BEGIN
  DECLARE total DECIMAL(10,2);
  SELECT COALESCE(SUM(amount), 0) INTO total FROM orders WHERE user_id = p_user_id;
  RETURN total;
END$$
DELIMITER ;

SELECT name, order_total(id) AS total_spent FROM users;

Nguyên tắc chọn: Cần thay đổi data + transaction → procedure. Cần compute 1 giá trị + dùng trong query → function.

69. Trigger — use case thực tế?

Trigger là code tự động chạy trước hoặc sau INSERT/UPDATE/DELETE. Có 2 loại: FOR EACH ROW (per-row) và FOR EACH STATEMENT.

Các use case thực tế:

1. Audit log — ghi lại mọi thay đổi:

CREATE TRIGGER trg_audit_users AFTER UPDATE ON users
FOR EACH ROW
  INSERT INTO audit_log(table_name, row_id, changed_by, old_val, new_val, changed_at)
  VALUES('users', OLD.id, USER(),
    JSON_OBJECT('email', OLD.email, 'role', OLD.role),
    JSON_OBJECT('email', NEW.email, 'role', NEW.role),
    NOW());

2. Validate business rule phức tạp:

CREATE TRIGGER trg_check_balance BEFORE INSERT ON withdrawals
FOR EACH ROW
BEGIN
  DECLARE current_balance DECIMAL(10,2);
  SELECT balance INTO current_balance FROM users WHERE id = NEW.user_id;
  IF current_balance < NEW.amount THEN
    SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Số dư không đủ';
  END IF;
END$$

3. Soft-delete cascade:

CREATE TRIGGER trg_soft_delete_posts AFTER UPDATE ON users
FOR EACH ROW
  IF NEW.deleted_at IS NOT NULL AND OLD.deleted_at IS NULL THEN
    UPDATE posts SET deleted_at = NOW() WHERE user_id = NEW.id;
  END IF;

4. Tự động cập nhật cột derived:

CREATE TRIGGER trg_update_order_total AFTER INSERT ON order_items
FOR EACH ROW
  UPDATE orders
  SET total = total + (NEW.price * NEW.quantity)
  WHERE id = NEW.order_id;

Lưu ý quan trọng:

  • Trigger chạy trong cùng transaction với DML gốc → rollback được.
  • Không nên dùng trigger cho heavy logic — ảnh hưởng performance mọi DML.
  • Recursive trigger: MySQL giới hạn 1 level, không cho phép trigger tự gọi chính nó.
  • Debug khó — logic nằm ẩn trong DB, không visible trong application code. Cân nhắc dùng application-level hook thay cho trigger nếu logic phức tạp.

70. Event scheduler — dùng khi nào?

Event Scheduler là cron job trong MySQL — chạy SQL định kỳ không cần cron/systemd bên ngoài, không phụ thuộc OS.

Cấu hình:

-- Bật event scheduler (cần SUPER privilege)
SET GLOBAL event_scheduler = ON;

-- my.cnf để auto-start sau restart
-- event_scheduler = ON

Use case thực tế:

1. Dọn dữ liệu cũ:

CREATE EVENT clean_expired_sessions
  ON SCHEDULE EVERY 1 HOUR
  DO DELETE FROM sessions WHERE expired_at < NOW();

2. Aggregation định kỳ:

CREATE EVENT daily_sales_report
  ON SCHEDULE EVERY 1 DAY
  STARTS '2026-07-09 02:00:00'
  DO
    INSERT INTO daily_sales_summary (report_date, total_amount, order_count)
    SELECT CURDATE(), SUM(amount), COUNT(*) FROM orders
    WHERE created_at >= CURDATE();

3. Rotate partition:

CREATE EVENT monthly_partition_maintenance
  ON SCHEDULE EVERY 1 MONTH
  DO
  BEGIN
    -- Tạo partition cho tháng sau
    SET @sql = CONCAT('ALTER TABLE logs ADD PARTITION ...');
    PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
    -- Xóa partition cũ (> 6 tháng)
    ...
  END;

4. Event chạy một lần:

CREATE EVENT one_time_data_migration
  ON SCHEDULE AT '2026-07-15 03:00:00'
  DO
    INSERT INTO archive_orders SELECT * FROM orders WHERE created_at < '2025-01-01';

Giám sát và quản lý:

SHOW EVENTS;                              -- Liệt kê tất cả event
SELECT * FROM information_schema.EVENTS;  -- Chi tiết hơn
ALTER EVENT clean_expired_sessions DISABLE; -- Tạm dừng
DROP EVENT IF EXISTS clean_expired_sessions; -- Xóa

Lưu ý quan trọng:

  • Khi replication: event chạy trên source, statement replicate xuống replica. Trên replica set event_scheduler = OFF.
  • Nếu event chạy quá lâu → event sau bị skip (không queue). Cần giám sát thời gian chạy.
  • Sau MySQL restart mà event_scheduler = OFF trong my.cnf → event không chạy → nhớ set ON.

© 2026 .NET Fresher Guide. All rights reserved.

Về Trang Web

Hướng dẫn toàn diện để chuẩn bị phỏng vấn vị trí .NET Fresher với nội dung từ lý thuyết đến thực hành.