Giới Thiệu

Trong bài viết trước đây của Grokking có đề cập đến bài toán Transferring Money. Vấn đề của bài toán này như đã được đề cập từ trước là do có nhiều thread xử lý cùng thay đổi dữ liệu trong database vào cùng một thời điểm. Nếu nhìn từ phía tiếp cận của những người quản trị database, bài toán Transferring Money là những vấn đề khi truy xuất và thay đổi dữ liệu của cùng một record trong database. Các hệ quản trị cơ sở dữ liệu phổ biến như Postgre, Mysql, SQL Server hay Oracle đều sử dụng một cơ chế gọi là Multiversion Concurrency Control (MVCC hay MCC) để xử lý những trường hợp tương tự như vậy (một ô dữ liệu bị thay đổi bởi nhiều transaction). Bài viết này sẽ đề cập về cơ chế này được Oracle thiết kế như thế nào.

Vì bài viết chỉ đề cập đến cơ chế quản lý phiên bản dữ liệu (MCC) nên yêu cầu bạn đọc phải nắm trước cấu trúc bên trong Oracle. Nếu bạn đọc nào chưa biết về cấu trúc của Oracle thì có thể xem link Oracle này để tìm hiểu trước khi đọc bài viết này.

Tại sao lại cần sử dụng cơ chế MVCC ?

Nếu như một RDBMS không thiết lập cơ chế MVCC thì sẽ gặp phải những vấn đề này:

  • Khi một transaction đọc dữ liệu đang được thực thi thì một transaction khác thay đổi dữ liệu làm giá trị của transaction đọc dữ liệu nhận được không còn chính xác. Đây được gọi là Dirty Read.
  • Khi một transaction (1) đang thực thi quá trình đọc dữ liệu tại một row nhiều lần thì có một transaction (2) khác thay đổi giá trị của row đó khiến cho những giá trị thuộc lần đọc sau này của transaction (1) thay đổi. Đây gọi là Nonrepeatable (fuzzy) reads.
  • Phantom Read là trường hợp khi transaction (1) đang đọc dữ liệu thì một transaction (2) thêm một hoặc nhiều dữ liệu mới vào database khiến cho những lần đọc dữ liệu sau của transaction (1) sẽ lấy thêm những dữ liệu được transaction (2) thêm vào. Phantom Read khác với Nonrepeatable Read ở chỗ nó không thay đổi dữ liệu cũ mà thêm dữ liệu mới vào.
  • Khi một transaction ghi dữ liệu được thực thi, Oracle sẽ tạo ra tập hợp của các change vector để thể hiện sự thay đổi của các row trong block. Trước khi sự thay đổi này được ghi vào data file, Oracle sẽ thực thi một process để lock dữ liệu mới nhất hiện tại của những block sẽ bị thay đổi dữ liệu trên buffer. Sau đó Oracle sẽ ghi những change vector này vào redo log file, rồi mới ghi vào data file.

System Change Number trong Oracle

  • Oracle sử dụng System Change Number (SCN) như một checkpoint để nhận biết được thời điểm một transaction được thực thi. Transaction thực thi trước sẽ có SCN nhỏ hơn SCN của transaction thưc thi sau nó. Nói một cách khác, phiên bản dữ liệu được đọc/ghi taị một row ở một thời điểm nào đó sẽ ứng với một SCN.
  • Cấu trúc của SCN:
    SCN có kich thước 6 bytes. Gồm hai phần, SCN__WRAP (4 bytes) và SCNBASE (2 bytes).
  • Giá trị của SCN__WRAP sẽ tăng từ 0 đến 4 tỷ, sau đó sẽ quay trở lại 0. Khi SCN_WRAP quay trở lại giá trị 0, SCN_BASE sẽ tăng giá trị hiện tại của nó lên 1. (Các phiên bản từ Oracle 11g trên lên thì SCN có kích thước 8 bytes).

