SQL

Em từng EXPLAIN để tối ưu query chưa ? Những chỉ số nào em hay xem ?

Dạ, câu này thực tế và thường gặp khi làm việc với MySQL/PostgreSQL trong các hệ thống backend. Em xin trả lời như sau:

Em đã dùng EXPLAIN để tối ưu query rồi.

EXPLAIN giúp em hiểu cách database engine thực thi câu SQL, từ đó:

  • Tối ưu chỉ số (index)

  • Hạn chế full table scan

  • Giảm thời gian và tài nguyên xử lý

📊 Các chỉ số em hay quan tâm nhất là:

1. type

  • Mức độ hiệu quả của truy vấn.

  • Ưu tiên: const, ref, range

  • Cảnh báo nếu thấy: ALL → tức là full table scan

2. key

  • Index nào được sử dụng trong truy vấn

  • Nếu null → có thể là thiếu index

3. rows

  • Số dòng ước tính cần đọc

  • Số càng lớn thì càng cần tối ưu

4. Extra

  • Thường sẽ thấy:

    • "Using index" → tốt

    • "Using where" → ổn

    • "Using temporary" / "Using filesort" → nên tối ưu lại

🛠️ Ví dụ thực tế em từng làm:

Trong 1 bảng orders có 1 truy vấn bị chậm khi filter theo user_idstatus.

  • Dùng EXPLAIN, thấy:

    • type: ALL

    • key: null

    • rows: rất cao (~1 triệu)

✅ Em tạo composite index: (user_id, status) → Sau đó EXPLAIN lại:

  • type: ref

  • key: idx_user_status

  • rows: giảm rõ rệt

💡 Kết luận:

EXPLAIN giúp em phát hiện các truy vấn không tối ưu, từ đó cải thiện hiệu năng hệ thống, đặc biệt với các bảng lớn hoặc hệ thống real-time.


Nếu query bị full table scan em sẽ xử lí như thế nào ?

Dạ, nếu em thấy truy vấn bị full table scan (thể hiện qua EXPLAIN với type = ALL), em sẽ xử lý theo các bước sau — trình bày rõ ràng như khi đi phỏng vấn:

1. Kiểm tra điều kiện WHERE

  • Em xem lại điều kiện trong WHERE có nằm trong column đã được index chưa.

  • Nếu chưa → tạo index phù hợp (thường là các cột lọc chính hoặc join).

Ví dụ:

SELECT * FROM orders WHERE customer_id = 100;
-- ⇒ tạo index:
CREATE INDEX idx_orders_customer ON orders(customer_id);

2. Dùng EXPLAIN lại để xác nhận

  • Sau khi tạo index, em chạy lại EXPLAIN để đảm bảo:

    • type chuyển từ ALLref, range, hoặc tốt hơn

    • key sử dụng đúng index mong muốn

3. Tránh SELECT *** nếu không cần

  • Chỉ lấy các cột cần thiết, giúp giảm I/O và tăng hiệu năng.

-- Tránh:
SELECT * FROM users;

-- Tốt hơn:
SELECT id, name FROM users;

4. Tối ưu JOIN (nếu có)

  • Kiểm tra các JOIN có sử dụng foreign key hoặc indexed columns không.

  • Dùng EXPLAIN để xem bảng nào được đọc trước và có sử dụng index không.

5. Phân mảnh bảng lớn (nếu cần)

  • Dùng partitioning cho bảng cực lớn

  • Hoặc tách dữ liệu thành bảng con nếu truy vấn theo ngày/tháng/năm

6. Cuối cùng, kiểm tra statistics / ANALYZE TABLE

  • Đảm bảo DB có thống kê mới nhất để chọn plan tối ưu.

ANALYZE TABLE orders;

👉 Em luôn ưu tiên phân tích query qua EXPLAIN, sau đó áp dụng kết hợp nhiều kỹ thuật tối ưu như trên, tùy tình huống thực tế.


Khi nào dùng WHERE khi nào dùng HAVING ? Cái nào hiệu năng tốt hơn ?

