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
để 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_id
và status
.
Dùng
EXPLAIN
, thấy:type
:ALL
key
: nullrows
: 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
EXPLAIN
lại để xác nhậnSau khi tạo index, em chạy lại
EXPLAIN
để đảm bảo:type
chuyển từALL
→ref
,range
, hoặc tốt hơnkey
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 WHERE
và HAVING
:
WHERE
và HAVING
:Tiêu chí
WHERE
HAVING
Áp dụng khi nào
Trước khi GROUP BY
và AGGREGATE
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ơnHAVING
, 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ênWHERE
trước, rồi mới dùngHAVING
nếu cần lọc thêm sauGROUP 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ếtTrá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
:
JOIN
: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.
Truy vấn không quá phức tạp, dữ liệu join không quá lớn.
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:
Khi dữ liệu bảng phụ quá lớn → dùng
JOIN
có thể gây chậm, tốn RAM.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).
Khi xử lý business logic phức tạp, cần lấy từng phần và xử lý riêng.
Khi muốn tận dụng lazy loading trong ORM (và đã kiểm soát được N+1).
⚖️ Tóm lại:
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:
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
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ằngLEFT 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:
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);
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 + "'";
Valid và sanitize dữ liệu đầu vào → Kiểm tra định dạng, độ dài, blacklist ký tự nguy hiểm.
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...
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
Là bảng thật, chứa dữ liệu thật
Là 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:
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.
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.
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.
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.
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
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
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ùngTRUNCATE
để 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_id
là foreign key tham chiếu đến customer.id
.
✅ Ưu điểm của việc dùng Foreign Key
Đảm bảo tính toàn vẹn dữ liệu
Không cho phép insert
order
vớicustomer_id
không tồn tại.
Tự động hỗ trợ hành vi ON DELETE / ON UPDATE
Có thể cấu hình để xoá cascade, set null,...
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.
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
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.
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.
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