Isolation Transaction Level trong Oracle

  • Isolation Transaction là cơ chế để Oracle quản lý từng transaction thực thi trong cơ sở dữ liệu với các cấp độ khác nhau, cho phép transaction đọc/ghi dữ liệu sao cho đảm bảo dữ liệu luôn phải chính xác với từng transaction tại mọi thời điểm.
  • Oracle sử dụng cơ chế Isolation Transaction với các cấp độ: Read Commited, Serializable và Read-Only.
  • Oracle sử dụng Undo Segment như một nơi chứa những phiên bản khác nhau của một block dữ liệu.
    Kích thước của Undo Segment có thể tùy chỉnh theo yêu cầu. Ngoài ra Oracle còn cho phép mở rộng Undo Segment khi vùng lưu trữ này đã đầy. Hoặc cũng có thể Oracle sẽ ghi đè lên những phiên bản dữ liệu tồn tại trước đối với những phiên bản gần thời điểm hiện tại hơn.
  • Khi một transaction thực hiện thao tác ghi hoặc thay đổi dữ liệu trong một row thì Oracle sẽ tạo một bản sao của block chứa row dữ liệu mà transaction muốn thay đổi, sau đó đưa bản sao đó vào Undo Segment, đồng thời tạo một con trỏ trong block dữ liệu hiện tại trỏ đến bản sao của nó trong Undo Segment. Cuối cùng, Oracle sẽ thay đổi dữ liệu hiện tại trên row đó, và chỉ có transaction đó nhìn thấy được dữ liệu mà nó đã thay đổi mà thôi.
  • Với cấp độ Read Commited (được thiết lập mặc định), Oracle sẽ giải quyết vấn đề Dirty Read. Khi một transaction (1) đọc dữ liệu từ database thì trong suốt quá trình đọc diễn ra, bất cứ transaction nào khác thay đổi tập dữ liệu mà transaction (1) đang đọc đều sẽ không ảnh hưởng. Oracle sử dụng Rollback Segment để chứa những “phiên bản” của một row dữ liệu. Khi transaction (1) đọc dữ liệu tại một row hiện tại đã bị thay đổi một transaction thực thi sau nó và transaction đó chưa commit, transaction (1) sẽ đọc dữ liệu của trong trong Undo Segment đế lấy phiên bản dữ liệu của row trước khi transaction (1) thực thi. Đảm bảo transaction (1) không đọc dữ liệu đã bị transaction khác thay đổi.

Oracle sử dụng SCN để đọc các phiên bản dữ liệu

  • Trong cấp độ Read Committed, Oracle chia ra 2 cấp độ nữa, đó là Statement Level Read Committed và Transaction Level Read Committed. Nếu một transaction được thiết lập cấp độ Statement Level Read Committed thì mỗi câu lệnh truy vấn dữ liệu trong transaction đó sẽ ứng với mỗi SCN, tại thời điểm câu lệnh đó bắt đầu thực thi. Còn với Transaction Level Read Committed thì toàn bộ câu lệnh trong transaction đó ứng với mỗi SCN tại thời điểm transaction đó được thực thi.
  • Statement Level Read Committed ngoài ra còn được dùng cho cấp độ Serializable và Read-Only. Thời điểm mỗi câu lệnh query trong transaction được thực thi sẽ tạo.
  • Tuy nhiên, transaction với cấp độ Read Committed sẽ tạo ra tình huống xung đột trong quá trình ghi dữ liệu. Khi một transaction thao tác ghi dữ liệu trên một row muốn thay đổi dữ liệu đã bị chỉnh sửa trước đó bởi một transaction chưa commit nào đó, được gọi blocking transaction. Transaction đọc dữ liệu sẽ chờ cho blocking transaction thực thi xong và gỡ lock của nó ra khỏi row dữ liệu. Sẽ có hai trường hợp xảy ra:
  • Nếu blocking transaction trả lại trạng thái của row dữ liệu trước khi thực thi (roll back) thì transaction đang đợi sẽ thực thi thay đổi trên phiên bản dữ liệu trước khi blocking transaction thực thi.
  • Nếu blocking transaction commit những thay đổi dữ liệu thành công, thì nó sẽ mở lock cho row dữ liệu và transaction thứ hai sẽ thay đổi dữ liệu đã được cập nhật bởi blocking transaction.
  • Cấp độ serializable được thiết lập sẽ cho phép một transaction thấy được những thay đổi của một row do những transaction trước đã được commit ngay khi transaction đó diễn ra, bao gồm những câu lệnh INSERT, UPDATE và DELETE. Cấp độ Serializable sẽ ngăn chặn Nonrepeatable reads và Phantom Reads.
  • Cấp độ Serializable cho phép thay đổi dữ liệu của row nếu những transaction này được sắp xếp thực thi tuần tự. Trong trường hợp đặc biệt, Oracle sẽ cho phép một serializable transaction thay đổi dữ liệu của một row chỉ khi nó xác định được độ ưu tiên của thay đổi đã được tạo ra bởi transaction trước đó đã commit khi serializable transaction bắt đầu.
  • Để làm được điều này, Oracle sử dụng những thông tin điều khiển lưu trữ trong các block để xác định dữ liệu trong block đó đã được thay đổi bởi transaction đã commit hay chưa, được chứa trong Block Header. Những thông tin này sẽ cho biết lịch sử về những transaction nào đã thay đổi dữ liệu của từng row trong block. Tuy nhiên, Oracle sẽ chỉ lưu trữ một số lượng nhất định lịch sử của một row vì giới hạn của kích thước của block.
  • Tất cả những transaction thay đổi hoặc xóa dữ liệu thực thi và commit sau khi serializable transaction bắt đầu đều sẽ phát sinh lỗi.

Bài viết sẽ không đề cập về chế độ Read Only. Bạn đọc có thể tìm hiểu thêm tại đây Oracle Database Concept