Mục lục bài họcĐang ở d08-b4
← IB Computer Science
0/32 bài đã học xong
Chương 8 · Option: Object-Oriented Programming and Databases · Bài 4/4 của chương · bài 32/32 của IB Computer Science

SQL Queries, Joins, Indexes and Database Security

Truy vấn SQL, phép nối, chỉ mục và bảo mật cơ sở dữ liệu
← Mục lục bài học
Lý thuyết · English

The anatomy of a query. SELECT names the columns wanted; FROM names the table; WHERE filters rows with a condition; ORDER BY sorts the result; GROUP BY collapses rows sharing a value so that aggregate functions — COUNT, SUM, AVG, MAX, MIN — can be applied per group, and HAVING filters those groups. Read a query in execution order, not written order: FROM and JOIN build the working table first, WHERE removes rows, GROUP BY and HAVING collapse and filter, ORDER BY sorts, and SELECT picks columns last. Most exam errors come from confusing WHERE (filters rows before grouping) with HAVING (filters groups after).

Joins. Normalisation splits data across tables; a join reassembles it. A foreign key in one table holds the primary key of another, and JOIN ... ON matches rows where they are equal. An inner join keeps only matching pairs — a student with no enrolments does not appear; a left join keeps every row of the left table, with empty values where there is no match, which is how you list students including those with no enrolments. Joining three tables chains two ON conditions. The join is what makes normalisation workable: data is stored once and reassembled on demand.

Indexes. Without an index, finding rows with a given value means scanning the whole table. An index is a separate sorted structure (typically a tree) on one column that lets the database jump to matching rows, so a search on an indexed column is logarithmic instead of linear. The cost: every insert, update or delete must also update the index, so writes slow down, and each index takes storage. The rule is to index columns used in WHERE and JOIN on tables that are read far more than written, and not to index everything.

Security and integrity. A database concentrates an organisation's most valuable data, so it needs layered protection: access rights per user or role (read-only for reporting, no access to salary tables for most staff); encryption of sensitive columns and of backups; audit logs of who read or changed what; backups stored off-site and tested by restoring them; and validation rules in the schema (types, ranges, foreign keys) so that bad data is rejected at the door. The classic attack is SQL injection: a program builds a query by pasting in text the user typed, so a user who types a fragment of SQL gets it executed with the program's rights. The defence is parameterised queries, which send user input as data that can never be interpreted as code. Finally, a centralised database gives a single version of the truth — but is also a single point of failure and a single target, which is the standard evaluation point.

Giải thích tiếng Việt

Cấu trúc một truy vấn. SELECT nêu các cột muốn lấy; FROM nêu bảng; WHERE lọc dòng bằng một điều kiện; ORDER BY sắp kết quả; GROUP BY gộp các dòng có cùng giá trị để các hàm tổng hợp — COUNT, SUM, AVG, MAX, MIN — áp dụng theo nhóm, và HAVING lọc các nhóm đó. Đọc truy vấn theo thứ tự thực thi, không theo thứ tự viết: FROM và JOIN dựng bảng làm việc trước, WHERE bỏ dòng, GROUP BY và HAVING gộp và lọc, ORDER BY sắp, và SELECT chọn cột sau cùng. Phần lớn lỗi thi tới từ nhầm WHERE (lọc dòng trước khi gộp) với HAVING (lọc nhóm sau khi gộp).

Phép nối. Chuẩn hoá tách dữ liệu ra nhiều bảng; phép nối ghép lại. Khoá ngoại ở một bảng giữ khoá chính của bảng khác, và JOIN ... ON khớp các dòng có giá trị bằng nhau. Nối trong chỉ giữ các cặp khớp — học sinh chưa đăng ký môn nào không xuất hiện; nối trái giữ mọi dòng của bảng trái, với giá trị rỗng ở chỗ không khớp, và đó là cách liệt kê học sinh kể cả người chưa đăng ký. Nối ba bảng thì nối chuỗi hai điều kiện ON. Phép nối là thứ làm chuẩn hoá vận hành được: dữ liệu lưu một lần và ghép lại khi cần.

