如何在Rails中结合SQL GROUP子句与FOR UPDATE SKIP LOCKED?
Rails中结合GROUP BY与FOR UPDATE SKIP LOCKED的ActiveRecord实现问题
问题描述
我希望在Rails中使用ActiveRecord而非原生SQL,实现结合SQL GROUP子句与FOR UPDATE SKIP LOCKED的功能。现有通用模型Job,包含client_class属性,需按该属性分组,同时排除超过类变量CLIENT_LIMITS设定上限的客户端。代码实现如下:
CLIENT_LIMITS = { CLIENT_1: 2, CLIENT_2: 3, ... }.freeze unavailable_clients = Job.running .group(:client_class) .count .select { |k, v| v >= CLIENT_LIMITS[k] } .keys job = Job.lock("FOR UPDATE SKIP LOCKED") .queued .where.not(client_class: unavailable_clients) .sample
错误信息
运行后出现如下错误:
.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/activerecord-6.1.4.1/lib/active_record/connection_adapters/postgresql_adapter.rb:672:in `exec_params': PG::FeatureNotSupported: ERROR: FOR UPDATE is not allowed with GROUP BY clause (ActiveRecord::StatementInvalid) .rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/activerecord-6.1.4.1/lib/active_record/connection_adapters/postgresql_adapter.rb:672:in `exec_params': ERROR: FOR UPDATE is not allowed with GROUP BY clause (PG::FeatureNotSupported)
解决方案
核心原因
PostgreSQL不允许同一个查询中同时包含GROUP BY和FOR UPDATE子句。你的代码逻辑上是分两次查询,但可能因为作用域包含GROUP BY、延迟加载特性或sample方法的数据库层面执行,导致两个查询逻辑被意外合并,触发了该错误。
具体修复步骤
在SQL层面完成超量客户端筛选,隔离统计查询
把超量客户端的判断逻辑放到SQL的HAVING子句中,直接从数据库获取结果,确保统计查询独立执行,不会和后续锁查询合并:# 动态生成匹配各客户端上限的HAVING条件 having_conditions = CLIENT_LIMITS.map do |client, limit| "client_class = '#{client}' AND COUNT(*) >= #{limit}" end.join(" OR ") unavailable_clients = Job.running .group(:client_class) .having(having_conditions) .pluck(:client_class) .map(&:to_sym) # 保证和CLIENT_LIMITS的key类型一致替换sample方法,避免数据库层面的查询冲突
sample在数据量大时会自动切换为数据库层面的随机查询,可能和锁语句产生冲突。改用显式的数据库随机排序+取第一条,逻辑更可控:job = Job.lock("FOR UPDATE SKIP LOCKED") .queued .where.not(client_class: unavailable_clients) .order(Arel.sql('RANDOM()')) # 用Arel规避SQL注入风险 .first检查并清理作用域中的GROUP BY
确认running和queued作用域中没有包含GROUP BY语句,如果有,需要调整作用域逻辑,或者在查询时通过unscope(:group)移除分组:# 若running作用域自带GROUP BY,查询时手动移除 unavailable_clients = Job.running.unscope(:group) .group(:client_class) .having(having_conditions) .pluck(:client_class)
内容的提问来源于stack exchange,提问作者kskickislk
相关产品推荐
相关产品推荐

