You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Ruby on Rails中向序列化数组列添加元素的最高效实现方案

优化方案

以下是两种不同方向的高性能实现方式:

方案1:保留现有字段结构优化

如果你不想改动表结构,可根据你使用的数据库选择适配方案:

1.1 使用PostgreSQL数据库(推荐)

PostgreSQL原生支持数组类型,你可以替换原有的serialize序列化字段,改为数据库原生数组字段:

  1. 生成迁移文件修改字段类型:
change_column :users, :unread_news_item_ids, :bigint, array: true, default: [], using: "unread_news_item_ids::bigint[]"
  1. 删除User模型中的serialize :unread_news_item_ids, Array代码,Rails会自动适配PG数组类型
  2. 修改inform_users方法,直接用update_all批量更新:
def inform_users
  User.update_all(["unread_news_item_ids = array_append(unread_news_item_ids, ?)", id])
end

该方案仅需1条SQL即可完成所有用户的未读消息ID追加,性能远高于逐行保存。

1.2 使用MySQL等不支持原生数组的数据库

如果仍使用序列化YAML格式存储数组,直接批量更新字符串格式容易出错,可对原有逻辑使用分批查询更新缓解压力:

def inform_users
  User.find_each do |user|
    user.unread_news_item_ids << id
    user.save(validate: false)
  end
end

find_each会分批加载用户数据,避免一次性加载全量用户数据占满内存,但本质还是逐行更新,仅作为兼容方案。

方案2:调整数据结构(性能最优)

序列化字段本身不适合批量更新、查询的场景,推荐新增中间表存储未读消息关联关系:

  1. 生成迁移创建中间表:
create_table :unread_news_items do |t|
  t.bigint :user_id, null: false
  t.bigint :news_item_id, null: false
  t.timestamps
end
add_index :unread_news_items, [:user_id, :news_item_id], unique: true
  1. 新增模型关联:
# User模型
has_many :unread_news_items
has_many :unread_news, through: :unread_news_items, source: :news_item

# NewsItem模型
has_many :unread_news_items
  1. 修改inform_users方法批量插入数据:
def inform_users
  user_ids = User.pluck(:id)
  insert_records = user_ids.map { |uid| { user_id: uid, news_item_id: id } }
  UnreadNewsItem.insert_all(insert_records)
end

该方案仅需1条批量插入SQL即可完成操作,后续标记已读、查询未读列表的性能也远高于操作序列化数组,生产环境推荐使用。

额外优化建议

如果你的用户量级超过10万,建议将inform_users的逻辑放到异步任务队列执行,避免阻塞管理员创建NewsItem的请求超时。

内容的提问来源于stack exchange,提问作者Tintin81

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.23 18:27:00