Chỉ mục. Không có chỉ mục, tìm các dòng có một giá trị nghĩa là quét cả bảng. Chỉ mục là một cấu trúc riêng đã sắp (thường là cây) trên một cột, cho phép cơ sở dữ liệu nhảy thẳng tới dòng khớp, nên tìm trên cột có chỉ mục là logarit thay vì tuyến tính. Cái giá: mỗi lần thêm, sửa hay xoá phải cập nhật cả chỉ mục nên ghi chậm đi, và mỗi chỉ mục tốn lưu trữ. Quy tắc là đánh chỉ mục các cột dùng trong WHERE và JOIN trên các bảng đọc nhiều hơn ghi, và không đánh chỉ mục mọi thứ.

Bảo mật và toàn vẹn. Cơ sở dữ liệu tập trung dữ liệu quý nhất của tổ chức, nên cần bảo vệ nhiều lớp: quyền truy cập theo người dùng hoặc vai trò (chỉ đọc cho báo cáo, không thấy bảng lương với đa số nhân viên); mã hoá các cột nhạy cảm và bản sao lưu; nhật ký kiểm tra ai đọc hay đổi gì; sao lưu cất ngoài máy và được kiểm bằng cách khôi phục thử; và quy tắc kiểm tra trong lược đồ (kiểu, khoảng, khoá ngoại) để dữ liệu xấu bị chặn ngay cửa. Tấn công kinh điển là SQL injection: chương trình dựng truy vấn bằng cách dán thẳng chữ người dùng gõ, nên người gõ một mảnh SQL sẽ được thực thi nó với quyền của chương trình. Cách phòng là truy vấn tham số hoá, gửi đầu vào người dùng như dữ liệu không bao giờ bị diễn giải thành mã. Cuối cùng, cơ sở dữ liệu tập trung cho một bản sự thật duy nhất — nhưng cũng là một điểm hỏng và một mục tiêu duy nhất, và đó là điểm đánh giá chuẩn.

PHÉP NỐI: KHOÁ NGOẠI LÀ CÂY CẦU GIỮA HAI BẢNG Student StudentIDNameYear S01An12 S02Bình11 S03Chi12 khoá chính: StudentID Enrolment StudentIDCourseMark S01Maths78 S01Physics65 S03Maths91 khoá ngoại: StudentID → Student Kết quả nối NameCourseMark AnMaths78 AnPhysics65 ChiMaths91 Bình không có dòng: chưa đăng ký CẤU TRÚC MỘT TRUY VẤN — ĐỌC THEO THỨ TỰ THỰC THI, KHÔNG THEO THỨ TỰ VIẾT SELECT Name, Course, Mark← cột nào (chạy CUỐI) FROM Student JOIN Enrolment ON Student.StudentID = Enrolment.StudentID← bảng nào (ĐẦU) WHERE Mark ≥ 70 AND Year = 12← lọc dòng ORDER BY Mark DESC← sắp xếp · kết quả: Chi 91, An 78 chỉ mục trên cột hay lọc và nối: tìm nhanh hơn nhiều, nhưng mỗi lần thêm hay sửa phải cập nhật chỉ mục → chậm ghi bảo mật: quyền theo người dùng (chỉ đọc, chỉ bảng mình), mã hoá dữ liệu nhạy cảm, ghi nhật ký truy cập, sao lưu ngoài máy SQL injection: ghép chuỗi người dùng nhập vào truy vấn → kẻ tấn công viết truy vấn của họ; phòng bằng tham số hoá một cơ sở dữ liệu tập trung có một bản sự thật, nhưng cũng là một điểm hỏng và một mục tiêu duy nhất
Hình trên cùng có hai bảng: Student với khoá chính StudentID, và Enrolment với StudentID là khoá ngoại trỏ về Student, nên một học sinh có thể có nhiều dòng đăng ký. Đường đỏ nối S01 ở hai bảng là ý nghĩa của phép nối: mỗi dòng Enrolment được ghép với dòng Student có cùng StudentID, cho ra bảng kết quả bên phải với tên, môn và điểm. Bình không xuất hiện trong kết quả vì chưa có dòng đăng ký nào — phép nối trong chỉ giữ các cặp khớp. Hộp giữa mổ xẻ một câu truy vấn và nhắc rằng nên đọc theo thứ tự thực thi chứ không theo thứ tự viết: FROM và JOIN chạy đầu tiên để dựng bảng tạm, WHERE lọc dòng, ORDER BY sắp xếp, và SELECT chọn cột chạy cuối cùng; với điều kiện điểm từ 70 và khối 12, kết quả là Chi 91 rồi An 78. Hộp dưới cùng gom ba ý hay được hỏi. Chỉ mục trên cột hay lọc và nối làm tìm nhanh hơn nhiều nhưng mỗi lần thêm hay sửa phải cập nhật chỉ mục nên ghi chậm hơn. Bảo mật gồm quyền theo người dùng, mã hoá dữ liệu nhạy cảm, ghi nhật ký truy cập và sao lưu ngoài máy. Dòng đỏ là lỗ hổng kinh điển SQL injection: ghép chuỗi người dùng nhập thẳng vào truy vấn cho phép kẻ tấn công viết truy vấn của họ, và cách phòng là tham số hoá. Câu cuối là điểm đánh giá về cơ sở dữ liệu tập trung: một bản sự thật, nhưng cũng là một điểm hỏng và một mục tiêu duy nhất.
Ví dụ — Ví dụ 1 — viết ba truy vấn tăng dần độ khó trên hai bảng

