Ở bài trước ta thấy cache của ORM giữ giá trị field ra sao. Bài này soi mặt còn lại của cùng cơ chế: khi bạn ghi. Có một sự thật khiến nhiều người ngạc nhiên: gọi tv.write({'diem': 125}) không lập tức chạy UPDATE xuống database. Nó chỉ cập nhật cache và đánh dấu "field này cần ghi". Câu UPDATE thật sự xảy ra muộn hơn — lúc flush.

Hiểu khoảnh khắc đó giải thích được cả một lớp bug ("tôi write rồi mà query SQL không thấy") lẫn vì sao Odoo nhanh đến vậy khi xử lý hàng loạt.

write() ghi vào cache, không ghi ngay xuống DB

ORM cố tình ghi lười (lazy write): nó gom các thay đổi trong cache, và chỉ sinh câu SQL khi cần. Chứng minh bằng cách write qua ORM rồi lập tức đọc thẳng database bằng SQL thô — hai nơi sẽ lệch nhau:

tv.write({'diem': 125})                          # cache = 125
env.cr.execute("SELECT diem FROM quan_the_thanh_vien WHERE id=1")
env.cr.fetchone()[0]                             # 120 — DB VẪN cũ!

Chạy thật:

Ảnh chụp phiên odoo shell blog19 trên bản ghi VIP-0001 có diem 120, sau khi gọi write ORM thì tv chấm diem trong cache là 125, nhưng đọc thẳng database bằng SQL thô ngay sau write và trước flush vẫn ra 120 với ghi chú DB vẫn giá trị cũ write còn treo, sau khi gọi flush_all thì đọc SQL thô ra 125, phần cuối gọi search theo giá trị mới goc cộng 7 trả về id 1 và search tự flush trước SELECT nên đọc SQL sau đó ra 127

Hình 1: write({'diem': 125}) cập nhật cache thành 125, nhưng đọc thẳng DB bằng SQL thô ngay sau đó vẫn ra 120 — câu UPDATE chưa được gửi đi. Chỉ sau flush_all(), DB mới thành 125. Phần cuối cho thấy search tự flush trước khi chạy: search theo giá trị mới (goc+7) tìm ra đúng bản ghi, và DB đã là 127.

Vì sao ghi lười? Để gộp

Ghi lười không phải để hành bạn — nó là tối ưu quan trọng. Tưởng tượng một compute đụng vào 5 field, hay một vòng xử lý write nhiều lần lên cùng bản ghi. Nếu mỗi lần write bắn ngay một UPDATE, bạn có hàng loạt query dư. Ghi lười cho phép ORM gộp tất cả thay đổi đang chờ của một bản ghi thành một câu UPDATE khi flush. Đây là một lý do lớn khiến vòng lặp trên recordset trong Odoo không đắt như ta tưởng.

Bạn hiếm khi phải flush tay — ORM tự làm

Điểm mấu chốt: trong code nghiệp vụ bình thường, bạn gần như không bao giờ gọi flush tay. ORM tự flush đúng vào những lúc kết quả có thể sai nếu không flush:

  • Trước mỗi search/read_group: vì query chạy bằng SQL trên database, ORM phải đẩy các thay đổi đang treo xuống trước, nếu không search sẽ bỏ sót hay lọc sai. (Thấy rõ ở Hình 1: search theo giá trị vừa write tìm được ngay.)
  • Trước commit: commit chốt giao dịch nên phải flush hết.
  • Cuối mỗi request: khung Odoo flush và commit khi xử lý xong.

Nói cách khác, ORM đảm bảo tính nhất quán mà bạn quan sát được luôn đúng — chỉ là thời điểm chạm ổ đĩa được dời lại cho hiệu quả.

Khi nào bạn PHẢI flush tay

tv.write({'diem': 125})     # cache = 125, DB chưa đổi
self.env.flush_all()        # đẩy hết thay đổi đang chờ xuống DB
tv.flush_recordset(['diem'])  # hoặc hẹp hơn: chỉ field này, recordset này

Ảnh chụp đoạn mã Python nền tối, phần đầu gọi tv chấm write đặt diem 125 với chú thích cache 125 DB vẫn 120, tiếp theo env chấm flush_all đẩy xuống DB và flush_recordset cho một field, phần giữa giải thích ghi lười để gộp nhiều thay đổi thành một câu update, phần sau liệt kê các trường hợp ORM tự flush gồm search tự flush trước select và commit cũng flush, phần cuối minh hoạ trường hợp phải flush tay là gọi flush_all trước khi dùng env chấm cr chấm execute chạy SQL thô đọc bảng

Hình 2: Chỉ cần flush tay ở đúng một tình huống — khi bạn đi vòng qua ORM. Nếu sắp chạy một câu SQL thô đọc bảng mà bạn vừa write qua ORM, phải flush_all() trước, nếu không SQL đọc phải dữ liệu cũ (như Hình 1 đã cho thấy). Đây là cặp đối xứng với invalidate ở bài trước: flush đẩy cache→DB, invalidate bỏ cache để đọc lại DB.

Ghép hai bài lại thành một quy tắc gọn:

  • Sắp đọc DB bằng SQL thô → flush trước (để DB có dữ liệu mới nhất).
  • Vừa ghi DB bằng SQL thô → invalidate sau (để cache không giữ giá trị cũ).

Một cạm bẫy hay gặp: ValidationError "trễ"

Vì ràng buộc SQL (như NOT NULL, UNIQUE, CHECK) chỉ bị database kiểm lúc câu UPDATE/INSERT thật sự chạy, một write sai ràng buộc có thể không nổ ngay tại dòng write mà nổ muộn hơn, tại điểm flush (thường là dòng search hay cuối request). Khi gỡ lỗi thấy exception chỉ vào một chỗ tưởng như vô can, hãy nhớ: đó có thể là nơi flush xảy ra, còn lỗi thật thuộc về một write phía trước.

Ba ý mang về

  1. write() ghi lười: cập nhật cache và đánh dấu cần ghi, chưa gửi UPDATE xuống DB ngay (đã chứng minh: cache 125 trong khi DB vẫn 120). Điều này cho phép ORM gộp nhiều thay đổi thành ít query.
  2. flush là lúc thật sự chạm database; ORM tự flush trước search, trước commit, và cuối request — nên code nghiệp vụ bình thường không cần gọi tay.
  3. Chỉ flush_all()/flush_recordset(fnames) tay khi đi vòng qua ORM: flush trước khi đọc DB bằng SQL thô. Cặp đối xứng với invalidate (đẩy cache→DB vs bỏ cache đọc lại DB).

Đã hiểu cache và flush, giờ tới phần "ma thuật" khiến vòng lặp trong Odoo nhanh bất ngờ: Phần sau mổ xẻ prefetch — vì sao lặp qua nghìn bản ghi và đọc field vẫn không bắn nghìn câu query.