为何调用ActiveRecord的update!前需查询所有belongs_to关联?
update!前为何会自动查询所有belongs_to关联? 我遇到一个问题:调用update!批量更新属性时,系统会先自动查询所有belongs_to关联的模型,之后才执行更新SQL。比如我更新Lead模型的status字段,日志里会先查organization、vendor等5个关联表,再执行UPDATE。但我明明没有添加任何验证,也没使用validates_associated。
我记得以前不是这样的,难道是用了已弃用的方法?我知道update_attribute没有这个问题,但我需要用带bang的方法批量更新多个属性。
相关代码和日志如下:
模型代码
class Lead < ApplicationRecord belongs_to :organization belongs_to :vendor belongs_to :prospect belongs_to :visit belongs_to :session has_one :result enum status: [ 'PENDING', 'COMPLETE', ] end
操作代码
lead = Lead.first lead.update!(status: 1)
执行日志示例
[8] pry(main)> s.update!(slug: 'x') TRANSACTION (0.2ms) BEGIN Campaign Load (0.5ms) SELECT "campaigns".* FROM "campaigns" WHERE "campaigns"."id" = $1 LIMIT $2 [["id", "76dfe777-f563-43b1-900b-a2c40ea7d072"], ["LIMIT", 1]] Sequence Update (0.6ms) UPDATE "sequences" SET "slug" = $1, "updated_at" = $2 WHERE "sequences"."id" = $3 [["slug", "x"], ["updated_at", "2023-01-27 01:49:32.472637"], ["id", "19c41977-71c6-4c60-a6b6-7af6ce090d80"]] TRANSACTION (0.7ms) COMMIT => true
关联字段定义示例
t.references :prospect, null: false, foreign_key: true, type: :uuid
这是Rails 5+的默认行为导致的:
原因
从Rails 5开始,belongs_to关联默认设置为optional: false(早期版本是required: true),这意味着Rails会自动为每个belongs_to关联添加存在性验证——在保存(包括update!)时,会检查关联的记录是否真实存在。
这种存在性验证默认会通过加载关联记录(执行SELECT查询)来完成,而不是仅检查外键字段非空。哪怕你在数据库层面设置了外键null: false,Rails依然会执行这个查询,防止外键指向已被删除的记录。
而update_attribute是绕过所有验证的,所以不会触发这些存在性检查,也就不会查询关联表。
解决办法
根据你的需求,可以选择以下几种方案:
关闭特定关联的存在性验证
如果你信任数据库的外键约束,不需要Rails层面的存在性检查,可以给每个belongs_to关联加上optional: true:class Lead < ApplicationRecord belongs_to :organization, optional: true belongs_to :vendor, optional: true # 其他关联同理 end这样Rails就不会再为这些关联触发存在性验证,也就不会执行额外的SELECT查询。
使用
update_columns直接更新数据库
如果你不需要触发任何验证和回调,可以用update_columns,它会直接生成UPDATE语句,完全跳过验证流程:lead.update_columns(status: 1, other_field: 'value')注意:
update_columns不会自动更新updated_at字段,也不会触发任何模型回调(比如after_update)。更新时跳过所有验证
如果你需要保留部分验证,但只想跳过关联的存在性检查,可以在update!中传递validate: false参数:lead.update!({ status: 1 }, validate: false)这个方法会跳过所有验证,适合你只需要批量更新字段、且能确保数据合法性的场景。
内容的提问来源于stack exchange,提问作者Tallboy