Với hai bảng Student(StudentID, Name, Year) và Enrolment(StudentID, Course, Mark): (a) tên và điểm của mọi học sinh khối 12 có điểm từ 70 trong bất kỳ môn nào, sắp giảm dần theo điểm; (b) điểm trung bình mỗi môn, chỉ hiện môn có trung bình trên 75; (c) danh sách mọi học sinh kể cả người chưa đăng ký môn nào.

Giải.

(a) Nối, lọc, sắp.
SELECT Name, Course, Mark FROM Student JOIN Enrolment ON Student.StudentID = Enrolment.StudentID WHERE Year = 12 AND Mark ≥ 70 ORDER BY Mark DESC.
Thứ tự thực thi: dựng bảng nối, giữ dòng khối 12 và điểm từ 70, sắp giảm dần, chọn ba cột. Với dữ liệu trong hình: Chi Maths 91, An Maths 78.

(b) Gộp và lọc nhóm.
SELECT Course, AVG(Mark) FROM Enrolment GROUP BY Course HAVING AVG(Mark) > 75.
Điểm cốt lõi: điều kiện trung bình trên 75 áp lên nhóm sau khi gộp nên phải dùng HAVING; viết WHERE AVG(Mark) là lỗi vì WHERE chạy trước khi gộp, lúc chưa có trung bình nào. Với dữ liệu trong hình: Maths trung bình 84,5 hiện; Physics 65 bị loại.

(c) Nối trái.
SELECT Name, Course FROM Student LEFT JOIN Enrolment ON Student.StudentID = Enrolment.StudentID.
Nối trong sẽ bỏ Bình vì không có dòng đăng ký khớp; nối trái giữ mọi dòng của bảng trái, và Bình xuất hiện với Course rỗng. Câu hỏi kể cả người chưa là dấu hiệu nhận biết nối trái. Muốn chỉ liệt kê người chưa đăng ký thì thêm WHERE Course IS NULL.

Điểm giám khảo tìm. Điều kiện ON đúng cặp khoá; phân biệt WHERE và HAVING; chọn đúng loại nối theo câu hỏi; và kiểm kết quả trên dữ liệu nhỏ.

