Sau create, thao tác ghi thứ hai là write() — cập nhật bản ghi đã có. Nó có một đặc điểm mạnh mà nếu tận dụng đúng, code vừa gọn vừa nhanh: write trên một recordset nhiều phần tử chỉ sinh một câu UPDATE cho cả tập. Bài này mổ xẻ cơ chế đó và đo thật để bạn thấy khác biệt.

write cả tập = một câu UPDATE

Gọi write({...}) trên recordset nào thì mọi bản ghi trong đó nhận cùng giá trị, bằng một câu SQL:

Cách dùng write trong Odoo 19 cập nhật cả tập bằng một câu UPDATE: write trên recordset nhiều gọi recs.write với city Đà Lạt và active False sinh ra một câu SQL UPDATE res_partner SET city bằng Đà Lạt WHERE id IN danh sách cho mọi bản ghi; gán trực tiếp thuộc tính là write ngầm, p.city bằng Nha Trang tương đương p.write city Nha Trang chỉ dùng khi p là tập một phần tử; trường quan hệ trong write, order.write với partner_id bằng 5 là Many2one truyền id, tag_ids bằng lệnh 6 0 list ids là Many2many thay toàn bộ; bẫy là đừng write trong vòng lặp khi có thể write cả tập, for r in recs r.write là N câu UPDATE còn recs.write là một câu UPDATE

Hình 1: recs.write({'city': 'Đà Lạt'}) sinh ra một câu UPDATE ... WHERE id IN (...) cho toàn tập. Gán trực tiếp p.city = '...' là write ngầm (chỉ dùng khi p là tập một). Trường quan hệ dùng cú pháp riêng: Many2one truyền id, Many2many dùng lệnh (6, 0, ids). Và bẫy kinh điển: đừng write trong vòng lặp khi có thể write cả tập.

Đo thật: write cả tập vs vòng lặp

Đây là output thật từ odoo shell trên db blog19, kèm số đo hiệu năng:

Output thật write chạy trên tập bản ghi database blog19: tạo recs bằng create bốn bản ghi trả về res.partner 441 442 443 444, mapped city trước là bốn giá trị CũTP; write một lần trên cả tập bốn với city Đà Lạt và comment Đã cập nhật hàng loạt, sau đó mapped city trả về bốn giá trị Đà Lạt, và recs chỉ số 0 chấm comment trả về chuỗi có thẻ p bọc Đã cập nhật hàng loạt vì trường Html tự bọc thẻ; gán trực tiếp p.city bằng Nha Trang là write ngầm cho kết quả p.city bằng Nha Trang; đo hiệu năng 100 write lẻ trong vòng for mất 0.012 giây còn một write cả tập 100 mất 0.001 giây, khung nhấn write cả tập nhanh 8.4 lần

Hình 2: Output thật. recs.write({'city': 'Đà Lạt'}) đổi cả 4 bản ghi cùng lúc. Trường comment (kiểu Html) tự bọc thẻ <p>. Và số đo hiệu năng: 100 lần write lẻ trong vòng for mất 0.012 giây, còn một lệnh write trên cả tập 100 chỉ 0.001 giây — nhanh gấp 8.4 lần.

Gán trực tiếp cũng là write

Trong code Odoo bạn thường thấy record.field = value — đây không phải gán biến Python thường, mà là write ngầm xuống CSDL:

partner.name = 'Tên mới'        # tương đương partner.write({'name': 'Tên mới'})

Vì là write ngầm cho từng bản ghi, chỉ dùng khi partner là tập một phần tử (nhớ ensure_one). Muốn cập nhật cả tập thì gọi write({...}) tường minh — hiệu quả hơn hẳn.

Trường quan hệ trong write

  • Many2one: truyền id — order.write({'partner_id': 5}).
  • Many2many / One2many: dùng lệnh đặc biệt (chi tiết ở bài riêng). Vài lệnh hay dùng:
    rec.write({'tag_ids': [(6, 0, [1, 2, 3])]})   # (6,0,ids): THAY toàn bộ bằng ids này
    rec.write({'tag_ids': [(4, 5)]})              # (4,id): THÊM một bản ghi vào tập
    rec.write({'tag_ids': [(3, 5)]})              # (3,id): GỠ liên kết bản ghi id 5
    

Điều xảy ra ngầm khi write

write không chỉ chạy UPDATE. Sau khi ghi, Odoo còn:

  • Tính lại các trường computed phụ thuộc trường vừa đổi (@api.depends).
  • Chạy ràng buộc (@api.constrains) để kiểm tính hợp lệ.
  • Ghi log cho các trường có tracking=True (khung chat "trường X đổi từ A sang B").

Đây là lý do write "một trường" đôi khi kéo theo nhiều việc — và cũng là lý do write cả tập một lần hiệu quả hơn: các bước phụ này chạy gộp cho cả lô.

Lưu ý an toàn

  • write cần quyền ghi: Access Rights + Record Rules quyết định bạn được ghi bản ghi nào; sai quyền là lỗi.
  • write trong sandbox odoo shell: nhớ env.cr.commit() mới lưu (như đã nói ở bài shell). Trong module, Odoo tự commit cuối request.
  • Đừng write trong vòng lặp nếu mọi bản ghi nhận cùng giá trị — gom lại recs.write({...}).

Nhớ ba ý

  1. write({...}) trên recordset nhiều sinh một câu UPDATE cho cả tập — đo thật nhanh gấp 8.4 lần vòng lặp write lẻ (100 bản ghi).
  2. Gán trực tiếp rec.field = value là write ngầm (chỉ cho tập một); cập nhật cả tập thì gọi write tường minh.
  3. write kéo theo tính lại computed, chạy constrains, ghi tracking — nên write cả tập một lần hiệu quả hơn nhiều lần lẻ; trường quan hệ dùng lệnh đặc biệt.

Tạo, sửa xong, phần còn lại của bộ tứ CRUD là đọc. Phần sau so sánh search, search_read, search_count — ba cách truy vấn với hiệu năng rất khác nhau, và khi nào dùng cái nào.