Dạ, câu này thường gặp để kiểm tra kiến thức SQL logic và tối ưu – em xin trình bày ngắn gọn, rõ ràng theo phong cách đi phỏng vấn:

Sự khác nhau giữa WHEREHAVING:

Tiêu chí

WHERE

HAVING

Áp dụng khi nào

Trước khi GROUP BYAGGREGATE

Sau khi GROUP BY hoặc AGGREGATE

Dùng cho

Dữ liệu hàng (row-level)

Dữ liệu nhóm (group-level)

Hỗ trợ hàm tổng hợp

❌ Không

✅ Có

📌 Ví dụ:

-- Lọc theo từng dòng: dùng WHERE
SELECT * FROM orders
WHERE status = 'PAID';

-- Lọc theo kết quả nhóm: dùng HAVING
SELECT customer_id, SUM(amount) AS total
FROM orders
GROUP BY customer_id
HAVING total > 1000;

⚙️ Hiệu năng: cái nào tốt hơn?

  • WHERE hiệu năng tốt hơn HAVING, vì:

    • WHERE lọc trước khi nhóm, nên giảm khối lượng dữ liệu xử lý.

    • HAVING lọc sau khi nhóm, nên nặng hơn nếu dữ liệu đầu vào lớn.

✅ Kinh nghiệm thực tế:

  • Nếu có thể lọc bằng WHERE, nên ưu tiên WHERE trước, rồi mới dùng HAVING nếu cần lọc thêm sau GROUP BY.

👉 Tóm lại:

  • WHERE: lọc sớm, tốt cho hiệu năng

  • HAVING: lọc sau khi nhóm hoặc dùng hàm tổng hợp → Kết hợp cả hai để tối ưu nhất


N+1 problem là gì ? Em đã gặp chưa ? Và cách xử lí ?

Dạ, em xin trình bày như đi phỏng vấn Senior Java Backend ạ:

N+1 Problem là gì?

Là một vấn đề hiệu năng thường gặp khi dùng ORM như Hibernate:

  • 1 truy vấn chính (N = 1) để lấy danh sách đối tượng cha (ví dụ: List<Order>).

  • Sau đó lại phát sinh N truy vấn phụ để lấy dữ liệu liên quan cho từng đối tượng con (ví dụ: Order -> Customer), dẫn đến tổng cộng N+1 truy vấn.

📌 Ví dụ cụ thể:

List<Order> orders = orderRepository.findAll();  
for (Order order : orders) {
    System.out.println(order.getCustomer().getName());  // Lazy fetch → mỗi lần gọi là 1 query
}

→ Nếu có 100 orders ⇒ sẽ thực hiện 1 + 100 = 101 truy vấn.

🚨 Hậu quả:

  • Rất tốn tài nguyên

  • Làm chậm performance, đặc biệt khi số lượng lớn bản ghi

🛠️ Em đã gặp và xử lý bằng các cách sau:

1. Sử dụng JOIN FETCH trong JPQL hoặc HQL

@Query("SELECT o FROM Order o JOIN FETCH o.customer")
List<Order> findAllWithCustomer();

2. Dùng @EntityGraph để eager fetch có kiểm soát

@EntityGraph(attributePaths = {"customer"})
List<Order> findAll();

Khi nào nên sử dụng cái gì?

  • Sử dụng JOIN FETCH khi bạn chỉ cần tải một hoặc một vài mối quan hệ một cách háo hức trong một truy vấn cụ thể và bạn không có nhu cầu tái sử dụng cấu hình tải này.

  • Sử dụng @EntityGraph khi bạn có các cấu hình tải dữ liệu phức tạp hơn hoặc khi bạn muốn tái sử dụng cấu hình tải này trong nhiều truy vấn khác nhau. Nó cung cấp sự kiểm soát chi tiết hơn về những gì được tải và có thể giúp quản lý hiệu suất ứng dụng tốt hơn.

3. Tối ưu fetch type:

  • Dùng LAZY mặc định, chỉ EAGER nếu thực sự cần thiết

  • Tránh truy cập property liên quan ngoài vòng kiểm soát

