Oracle 6 — Đồng thời, đọc nhất quán & Flashback
Vì sao đồng thời là trái tim của Oracle
Trong một hệ thống ngân hàng hay ERP, hàng nghìn phiên (session) chạy chồng lên nhau: người này quét báo cáo tổng dư nợ toàn hệ thống trong khi hàng trăm giao dịch khác vẫn đang chuyển tiền, cập nhật số dư, ghi log. Câu hỏi kinh điển của mọi CSDL là: làm sao để nhiều người đọc/ghi cùng lúc mà kết quả vẫn đúng và không phải xếp hàng chờ nhau?
Oracle trả lời bằng một triết lý đã trở thành thương hiệu:
Readers don't block writers, writers don't block readers. (Người đọc không chặn người ghi, người ghi không chặn người đọc.)
Điểm khác biệt lớn nhất so với PostgreSQL nằm ở cách lưu phiên bản cũ của dữ liệu. PostgreSQL giữ nhiều version dòng ngay trong heap (xem PostgreSQL MVCC). Oracle thì sửa dữ liệu tại chỗ trong block, còn ảnh cũ được đẩy vào một vùng riêng gọi là undo. Bài này đào sâu vào cơ chế đó, các mức cách ly, khóa dòng, những lỗi ORA hay gặp, và Flashback — tính năng "du hành thời gian" mà chính undo tạo ra. Nội dung nối tiếp bài Kiến trúc Oracle & undo và PL/SQL.
Lưu ý sandbox: SQL Builder của trang này chạy trên PostgreSQL. Cú pháp riêng của Oracle (Flashback,
FOR UPDATEkiểu Oracle,PRAGMA AUTONOMOUS_TRANSACTION...) không chạy được ở đây, nên các ví dụ được ghi rõ là (minh hoạ — Oracle, không chạy trong sandbox Postgres).
Read consistency: đọc nhất quán nhờ undo
Nguyên tắc read consistency của Oracle bảo đảm rằng dữ liệu một câu truy vấn nhìn thấy là nhất quán tại một thời điểm — thường là thời điểm câu lệnh bắt đầu. Cụ thể:
- Câu
SELECTkhông bao giờ thấy thay đổi chưaCOMMITcủa giao dịch khác. - Nếu trong lúc
SELECTđang chạy, một block bị giao dịch khác sửa (và thậm chí đã commit), Oracle không đọc dữ liệu mới. Thay vào đó nó nhận ra block đã đổi và dựng lại ảnh cũ (consistent read image) của block đó từ undo.
Undo hoạt động thế nào
Khi một giao dịch chạy DML (INSERT/UPDATE/DELETE), Oracle làm hai việc ghi log song song:
- Redo: ghi lại "phải làm gì để lặp lại thay đổi" — phục vụ recovery, ghi vào redo log.
- Undo: ghi lại "phải làm gì để hoàn tác thay đổi" — tức là giá trị cũ của dữ liệu, lưu trong undo segment (thuộc undo tablespace).
Undo phục vụ ba mục đích: rollback giao dịch, read consistency cho câu lệnh khác, và Flashback. Khi một reader gặp block đã bị đổi bởi giao dịch có SCN (System Change Number) lớn hơn snapshot của mình, Oracle lần theo con trỏ undo, áp ngược các thay đổi để tái tạo đúng phiên bản block tại SCN của câu truy vấn.
Chính vì reader tự dựng ảnh cũ từ undo, nên nó không phải chờ writer và writer cũng không phải chờ reader — đó là gốc rễ của câu khẩu hiệu ở đầu bài.
Undo vs heap: khác biệt cốt lõi với PostgreSQL
| Khía cạnh | Oracle (undo) | PostgreSQL (heap versions) |
|---|---|---|
| Nơi lưu phiên bản cũ | Undo segment (tablespace riêng) | Ngay trong heap của bảng (dead tuple) |
| Cách UPDATE | Sửa tại chỗ trong block, đẩy ảnh cũ ra undo | Chèn tuple mới, đánh dấu tuple cũ hết hạn (xmax) |
| Dọn rác | Undo bị ghi đè vòng theo UNDO_RETENTION | VACUUM/autovacuum dọn dead tuple |
| Rủi ro đặc trưng | ORA-01555 snapshot too old (undo bị ghi đè) | Table bloat, transaction ID wraparound |
| Rollback | Nhanh với giao dịch nhỏ; giao dịch lớn phải áp lại undo | Rẻ (tuple mới bị bỏ, cũ vẫn còn) |
Điểm mấu chốt: undo là vùng có hạn và tái sử dụng vòng tròn. Nếu một câu truy vấn chạy quá lâu và undo chứa ảnh cũ nó cần đã bị giao dịch khác ghi đè, Oracle không còn cách dựng lại ảnh nhất quán → ném lỗi ORA-01555.
Các mức cách ly (isolation levels) của Oracle
Oracle không hỗ trợ đủ bốn mức cách ly của chuẩn ANSI. Đáng chú ý, Oracle không có Repeatable Read riêng như chuẩn. Thực tế Oracle cung cấp ba chế độ:
| Mức | Phạm vi nhất quán | Đặc điểm | Lỗi/retry |
|---|---|---|---|
| Read Committed (mặc định) | Theo từng câu lệnh (statement-level) | Mỗi SELECT thấy dữ liệu đã commit tính đến lúc câu lệnh bắt đầu. Không đọc bẩn (dirty read). Hai lần đọc trong cùng giao dịch có thể khác nhau (non-repeatable read / phantom). | Không cần retry; Oracle tự xử lý xung đột ghi (lock + restart câu lệnh). |
| Serializable | Theo cả giao dịch (transaction-level) | Toàn giao dịch nhất quán tại SCN lúc BEGIN. Mọi dòng đọc lại đảm bảo không đổi. Chỉ cho sửa dòng nếu thay đổi của giao dịch khác đã commit trước khi giao dịch này bắt đầu. | Có thể ném ORA-08177 can't serialize access → ứng dụng phải bắt và retry. |
| Read-Only | Cả giao dịch, chỉ đọc | Giao dịch chỉ đọc, thấy ảnh nhất quán tại lúc bắt đầu; không cho DML. Tốt cho báo cáo dài. | Không cho ghi. |
Cách đặt mức cách ly (minh hoạ — Oracle, không chạy trong sandbox Postgres):
-- Read Committed là mặc định, không cần khai báo.
-- Serializable: nhất quán cấp giao dịch
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- ... các câu lệnh ...
COMMIT;
-- Read-Only: báo cáo dài không bị ảnh hưởng bởi DML đồng thời
SET TRANSACTION READ ONLY;
SELECT SUM(balance) FROM accounts; -- ảnh nhất quán tại lúc BEGIN
COMMIT;
Với Serializable, mẫu code chuẩn là vòng lặp retry: bắt ORA-08177, rollback, rồi chạy lại giao dịch. Trong hầu hết trường hợp, replay là hoàn toàn hợp lệ.
Khóa (locking) trong Oracle
Oracle dùng row-level locking — khóa ở mức từng dòng, tự động cấp khi DML sửa dòng. Vài đặc điểm quan trọng:
- Tự động:
UPDATE/DELETE/INSERTtự khóa các dòng bị đụng tới cho đến khiCOMMIT/ROLLBACK. Không cần khai báo tay. - Không có lock escalation: Oracle không tự "nâng" hàng loạt row lock thành table lock khi số dòng lớn — đây là khác biệt với SQL Server. Khóa dòng vẫn là khóa dòng.
- Đọc không cần khóa:
SELECTthuần không xin lock trên dòng (nhờ read consistency ở trên). - Enqueue: cơ chế xếp hàng chờ khóa của Oracle; các session chờ cùng một tài nguyên xếp theo enqueue.
SELECT ... FOR UPDATE và các biến thể
Khi cần khóa dòng chủ động để đọc-rồi-ghi (pessimistic locking) (minh hoạ — Oracle, không chạy trong sandbox Postgres):
-- Khóa dòng, chờ vô hạn nếu dòng đang bị khóa
SELECT * FROM orders WHERE id = 42 FOR UPDATE;
-- NOWAIT: nếu dòng đang bị khóa -> báo lỗi NGAY (ORA-00054)
SELECT * FROM orders WHERE id = 42 FOR UPDATE NOWAIT;
-- WAIT n: chờ tối đa n giây rồi báo lỗi
SELECT * FROM orders WHERE id = 42 FOR UPDATE WAIT 5;
-- SKIP LOCKED: bỏ qua các dòng đang bị khóa, chỉ lấy dòng rảnh
SELECT * FROM job_queue WHERE status = 'READY'
FOR UPDATE SKIP LOCKED;
NOWAIT: không chờ, đụng khóa là ném ORA-00054 (resource busy) ngay.WAIT n: chờ tối đangiây, hết giờ mới báo lỗi.SKIP LOCKED: bỏ qua những dòng đang bị session khác khóa, chỉ khóa và lấy các dòng còn rảnh. Điểm tinh tế: vớiSKIP LOCKED, dòng chỉ bị khóa khi được fetch, không phải khi mở cursor. Đây là công cụ vàng để xây hàng đợi công việc (job queue) cho nhiều worker chạy song song mà không giẫm chân nhau, tránh được cả deadlock lẫn "resource busy".
Deadlock — Oracle tự phát hiện
Khi hai giao dịch khóa chéo (A giữ dòng 1 chờ dòng 2, B giữ dòng 2 chờ dòng 1), Oracle tự động phát hiện deadlock, chọn một giao dịch làm "nạn nhân", rollback câu lệnh gây ra deadlock của nó và ném ORA-00060: deadlock detected. Lưu ý Oracle chỉ rollback câu lệnh gây kẹt chứ không tự rollback toàn bộ giao dịch — ứng dụng vẫn nên bắt lỗi và quyết định rollback/retry. Cách phòng deadlock kinh điển: luôn khóa các tài nguyên theo cùng một thứ tự trong mọi luồng nghiệp vụ.
Giao dịch: COMMIT, ROLLBACK, SAVEPOINT, autonomous
Giao dịch trong Oracle bắt đầu ngầm ở câu DML đầu tiên và kết thúc bằng COMMIT (ghi bền vững, giải phóng khóa) hoặc ROLLBACK (hoàn tác bằng undo). SAVEPOINT cho phép quay lui một phần (minh hoạ — Oracle, không chạy trong sandbox Postgres):
INSERT INTO log_events(msg) VALUES ('start');
SAVEPOINT sp1;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- đổi ý, chỉ hủy phần sau SAVEPOINT
ROLLBACK TO sp1;
COMMIT; -- dòng log 'start' vẫn được giữ
Autonomous transaction
Đôi khi bạn cần một giao dịch con độc lập commit riêng, bất kể giao dịch cha sau đó commit hay rollback — kinh điển là ghi audit log phải tồn tại ngay cả khi nghiệp vụ chính bị rollback. Đó là autonomous transaction, khai báo bằng PRAGMA AUTONOMOUS_TRANSACTION trong khối PL/SQL (minh hoạ — Oracle, không chạy trong sandbox Postgres):
CREATE OR REPLACE PROCEDURE write_audit(p_msg VARCHAR2) IS
PRAGMA AUTONOMOUS_TRANSACTION; -- tách khỏi giao dịch cha
BEGIN
INSERT INTO audit_log(msg, ts) VALUES (p_msg, SYSTIMESTAMP);
COMMIT; -- bắt buộc COMMIT/ROLLBACK riêng trước khi kết thúc
END;
Autonomous transaction có undo/redo và khóa riêng, không nhìn thấy thay đổi chưa commit của giao dịch cha. Dùng cẩn thận: lạm dụng dễ gây khó suy luận về tính đúng đắn dữ liệu.
Tổng hợp các lỗi ORA hay gặp
| Mã lỗi | Ý nghĩa | Nguyên nhân điển hình | Hướng xử lý |
|---|---|---|---|
| ORA-00054 | resource busy and acquire with NOWAIT | FOR UPDATE NOWAIT / DDL đụng dòng-bảng đang bị khóa | Dùng WAIT n, retry, hoặc lên lịch lúc ít tải |
| ORA-00060 | deadlock detected | Hai giao dịch khóa chéo | Bắt lỗi, rollback + retry; khóa theo cùng thứ tự |
| ORA-01555 | snapshot too old | Query dài, undo chứa ảnh cũ đã bị ghi đè | Tăng UNDO_RETENTION/undo tablespace, rút ngắn query, tránh commit trong vòng lặp fetch |
| ORA-08177 | can't serialize access for this transaction | Giao dịch SERIALIZABLE đụng dữ liệu bị giao dịch khác commit sau khi nó bắt đầu | Bắt lỗi và retry toàn bộ giao dịch |
Flashback: du hành thời gian nhờ undo
Vì undo đã lưu sẵn ảnh cũ của dữ liệu, Oracle tận dụng nó để cho phép xem/khôi phục dữ liệu trong quá khứ — nhóm tính năng Flashback.
Flashback Query (AS OF)
Truy vấn dữ liệu như nó từng tồn tại tại một mốc thời gian hoặc SCN (minh hoạ — Oracle, không chạy trong sandbox Postgres):
-- Xem bảng như 1 giờ trước
SELECT * FROM accounts
AS OF TIMESTAMP (SYSTIMESTAMP - INTERVAL '1' HOUR)
WHERE id = 1;
-- Chính xác hơn: dùng SCN
SELECT * FROM accounts AS OF SCN 1042837 WHERE id = 1;
Lưu ý về độ chính xác: Oracle theo dõi thay đổi bằng SCN, còn timestamp được ánh xạ sang SCN ở độ mịn khoảng 3 giây. Nếu cần chính xác tuyệt đối (ví dụ điều tra một sự cố đúng thời điểm), hãy dùng SCN thay vì timestamp.
Flashback Version Query (VERSIONS BETWEEN)
Xem lịch sử các phiên bản của một dòng trong một khoảng thời gian — mỗi lần COMMIT tạo một version. Kèm các cột giả VERSIONS_STARTSCN, VERSIONS_XID, VERSIONS_OPERATION (minh hoạ — Oracle, không chạy trong sandbox Postgres):
SELECT versions_startscn, versions_operation, balance
FROM accounts
VERSIONS BETWEEN TIMESTAMP
(SYSTIMESTAMP - INTERVAL '1' HOUR) AND SYSTIMESTAMP
WHERE id = 1;
Flashback Table
Đưa nguyên một bảng trở lại trạng thái quá khứ mà không cần restore backup (minh hoạ — Oracle, không chạy trong sandbox Postgres):
ALTER TABLE accounts ENABLE ROW MOVEMENT; -- điều kiện bắt buộc
FLASHBACK TABLE accounts TO SCN 1042000;
-- hoặc TO TIMESTAMP (SYSTIMESTAMP - INTERVAL '30' MINUTE);
Flashback Drop (recycle bin)
Khi DROP TABLE, Oracle không xóa ngay mà đổi tên bảng và đưa vào recycle bin. Có thể khôi phục (minh hoạ — Oracle, không chạy trong sandbox Postgres):
DROP TABLE orders; -- vào recycle bin
FLASHBACK TABLE orders TO BEFORE DROP; -- lấy lại
Điểm cần nhớ: recycle bin không dùng undo hay flashback logs — nó độc lập với các Flashback khác, chỉ là cơ chế "đổi tên và giữ chỗ".
Flashback Database
Đưa cả CSDL về một SCN/timestamp trong quá khứ, nhanh hơn nhiều so với restore từ backup. Tính năng này dựa vào flashback logs (khác undo), phải được bật trước bằng cấu hình flashback. Đây là công cụ hạng nặng cho các sự cố logic diện rộng (ví dụ chạy nhầm batch xóa dữ liệu).
Use case thực tế
1. Điều tra sự cố bằng Flashback Query. 9h sáng, đội vận hành báo số dư một nhóm tài khoản sai. Bạn nghi một batch chạy lúc 8h đã ghi đè. Thay vì mò log, bạn chụp ảnh dữ liệu trước thời điểm batch:
-- So sánh hiện tại với 1 giờ trước để tìm dòng bị đổi
SELECT id, balance FROM accounts AS OF TIMESTAMP
(SYSTIMESTAMP - INTERVAL '1' HOUR)
MINUS
SELECT id, balance FROM accounts;
Nếu cần khôi phục, dùng FLASHBACK TABLE ... TO TIMESTAMP hoặc ghép MERGE/UPDATE từ ảnh cũ. Toàn bộ chỉ mất vài phút, không cần restore backup — miễn là undo còn giữ đủ dữ liệu trong UNDO_RETENTION.
2. Xử lý ORA-01555 trên hệ thống tải cao. Một job báo cáo đêm quét bảng lớn kéo dài 40 phút, thỉnh thoảng chết giữa chừng với ORA-01555. Nguyên nhân: undo chứa ảnh cũ mà job cần đã bị các DML ban đêm ghi đè, cộng thêm một anti-pattern kinh điển — commit trong vòng lặp fetch khiến undo cần cho chính con trỏ đang chạy bị giải phóng sớm. Cách khắc phục:
- Tăng
UNDO_RETENTIONvà bảo đảm undo tablespace đủ lớn (cân nhắcRETENTION GUARANTEE). - Bỏ commit trong vòng fetch — không commit giữa lúc đang đọc con trỏ.
- Rút ngắn thời gian query (thêm index, chia nhỏ) hoặc chạy dưới Read-Only transaction vào giờ thấp điểm.
Ghi nhớ
- Read consistency = undo: reader không thấy dữ liệu chưa commit; nếu block đã đổi, Oracle dựng lại ảnh cũ từ undo → readers don't block writers, writers don't block readers.
- Oracle sửa tại chỗ và đẩy ảnh cũ ra undo; PostgreSQL giữ version cũ ngay trong heap. Hệ quả đặc trưng của Oracle: ORA-01555.
- Ba chế độ cách ly: Read Committed (mặc định, cấp câu lệnh), Serializable (cấp giao dịch, coi chừng ORA-08177 → retry), Read-Only. Oracle không có Repeatable Read riêng.
- Khóa cấp dòng, tự động, không lock escalation.
FOR UPDATE+NOWAIT/WAIT n/SKIP LOCKED;SKIP LOCKEDlà "vũ khí" cho job queue. - Deadlock được tự phát hiện (ORA-00060); phòng bằng cách khóa theo cùng thứ tự.
- Autonomous transaction (
PRAGMA AUTONOMOUS_TRANSACTION) cho audit log commit độc lập với giao dịch cha. - Flashback (Query, Version Query, Table, Drop, Database) biến undo/flashback logs thành khả năng "du hành thời gian" để điều tra và khôi phục nhanh.
Đọc thêm
Bài viết liên quan
Cách index hoạt động (B-Tree), đọc EXPLAIN, seq scan vs index scan và mẫu tối ưu truy vấn.
Khoá, ràng buộc, quan hệ và chuẩn hoá 1NF/2NF/3NF — thiết kế lược đồ đúng từ đầu.
Kết nhiều bảng đúng cách: các loại JOIN, bẫy thường gặp và phép hợp tập.
Truy vấn lồng, CTE (WITH), CTE đệ quy và hàm cửa sổ — vũ khí cho phân tích nâng cao.