Ví dụ — Ví dụ 2 — chẩn đoán một trang đăng nhập bị tấn công và một truy vấn chậm

(a) Trang đăng nhập dựng truy vấn bằng cách ghép chuỗi: SELECT * FROM Users WHERE name = '[tên gõ]' AND password = '[mật khẩu gõ]'. Một người gõ tên là: admin' -- và bất kỳ mật khẩu nào, rồi vào được tài khoản admin. Giải thích và sửa. (b) Truy vấn tìm đơn hàng theo mã khách trên bảng 20 triệu dòng mất 40 giây; đề xuất và nêu cái giá.

Giải.

(a) Cơ chế. Sau khi ghép, truy vấn thành: SELECT * FROM Users WHERE name = 'admin' -- ' AND password = '...'. Trong SQL, hai dấu gạch ngang bắt đầu một chú thích tới hết dòng, nên toàn bộ phần kiểm mật khẩu bị bỏ qua; điều kiện còn lại chỉ là name = 'admin', đúng, và người đó vào với quyền admin. Nguyên nhân gốc: chương trình để dữ liệu người dùng gõ trở thành vì nó được dán thẳng vào câu lệnh. Biến thể nguy hiểm hơn có thể xoá bảng hoặc đọc toàn bộ dữ liệu.

Sửa. Dùng truy vấn tham số hoá: câu lệnh được gửi với chỗ trống, SELECT * FROM Users WHERE name = ? AND password = ?, và giá trị người dùng gõ được gửi riêng như dữ liệu; cơ sở dữ liệu không bao giờ diễn giải chúng thành SQL, nên chuỗi admin' -- chỉ là một cái tên không tồn tại. Kèm theo: không lưu mật khẩu dạng chữ mà lưu băm, và tài khoản ứng dụng chỉ có quyền tối thiểu để kể cả khi bị tấn công cũng không xoá được bảng.

(b) Chẩn đoán. 40 giây trên 20 triệu dòng là quét toàn bảng: không có chỉ mục trên cột mã khách. Tạo chỉ mục trên CustomerID; tìm kiếm thành logarit, vài mili giây. Cái giá: mỗi đơn hàng mới phải cập nhật chỉ mục nên chèn chậm hơn một chút, và chỉ mục tốn lưu trữ. Với bảng đơn hàng — đọc nhiều hơn ghi rất nhiều — cái giá đáng trả. Cần nói thêm: không đánh chỉ mục mọi cột, chỉ cột dùng trong WHERE và JOIN, vì một bảng có mười chỉ mục ghi rất chậm.

Bẫy hay mất điểm — Bẫy 1 — dùng WHERE cho điều kiện trên hàm tổng hợp. WHERE chạy trước khi gộp; điều kiện trên nhóm phải là HAVING.
Bẫy 2 — dùng nối trong khi câu hỏi nói kể cả người không có. Nối trong bỏ dòng không khớp; cần nối trái.
Bẫy 3 — nói chỉ mục luôn tốt. Ghi chậm đi và tốn lưu trữ; chỉ đánh cột lọc và nối trên bảng đọc nhiều.
Phải nhớ — Chốt bài 4. Bốn thứ mang vào phòng thi. Một, đọc truy vấn theo thứ tự thực thi: FROM, WHERE, GROUP, HAVING, ORDER, SELECT. Hai, nối trong giữ cặp khớp, nối trái giữ mọi dòng bảng trái. Ba, chỉ mục đổi tốc độ đọc lấy tốc độ ghi. Bốn, SQL injection là dữ liệu thành mã, phòng bằng tham số hoá; tập trung là một bản sự thật và một mục tiêu.

Đọc xong rồi — làm thử ngay

Bài tập của chương Option: Object-Oriented Programming and Databases gồm 14 câu trắc nghiệm và 5 đề tự luận. Đáp án hiện ngay khi chọn, miễn phí.

Làm bài tập chương →