Tóm lại:

N+1 problem xảy ra khi ORM tự động load quá nhiều truy vấn con, gây giảm hiệu năng. Em xử lý bằng JOIN FETCH, @EntityGraph, hoặc viết custom query phù hợp với use case.


Khi nào dùng JOIN khi nào tách Query riêng ?

Dạ, câu này rất thực tế và thường quyết định đến hiệu năng cũng như maintainability của hệ thống. Em xin trình bày rõ ràng như sau:

Khi nào dùng JOIN:

  1. Cần lấy dữ liệu liên quan từ nhiều bảng cùng lúc → Muốn kết hợp quan hệ 1-N hoặc N-1 để giảm số lượng truy vấn.

  2. Truy vấn không quá phức tạp, dữ liệu join không quá lớn.

  3. Muốn đảm bảo tính nhất quán (consistency) trong 1 truy vấn.

Ví dụ:

SELECT o.id, o.total, c.name
FROM orders o
JOIN customers c ON o.customer_id = c.id;

Khi nào nên tách truy vấn riêng:

  1. Khi dữ liệu bảng phụ quá lớn → dùng JOIN có thể gây chậm, tốn RAM.

  2. Khi cần cache từng phần riêng (ví dụ: query chính gọi DB, còn dữ liệu phụ lấy từ Redis).

  3. Khi xử lý business logic phức tạp, cần lấy từng phần và xử lý riêng.

  4. Khi muốn tận dụng lazy loading trong ORM (và đã kiểm soát được N+1).

⚖️ Tóm lại:

Tiêu chí
JOIN
Tách Query

Số lượng truy vấn

1

N (nhiều hơn)

Độ phức tạp dữ liệu

Đơn giản

Dữ liệu lớn, logic phức tạp

Hiệu năng nếu dùng đúng

Cao (vì ít truy vấn)

Có thể thấp hơn nếu không cache tốt

Dễ debug, kiểm soát logic

Trung bình

Dễ chia nhỏ xử lý

ORM-friendly

Có thể gây N+1 nếu không dùng fetch

Kiểm soát tốt hơn

🔧 Em thường chọn JOIN khi:

  • Truy vấn đơn giản, cần lấy nhiều thông tin cùng lúc

  • Dữ liệu không quá nặng

Còn nếu logic phức tạp, nhiều bước xử lý hoặc có thể tái sử dụng kết quả, em sẽ tách riêng để dễ quản lý, test và scale về sau.


So sánh INNER JOIN, LEFT JOIN, RIGHT JOIN.

Dạ, em xin trình bày gọn và dễ hiểu nhất – như đang đi phỏng vấn Backend Java có làm nhiều với SQL:

So sánh giữa INNER JOIN, LEFT JOIN và RIGHT JOIN

JOIN Type
Mô tả
Kết quả trả về

INNER JOIN

Chỉ lấy dòng trùng khớp giữa 2 bảng

Chỉ những bản ghi có match ở cả 2 bảng

LEFT JOIN

Lấy tất cả dòng từ bảng bên trái, và dòng khớp từ bảng bên phải

Nếu không khớp → phần bên phải là NULL

RIGHT JOIN

Ngược lại với LEFT JOIN: giữ tất cả dòng bên phải

Nếu không khớp → phần bên trái là NULL

📌 Ví dụ minh hoạ:

Giả sử có 2 bảng:

  • customers (id, name)

  • orders (id, customer_id, total)

INNER JOIN:

SELECT c.name, o.total
FROM customers c
INNER JOIN orders o ON c.id = o.customer_id;

→ Chỉ lấy khách hàng có đơn hàng.

LEFT JOIN:

SELECT c.name, o.total
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id;

→ Lấy toàn bộ khách hàng, kể cả không có đơn hàng (total sẽ NULL).

RIGHT JOIN:

SELECT c.name, o.total
FROM customers c
RIGHT JOIN orders o ON c.id = o.customer_id;

→ Lấy toàn bộ đơn hàng, kể cả đơn không rõ khách (name sẽ NULL nếu khách bị xoá).

