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:

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:

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 ý
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).- Gán trực tiếp
rec.field = valuelà write ngầm (chỉ cho tập một); cập nhật cả tập thì gọiwritetường minh. - 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.