Trong bài này
Máy chủ của câu lạc bộ gặp sự cố ngay trước buổi học. Cô Mai có một tệp sao lưu từ sáng, nhưng chưa biết tệp ấy có đủ điểm và lịch sử xu không. Mở lại website ngay có thể khiến mọi người tiếp tục ghi trên dữ liệu thiếu. Cô cần một lần khôi phục riêng và những phép kiểm rõ ràng trước khi cho lớp dùng lại.
Đặt hai mục tiêu khác nhau cho dữ liệu và thời gian
Sao lưu (backup) tạo bản dữ liệu để dùng khi bản đang hoạt động gặp vấn đề. Khôi phục (restore) dùng bản sao ấy để dựng lại hệ thống. Có tệp sao lưu mới chứng minh đã tạo được một tệp, chưa chứng minh dựng lại thành công.
Cần thống nhất riêng hai mục tiêu trước sự cố:
- Mục tiêu điểm khôi phục, viết tắt
RPO(recovery point objective), giới hạn khoảng thời gian dữ liệu có thể mất. - Mục tiêu thời gian phục hồi, viết tắt
RTO(recovery time objective), giới hạn thời gian đưa dịch vụ hoạt động lại.
Tình huống riêng sau chỉ có một bản sao hoàn tất lúc 10:00. Sự cố xảy ra lúc 10:50, dịch vụ được xác nhận sẵn sàng lúc 11:20. Không có bản sao bổ sung hay cơ chế phục hồi tới thời điểm khác:
Dùng các mốc cùng múi giờ để tính số phút:
Dữ liệu đầu vào
recovery_drill1 dòng| id | backup_at | incident_at | ready_at |
|---|---|---|---|
| 1 | 2026-09-03 10:00:00 | 2026-09-03 10:50:00 | 2026-09-03 11:20:00 |
Câu lệnh
SELECT
(unixepoch(incident_at) - unixepoch(backup_at)) / 60
AS possible_loss_minutes,
(unixepoch(ready_at) - unixepoch(incident_at)) / 60
AS recovery_minutes
FROM recovery_drill;Kết quả
| possible_loss_minutes | recovery_minutes |
|---|---|
| 50 | 30 |
Kết quả cho khoảng mất dữ liệu có thể tới 50 phút và thời gian phục hồi là 30 phút. Nếu mục tiêu là RPO không quá 15 phút, bản sao này chưa đáp ứng. Đạt mục tiêu RTO là 30 phút cũng không bù được khoảng dữ liệu thiếu.
| Đại lượng | Bắt đầu tính từ đâu? | Kết thúc ở đâu? |
|---|---|---|
| Khoảng có thể mất dữ liệu | Điểm dữ liệu được khôi phục | Thời điểm sự cố |
| Thời gian phục hồi | Thời điểm sự cố | Dịch vụ đã sẵn sàng sau kiểm tra |
Khoảng mất dữ liệu nói về giới hạn thời gian, không khẳng định có đúng từng ấy phút phát sinh giao dịch. Nếu không ai ghi gì trong khoảng đó, số dòng mất có thể bằng không. Bạn vẫn cần đo mốc dữ liệu cuối có thể khôi phục thay vì chỉ nhìn giờ tạo tệp.
Bản sao nhất quán phải đi cùng cách khôi phục phù hợp
Bản sao nhất quán (consistent backup) thể hiện một trạng thái dữ liệu hợp lệ theo cơ chế sao lưu. Nếu lấy mỗi tệp ở một thời điểm khác nhau trong lúc hệ thống ghi, bạn có thể không có trạng thái ấy. Hãy dùng công cụ dành cho hệ quản trị thay vì tự coi mọi phép chép tệp là tương đương.
Với SQLite, Online Backup API hỗ trợ sao chép nội dung cơ sở dữ liệu đang mở bằng cơ chế của SQLite. Với PostgreSQL, pg_dump tạo bản sao lôgic. Công cụ phục hồi phụ thuộc định dạng: tệp SQL dạng chữ thường được nạp bằng psql, còn định dạng lưu trữ phù hợp dùng pg_restore.
Chọn bản saoĐúng hệ thống và mốcDựng nơi riêngKhông ghi đè bản đang dùngKhôi phụcDùng công cụ phù hợpKiểm dữ liệuSo với bằng chứng đã lưuThử ứng dụngĐọc và ghi có kiểm soátĐo thời gianGhi cả bước kiểm traQuyết địnhMở lại khi đã đạt
PostgreSQL còn có phục hồi đến thời điểm với nhật ký ghi trước, gọi là WAL (write-ahead log). Cách này cần bản sao nền và chuỗi nhật ký cần thiết. Chỉ có một tệp pg_dump không tự cho phép chọn mọi thời điểm sau khi sao lưu.
Trước khi sao lưu, người vận hành nên lưu bảng kê đối chiếu (manifest). Bảng kê ghi mốc dữ liệu, phiên bản cấu trúc và các số liệu cần so sau khôi phục. Ví dụ nhỏ dưới đây tạo số đếm trực tiếp từ dữ liệu mẫu:
Dữ liệu đầu vào
students6 dòng| id | name |
|---|---|
| 1 | An |
| 2 | Bình |
| 3 | Chi |
| 4 | Dung |
| 5 | Huy |
| 6 | Lan |
scores7 dòng| id | student_id | score |
|---|---|---|
| 1 | 1 | 80 |
| 2 | 1 | 90 |
| 3 | 2 | 70 |
| 4 | 3 | 90 |
| 5 | 3 | 100 |
| 6 | 4 | 50 |
| 7 | 5 | 90 |
Câu lệnh
SELECT 'students' AS table_name, COUNT(*) AS row_count
FROM students
UNION ALL
SELECT 'scores', COUNT(*)
FROM scores;Kết quả
| table_name | row_count |
|---|---|
| students | 6 |
| scores | 7 |
Hai dòng cho biết số học viên và số lần chấm ở thời điểm đo. Bảng kê cần được lưu cùng mốc sao lưu, không lấy số đếm của website ngày hôm sau để so. Những dòng phát sinh sau mốc sao lưu không thể tự xuất hiện trong một bản khôi phục chỉ chứa dữ liệu trước đó.
Đúng số dòng chưa chứng minh đúng nội dung và quan hệ
Một bản khôi phục có thể giữ nguyên số lần chấm nhưng gắn điểm cho sai người. Khi ấy số đếm vẫn khớp. Bạn cần kiểm toàn vẹn tham chiếu (referential integrity): mã trỏ sang bảng khác có tìm được dòng thật không.
Để nhìn thấy lỗi này, dữ liệu riêng dưới đây thay người sở hữu một lần chấm bằng mã 999 không tồn tại. Việc tắt kiểm khóa ngoại chỉ phục vụ mô phỏng dữ liệu hỏng, không phải cách nhập dữ liệu bình thường:
Xem các dòng điểm sau thay đổi:
scores7 dòng| id | student_id | score |
|---|---|---|
| 1 | 1 | 80 |
| 2 | 1 | 90 |
| 3 | 2 | 70 |
| 4 | 3 | 90 |
| 5 | 3 | 100 |
| 6 | 4 | 50 |
| 7 | 999 | 90 |
Dùng cùng bản dữ liệu ấy để kiểm khóa ngoại:
Dữ liệu đầu vào
students6 dòng| id | name |
|---|---|
| 1 | An |
| 2 | Bình |
| 3 | Chi |
| 4 | Dung |
| 5 | Huy |
| 6 | Lan |
Câu lệnh
PRAGMA foreign_key_check;Kết quả
| table | rowid | parent | fkid |
|---|---|---|---|
| scores | 7 | students | 0 |
Kết quả báo vi phạm ở bảng scores, trỏ tới bảng cha students. Cột rowid chỉ dòng cần điều tra trong bảng bị lỗi. Bật lại kiểm khóa ngoại không tự sửa những dòng đã được đưa vào khi kiểm tra đang tắt.
| Lớp kiểm | Ví dụ việc cần làm | Điều còn có thể bị bỏ sót |
|---|---|---|
| Cấu trúc | Kiểm bảng, cột, khóa, chỉ mục | Nội dung một ô bị đổi |
| Số lượng | So số dòng theo mốc | Một dòng đúng bị thay bằng dòng sai |
| Quan hệ | Tìm mã tham chiếu bị thiếu | Trỏ nhầm sang một người có thật |
| Nội dung | So các trường quan trọng với bằng chứng | Lỗi ở trường chưa đối chiếu |
SQLite còn có phép kiểm cấu trúc lưu trữ. Chạy trên dữ liệu mẫu nguyên vẹn:
Câu lệnh
PRAGMA integrity_check;Kết quả
| integrity_check |
|---|
| ok |
Giá trị ok nói về những kiểm tra mà lệnh thực hiện, không khẳng định mọi quy tắc nghiệp vụ đều đúng. Phép này không thay thế foreign_key_check. Bạn vẫn phải đọc các báo cáo quan trọng của ứng dụng để phát hiện việc gắn nhầm sang một người có thật.
Đối soát số dư cần biết điểm bắt đầu của lịch sử
Trong bộ mẫu, An có 120 xu nhưng tổng biến động được ghi chỉ là 10. Hai số khác nhau chưa chứng minh sai. Số dư đầu kỳ (opening balance) là số xu đã có trước khoảng lịch sử đang đối chiếu.
Mục này khai báo bằng chứng số dư đầu kỳ đã lưu cho sáu học viên. Sau đó, bản khôi phục mô phỏng làm số dư của Huy giảm từ 180 xuống 175 xu. Số dư đầu là dữ liệu bổ sung, không có sẵn trong bảng ledger:
Khi lịch sử đầy đủ trong kỳ, số dư dự kiến bằng số dư đầu cộng tổng mọi biến động. Giữ cả khoản trừ để tính đúng:
Dữ liệu đầu vào
opening_balances6 dòng| student_id | opening_coins |
|---|---|
| 1 | 110 |
| 2 | 75 |
| 3 | 150 |
| 4 | 65 |
| 5 | 155 |
| 6 | 150 |
ledger6 dòng| id | student_id | delta |
|---|---|---|
| 1 | 1 | 20 |
| 2 | 1 | -10 |
| 3 | 2 | 15 |
| 4 | 3 | 30 |
| 5 | 4 | -5 |
| 6 | 5 | 25 |
students6 dòng| id | name | coins |
|---|---|---|
| 1 | An | 120 |
| 2 | Bình | 90 |
| 3 | Chi | 180 |
| 4 | Dung | 60 |
| 5 | Huy | 175 |
| 6 | Lan | 150 |
Câu lệnh
WITH changes AS (
SELECT student_id, SUM(delta) AS net
FROM ledger
GROUP BY student_id
)
SELECT s.id, s.coins AS actual_coins,
o.opening_coins + COALESCE(c.net, 0) AS expected_coins
FROM students AS s
JOIN opening_balances AS o ON o.student_id = s.id
LEFT JOIN changes AS c ON c.student_id = s.id
ORDER BY s.id;Kết quả
| id | actual_coins | expected_coins |
|---|---|---|
| 1 | 120 | 120 |
| 2 | 90 | 90 |
| 3 | 180 | 180 |
| 4 | 60 | 60 |
| 5 | 175 | 180 |
| 6 | 150 | 150 |
Dòng mã 5 của Huy có số dư thực tế 175, còn số dư dự kiến là 180. Bằng chứng đầu kỳ 155 cộng biến động 25 cho thấy Huy bị thiếu 5 xu. Chỉ dòng Huy lệch, còn An có cả hai số đều là 120.
Lan chưa có lịch sử nên phần biến động bằng 0, số dư dự kiến vẫn bằng số dư đầu kỳ. Bằng chứng đầu kỳ đã được lưu từ trước, không suy ra từ số dư đang kiểm.
Nếu dùng JOIN với bảng biến động đã gom, bạn sẽ làm mất người chưa có lịch sử. Hãy so cách ghép này với LEFT JOIN ở trên:
Dữ liệu đầu vào
students6 dòng| id | name | coins |
|---|---|---|
| 1 | An | 120 |
| 2 | Bình | 90 |
| 3 | Chi | 180 |
| 4 | Dung | 60 |
| 5 | Huy | 175 |
| 6 | Lan | 150 |
ledger6 dòng| id | student_id | delta |
|---|---|---|
| 1 | 1 | 20 |
| 2 | 1 | -10 |
| 3 | 2 | 15 |
| 4 | 3 | 30 |
| 5 | 4 | -5 |
| 6 | 5 | 25 |
Câu lệnh
WITH changes AS (
SELECT student_id, SUM(delta) AS net
FROM ledger
GROUP BY student_id
)
SELECT s.id, s.name, s.coins AS actual_coins,
o.opening_coins + c.net AS expected_coins
FROM students AS s
JOIN opening_balances AS o ON o.student_id = s.id
JOIN changes AS c ON c.student_id = s.id
ORDER BY s.id;Kết quả
| id | name | actual_coins | expected_coins |
|---|---|---|---|
| 1 | An | 120 | 120 |
| 2 | Bình | 90 | 90 |
| 3 | Chi | 180 | 180 |
| 4 | Dung | 60 | 60 |
| 5 | Huy | 175 | 180 |
Báo cáo chỉ còn năm người, không có Lan vì không tìm được dòng tương ứng trong changes. Không có biến động vẫn là trường hợp cần đối soát. Dùng LEFT JOIN và thay tổng thiếu bằng 0 giúp giữ đủ sáu học viên.
Bạn chỉ dùng công thức này khi số dư đầu đáng tin cậy và lịch sử bao phủ đúng kỳ. Tự tính ngược số dư đầu từ số dư đang nghi ngờ sẽ khiến phép kiểm luôn khớp mà không phát hiện lỗi. Khi chưa đủ bằng chứng, cần ghi rõ chưa đối soát được thay vì sửa số dư cho giống tổng.
Diễn tập kết thúc bằng bằng chứng, không chỉ tệp sao lưu
Trước khi mở lại dịch vụ, cần thử các luồng đại diện bằng vai trò thông thường. Bài 39 giúp bạn phân biệt đọc được bằng tài khoản đặc quyền với đọc đúng phạm vi của học viên. Thử ghi nên diễn ra ở nơi riêng, rồi kiểm dữ liệu và lịch sử thay đổi cùng nhau.
Một báo cáo có thể đúng số dòng nhưng sai nội dung. Phép so tập hợp giúp chỉ ra những dòng thiếu hoặc lệch. Ví dụ này tạo bảng kê riêng từ điểm gốc rồi mô phỏng một điểm bị đổi trong bản cần kiểm:
Tìm các dòng mong đợi không còn nguyên vẹn:
Dữ liệu đầu vào
scores7 dòng| id | student_id | score |
|---|---|---|
| 1 | 1 | 85 |
| 2 | 1 | 90 |
| 3 | 2 | 70 |
| 4 | 3 | 90 |
| 5 | 3 | 100 |
| 6 | 4 | 50 |
| 7 | 5 | 90 |
expected_scores7 dòng| id | student_id | score |
|---|---|---|
| 1 | 1 | 80 |
| 2 | 1 | 90 |
| 3 | 2 | 70 |
| 4 | 3 | 90 |
| 5 | 3 | 100 |
| 6 | 4 | 50 |
| 7 | 5 | 90 |
Câu lệnh
SELECT id, student_id, score, day
FROM expected_scores
EXCEPT
SELECT id, student_id, score, day
FROM scores;Kết quả
| id | student_id | score | day |
|---|---|---|---|
| 1 | 1 | 80 | 2026-09-01 |
Kết quả chỉ ra dòng điểm 80 mong đợi của lần chấm mã 1. Ở bản cần kiểm, dòng ấy đã thành 85, dù tổng số lần chấm không đổi. Bạn có thể đối chiếu hai chiều theo cách ở bài 36 để tìm cả dòng mới khác với bảng kê.
Trong diễn tập thật, bảng kê phải đến từ bằng chứng đã lưu theo bản sao. Hai chiều rỗng mới cho thấy hai tập dòng bằng nhau trong các cột được so. Phép so tập hợp không phát hiện số lượng dòng trùng, nên còn cần kiểm số dòng và các khóa phù hợp.
- Ghi lại bản sao nào được dùng, mốc dữ liệu và phiên bản cấu trúc.
- Ghi thời gian lấy tệp, khôi phục, kiểm tra và đưa dịch vụ sẵn sàng.
- Lưu kết quả từng phép kiểm, kể cả mục chưa kiểm được.
- Giữ bản sao ở nơi được bảo vệ, với mã hóa và thời hạn lưu phù hợp.
Bản sao cũng chứa thông tin cần bảo vệ như dữ liệu gốc. Khóa giải mã cần được cất riêng với bản sao, và cũng phải được thử khi diễn tập. Khôi phục được bảng nhưng thiếu khóa giải mã vẫn có thể khiến ứng dụng không dùng được dữ liệu.
Lỗi thường gặp
| Lỗi | Vì sao sai | Cách sửa |
|---|---|---|
| Coi có tệp là đã phục hồi được | Chưa thử công cụ và ứng dụng | Khôi phục vào nơi riêng rồi kiểm |
| Chỉ so số dòng | Nội dung có thể bị thay mà số dòng không đổi | So thêm quan hệ và các trường quan trọng |
Buộc tổng ledger bằng coins | Có thể thiếu số dư đầu hoặc một phần lịch sử | Xác định kỳ và bằng chứng đầu kỳ |
| Đo thời gian nạp tệp rồi dừng | Còn phải kiểm trước khi phục vụ người dùng | Đo tới lúc dịch vụ sẵn sàng |
Tóm tắt
RPOnói về khoảng dữ liệu có thể mất,RTOnói về thời gian phục hồi.- Công cụ sao lưu, định dạng tệp và công cụ khôi phục phải phù hợp nhau.
- Số dòng, quan hệ, nội dung và quy tắc nghiệp vụ cần các phép kiểm riêng.
- Số dư đầu cộng lịch sử đầy đủ mới tạo cơ sở đối soát số dư cuối.
- Một lần diễn tập có kết quả kiểm và thời gian đo giúp đánh giá khả năng khôi phục.