🔍 Khi nào dùng cái nào?

  • INNER JOIN: dùng mặc định khi bắt buộc phải có liên kết.

  • LEFT JOIN: khi muốn lấy hết bên trái (dữ liệu chính), bên phải có thì ghép, không có thì vẫn lấy.

  • RIGHT JOIN: ít dùng hơn, thường thay bằng LEFT JOIN với đổi thứ tự bảng.

Nếu có dùng ORM như Hibernate, các join này tương ứng với fetch join, hoặc dùng @OneToMany, @ManyToOne với fetch = FetchType.LAZY hoặc EAGER.


SQL Injection là gì ? Làm sao để phòng tránh ?

Dạ, câu này rất quan trọng khi đánh giá về bảo mật backend, em xin trả lời như sau:

SQL Injection là gì?

Là một kiểu tấn công mà hacker chèn mã SQL độc hại vào các input (form, URL, query string...) để:

  • Truy cập trái phép, lấy dữ liệu nhạy cảm

  • Xoá, sửa dữ liệu, thậm chí thực thi lệnh nguy hiểm trên DB

📌 Ví dụ tấn công:

SELECT * FROM users WHERE username = 'admin' AND password = '123' OR '1'='1';

→ Hacker nhập ' OR '1'='1 ở ô password → hệ thống sẽ luôn đăng nhập thành công.

Cách phòng tránh SQL Injection:

  1. Dùng Prepared Statement (tham số hoá câu lệnh) → Spring JPA, JDBC Template, Hibernate đều hỗ trợ:

    String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
    jdbcTemplate.query(sql, username, password);
  2. Không bao giờ nối chuỗi để tạo câu SQL trực tiếp

    // ❌ tuyệt đối tránh
    String query = "SELECT * FROM users WHERE user='" + input + "'";
  3. Valid và sanitize dữ liệu đầu vào → Kiểm tra định dạng, độ dài, blacklist ký tự nguy hiểm.

  4. Hạn chế quyền truy cập của DB account → Tài khoản kết nối DB không nên có quyền DROP, DELETE...

  5. Sử dụng ORM frameworks như Hibernate, JPA → ORM thường auto dùng prepared statements.

📌 Thực tế trong dự án:

Trong các dự án Spring Boot, em luôn sử dụng JPA/Hibernate để truy vấn với @Query, CriteriaBuilder, hoặc Specification – tất cả đều đã tránh được SQL Injection bằng cách binding tham số.


Sự khác nhau giữa View và Table là gì ?

Dạ, em xin trả lời gọn gàng, đúng trọng tâm như đi phỏng vấn:

Sự khác nhau giữa View và Table

Tiêu chí

Table

View

Khái niệm

bảng thật, chứa dữ liệu thật

bảng ảo, tạo từ 1 câu lệnh SELECT

Dữ liệu

Lưu trực tiếp trong DB

Không lưu dữ liệu, chỉ lưu câu truy vấn

Hiệu suất

Truy vấn nhanh nếu có chỉ mục

Chậm hơn nếu view phức tạp hoặc không materialized

Update/Insert

Có thể thực hiện bình thường

Chỉ update được nếu view đơn giản, không có JOIN, GROUP BY,...

Mục đích sử dụng

Lưu trữ, thao tác CRUD

Ẩn phức tạp truy vấn, tăng bảo mật, tái sử dụng logic

📌 Ví dụ thực tế:

  • Table: orders, products, users

  • View: v_monthly_sales – tổng hợp doanh thu theo tháng, dùng trong báo cáo.

Khi nào dùng View?

  • Khi muốn ẩn logic phức tạp

  • Khi cần tách biệt truy vấn báo cáo khỏi logic chính

  • Khi muốn giới hạn quyền truy cập (người dùng chỉ query được view, không được động vào table thật)

Tóm lại: Table là dữ liệu thực, còn View là cách nhìn (ảo) dựa trên dữ liệu thực.


Khi nào nên dùng stored procedure thay vì viết code ở service ?

