Ruby 优雅地更新数据库索引

luolinae86 · 2022年09月28日 · 最后由 luolinae86 回复于 2022年11月29日 · 885 次阅读

业务背景

业务中有一个表按列创建了普通单列索引,但是没有唯一性约束,现在想要为这个字段添加唯一性索引。

比如我们需要对通知记录表 notificationsorder_id 字段添加唯一约束,但是这个表已经有了 order_id 的普通索引。

不优雅的做法

直接添加唯一索引,当数据库里面已经存在order_id重复记录的时候会报错。

Duplicate entry '1' for key 'index_notifications_on_order_id'

如果直接删除索引,再新建索引,在索引新建完成前,业务会受到影响,涉及到按order_id 查询记录的会导致全表扫描,影响数据库性能

优雅的处理方式

notifications 表为例

  1. 删除表中order_id字段重复的记录
def delete_duplicated_records
  # 查找存在重复的记录
  dup_order_ids = ::Notification.group(:order_id).having('COUNT(*) > 1').pluck(:order_id)
  dup_order_ids.each_slice(500) do |order_ids|
    not_remove_order_ids = ::Notification.where(order_id: order_ids).group(:order_id).having('COUNT(*) > 1').pluck('MIN(id)')
    Notification.where(order_id: order_ids).where.not(id: not_remove_order_ids).destroy_all
  end
end
  1. 将原索引重命名
rename_index :notifications, :index_notifications_on_order_id, :non_uniq_index_notifications_on_order_id
  1. 添加唯一索引
add_index :notifications, :order_id, unique: true
  1. 删除原索引
remove_index :notifications, name: :non_uniq_index_notifications_on_order_id, column: :order_id

注意事项

  1. 涉及到索引的创建,当表数据量比较大时,一定要避开业务高峰期,切记!!!
  2. 删除重复记录之前,如果数据对业务有用的,可以先将数据进行拷贝
hjiangwen 回复

感谢补充

前天刚好遇到这个问题,实际上加唯一约束前删重复数据的时候就干了好久,而且在上亿条里头找重复的还是需要时间。最后只能容忍之前的重复数据。新的数据双写 redis 和 pg,用 redis 来判断重复

SunA0 回复

业务并发高的时候,redis 不能保证真正的去重复,😀

6 楼 已删除

这一步:add_index :notifications, :order_id, unique: true,因为是大表,所以会花些时间。 但由于老索引并没有被删除,所以查询效率不受影响。 在add_index :notifications, :order_id, unique: true完成后,其实是针对同一个 column order_id 同时有两个索引。 原理是这样的吗? @luolinae86

gazeldx 回复

是的,为了保证当前查询业务不受影响,通过重命名,将之前老的索引,暂作为冗余索引。 当唯一索引创建成功之后,再将冗余索引删除即可。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号