Ruby on Rails中向序列化数组列添加元素的最高效实现方案
优化方案
以下是两种不同方向的高性能实现方式:
方案1:保留现有字段结构优化
如果你不想改动表结构,可根据你使用的数据库选择适配方案:
1.1 使用PostgreSQL数据库(推荐)
PostgreSQL原生支持数组类型,你可以替换原有的serialize序列化字段,改为数据库原生数组字段:
- 生成迁移文件修改字段类型:
change_column :users, :unread_news_item_ids, :bigint, array: true, default: [], using: "unread_news_item_ids::bigint[]"
- 删除User模型中的
serialize :unread_news_item_ids, Array代码,Rails会自动适配PG数组类型 - 修改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:调整数据结构(性能最优)
序列化字段本身不适合批量更新、查询的场景,推荐新增中间表存储未读消息关联关系:
- 生成迁移创建中间表:
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
- 新增模型关联:
# User模型 has_many :unread_news_items has_many :unread_news, through: :unread_news_items, source: :news_item # NewsItem模型 has_many :unread_news_items
- 修改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
相关产品推荐
相关产品推荐