Dạ, câu này thường gặp khi đánh giá về khả năng thiết kế hệ thống tối ưu hiệu năng. Em xin trả lời như sau:

Khi nào nên dùng Stored Procedure thay vì xử lý ở service:

  1. Xử lý nghiệp vụ phức tạp liên quan nhiều thao tác SQL trên DB

    • Ví dụ: nhiều bảng, tính toán nhiều bước, cập nhật trạng thái liên tục.

    • Lúc này stored procedure giúp giảm round-trip giữa ứng dụng và DB.

  2. Yêu cầu hiệu năng cao, xử lý khối lượng dữ liệu lớn

    • Stored procedure chạy ngay trong DB engine nên tối ưu tốc độ hơn so với gọi nhiều query từ code.

  3. Cần đảm bảo tính toàn vẹn giao dịch (ACID) ở cấp DB

    • Dễ kiểm soát transaction logic nếu toàn bộ nằm trong 1 procedure.

  4. Tái sử dụng logic DB từ nhiều ứng dụng khác nhau

    • Nếu nhiều hệ thống (Java, Python, Report Tool...) dùng chung logic, thì đặt ở DB sẽ tiện hơn.

  5. Yêu cầu bảo mật:

    • Có thể giới hạn user chỉ được thực thi procedure thay vì query trực tiếp bảng.

Khi không nên dùng Stored Procedure:

  • Logic nghiệp vụ cần thay đổi thường xuyên → viết ở service sẽ dễ maintain hơn.

  • Logic cần nhiều tương tác với API khác, file system, hoặc service ngoài DB.

  • Team không quen hoặc không giỏi PL/SQL → khó debug, khó test.

📌 Kết luận (ngắn gọn):

👉 Nếu nghiệp vụ thuần về dữ liệu, hiệu năng quan trọng và ít thay đổi → nên dùng stored procedure 👉 Nếu logic phức tạp, thay đổi nhiều, tích hợp nhiều hệ thống → nên xử lý ở service (Java code)


Tại sao hệ thống ngân hàng cần dùng Transaction ? Giải thích ACID?

Dạ, đây là câu hỏi quan trọng khi đánh giá hiểu biết về tính toàn vẹn dữ liệu trong các hệ thống nhạy cảm như ngân hàng. Em xin trả lời như sau:

Tại sao hệ thống ngân hàng cần dùng Transaction?

Trong hệ thống ngân hàng, mọi thao tác như chuyển khoản, rút tiền, giao dịch tài chính đều yêu cầu dữ liệu phải chính xác tuyệt đối. Không thể có chuyện:

  • Tiền trừ ở tài khoản A nhưng không cộng vào tài khoản B

  • Giao dịch bị dừng giữa chừng làm mất cân đối sổ sách

👉 Vì vậy, cần dùng Transaction để đảm bảo mọi thao tác phải thành công toàn bộ hoặc không gì cả.

Giải thích ACID trong Transaction

Thuộc tính
Ý nghĩa
Ví dụ trong ngân hàng

A – Atomicity

Tính nguyên vẹn: hoặc tất cả thao tác trong transaction thực hiện thành công, hoặc rollback hết

Trừ tiền tài khoản A và cộng tiền tài khoản B phải xảy ra đồng thời

C – Consistency

Dữ liệu luôn trong trạng thái hợp lệ, tuân thủ mọi ràng buộc (ràng buộc số dư, foreign key,...)

Không thể giao dịch nếu tài khoản không tồn tại hoặc số dư âm

I – Isolation

Giao dịch độc lập: tránh dirty read, phantom read,... nếu nhiều giao dịch chạy song song

2 người chuyển tiền cùng lúc không làm loạn số dư tài khoản

D – Durability

Dữ liệu đã commit thì sẽ được lưu vĩnh viễn, không bị mất kể cả khi mất điện

Sau khi giao dịch hoàn tất, dữ liệu được ghi chắc chắn vào DB

✅ Kết luận ngắn gọn:

Giao dịch trong ngân hàng là bắt buộc phải chính xác tuyệt đối, do đó Transaction kết hợp với nguyên tắc ACID chính là "hàng rào bảo vệ" để đảm bảo tính toàn vẹn và an toàn của dữ liệu.


So sánh truncate, delete, drop ?

Dạ, câu này rất hay để kiểm tra hiểu biết về thao tác dữ liệu trong SQL. Em xin trình bày ngắn gọn, rõ ràng:

So sánh TRUNCATE, DELETE, DROP

Tiêu chí

DELETE

TRUNCATE

DROP

Mục đích

Xoá 1 phần hoặc toàn bộ dữ liệu

Xoá toàn bộ dữ liệu

Xoá cả bảng (cấu trúc + dữ liệu)

Có WHERE

✅ Có

❌ Không

❌ Không

Transaction

✅ Có thể rollback

⚠ Tuỳ DBMS – thường không rollback

❌ Không rollback

Hiệu suất

Chậm hơn (ghi log từng dòng)

Nhanh hơn (xoá bulk, ghi log tối thiểu)

Nhanh nhất (xoá cả bảng)

Reset auto-increment

❌ Không

✅ Có

✅ Có khi tạo lại

Constraint / FK

Tôn trọng ràng buộc khóa ngoại

Có thể bị hạn chế nếu có FK

Xoá hết cả ràng buộc luôn

Khi nào dùng?

  • DELETE: Khi cần xoá có điều kiện, hoặc cần rollback.

  • TRUNCATE: Khi muốn xóa sạch dữ liệu nhanh, không cần rollback.

  • DROP: Khi muốn xoá luôn cả bảng khỏi DB, không còn sử dụng nữa.

Nếu phỏng vấn hệ thống thực tế:

"Trong dự án thực tế, em thường dùng DELETE khi cần xóa mềm hoặc chọn lọc theo điều kiện. Còn với dữ liệu tạm hoặc log table, em dùng TRUNCATE để tối ưu hiệu năng. DROP chỉ dùng khi migration hoặc không cần bảng nữa."


Ưu nhược điểm của việc dùng foreign key ?

Dạ, đây là câu hỏi rất thực tế khi làm việc với hệ thống quan hệ nhiều bảng. Em xin trả lời rõ ràng như sau:

Foreign Key là gì?

Là một ràng buộc dùng để liên kết dữ liệu giữa 2 bảng, đảm bảo tính toàn vẹn tham chiếu trong database.

Ví dụ: order.customer_idforeign key tham chiếu đến customer.id.

Ưu điểm của việc dùng Foreign Key

  1. Đảm bảo tính toàn vẹn dữ liệu

    • Không cho phép insert order với customer_id không tồn tại.

  2. Tự động hỗ trợ hành vi ON DELETE / ON UPDATE

    • Có thể cấu hình để xoá cascade, set null,...

  3. Giúp hiểu rõ quan hệ giữa các bảng

    • Rõ ràng hơn trong thiết kế DB, dễ tạo ERD.

  4. Dễ bảo trì và tránh lỗi nghiệp vụ

    • Ví dụ tránh sai lệch do dữ liệu "mồ côi".

Nhược điểm của việc dùng Foreign Key

  1. Giảm hiệu năng với hệ thống lớn, real-time

    • Do DB phải kiểm tra ràng buộc mỗi lần insert/update.

  2. Khó scale theo hướng microservice

    • Giữa các service tách biệt, không nên ràng buộc FK ở mức DB.

  3. Gây khó khăn khi migrate hoặc delete dữ liệu hàng loạt

    • Bị chặn nếu dữ liệu bị ràng buộc.

Khi nào nên dùng?

  • Hệ thống đơn khối (monolith), cần bảo vệ dữ liệu chặt chẽ.

  • Dữ liệu quan trọng, không thể để mất liên kết (ví dụ: ngân hàng, kế toán).

Khi nên cân nhắc không dùng FK

  • Trong hệ thống microservice (đảm bảo bằng logic ứng dụng).

  • Khi cần xử lý big data, hiệu năng ưu tiên cao (đảm bảo toàn vẹn bằng app-side logic).


Last updated