前段时间遇到的一个问题,在这里记录下来。
需求:批量的将表A中的status 创建 到表B中 主键为id
例:
表offers id status --> 表offer_scores id offer_id status
1 online 1
2 offline 2
想要通过表offers得到表offer_scores,也就是批量创建。
看起来没有什么难度,尝试写一下试试看
# offers数据迁移至offer_scores Offer.find_each(batch_size: 1000) do |offer| OfferScore.create(offer_id: offer.id) end
好了 运行起来也没问题。
不过当我的数据有6万条的时候 (我的实际情况),性能问题就出现了,它跑得太慢了(大约跑了一个半小时还在跑,最后我不得不中断了它)
思考了一下之后,我决定优化一下这段语句
我查找了一下 发现了一个 好用的方法 叫做 find_in_batches
并且配合了一个gem 叫做 activerecord-import
去文档查看了一下用法, 用法很简单 和 find_in_batches 搭配很不错
这里边记录一下 find_in_batches的用法
通过查询文档 我们可以看出
find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
Person.where("age > 21").find_in_batches do |group| sleep(50) # Make sure it doesn't get too crowded in there! group.each { |person| person.party_all_night! } end Person.find_in_batches.with_index do |group, batch| puts "Processing group ##{batch}" group.each(&:recover_from_last_night!) end
它的查询是可以附带参数的batch_size 这个可以看出 默认的参数是一次查询1000条,恩符合我们要求,
另外 查询之后他会将我们查询的1000条数据放入 group中,好了,现在我们就可以对第一个创建方法进行改良了
Offer.find_in_batches do |group| scores = [] scores = group.map { |offer| OfferScore.new(offer_id: offer.id)} OfferScore.import scores end
首先我们按照一次查询1000的条件 把1000条查询出来,然后新建了 数组,将group 进行遍历 并new了一个 目标对象 存入了 details 中,然后执行 OfferScoreDetail.import details, 这是gem提供的方法,后边是附带参数,这个方法会将我们提供的details集合中的所有对象一次性创建出来。
然后我将上面的代码重运行一次,6w条数据大约执行了100秒左右,相对于第种还是有很大提升的
优化的思路:
如果逐条执行,将会产生大量的数据库查询和创建语句,并且rails每次和数据库交互都会产生链接语句
这些语句如果过多都会产生数据库性能问题,所以类似此类问题,可以优先尝试降低数据库连接次数,查询 次数来提高速度。
上边说了批量创建的问题,下边来说一下批量更新的问题。
假设还是上边的两张表
例:
表offers id status --> 表offer_scores id offer_id status
1 online 1 1
2 offline 2 2
我们的目标是把表offers中的 status更新到表offer_scores中
Offer.find_each(batch_size: 1000) do |offer| OfferScore.find_by(offer_id: offer.id).update_columns(status: offer.status) end
这句已经可以完成我们想要的情况了(我们此次更新使用了update_columns 不触发回调),如果只是简单的更新数据到另一个表 这样子虽然可行,但是显而易见的 性能还是很差,试着跑一下6W条数据,还是非常慢的。
如果不触发回调机制,也不更新索引只是单纯的更新数据,利用SQL语句是不是要更好呢?来试试看另一种方法。
ActiveRecord::Base.connection.exec 利用这个的方法我们就可以直接写SQL 语句了
ActiveRecord::Base.connection.execute("UPDATE offers JOIN offer_scores ON offers.id = offer_scores.offer_id SET offers.status = offer_scores.score")
利用上边这条SQL 6w条数据大约不到2秒就能执行完毕,速度是非常快了,但是有时候我们的业务很复杂并不是简简单单的更新数据就可以,有时候我们需要更新索引的时候就不能采用这种方式,我们要触发更新索引的回调。
例:
表offers id status --> 表offer_scores id offer_id status
1 online 1 1
2 offline 2 2
我们的目标是把表offers中的 status更新到表offer_scores中
同样的任务,但是这次我们需要触发回调
Offer.find_each(batch_size: 1000) do |offer| OfferScore.find_by(offer_id: offer.id).update_attributes(status: offer.status) end
利用 update_attributes就可以触发回调了,当然问题还是性能,首先我们考虑到是不是可以采用事务将更新语句打包一起提交而不是逐条更新呢?尝试一下。
Offer.find_each(batch: 1000) do |offer| ActiveRecord::Base::transaction do OfferScore.find_by(offer_id: offer.id).update_attributes(status: offer.status) end end
这样我们将所有的更新语句都放在事务中,不过这样子虽然所有的更新请求都是一起提交,减少了链接数据库的次数,但是如果一个事务中条目过多恐怕也会处理不过来,不如把每1000条作为一个事务发起请求。
Offer.find_in_batches do |offer_group| ActiveRecord::Base::transaction do offer_group.each do |offer| OfferScore.find_by(offer_id: offer.id).update_attributes(status: offer.status) end end end
这样 我们每次查询1000条打包扔到事务中,事务中将会产生1000条更新请求,我们作为一个请求,链接数据库进行更新。
优化数据库连接方法还有很多,以上方法还有很多可优化之处,以后再来添加。
相关推荐
Rails中的Migration相对来说更适合做数据库的对象集合操作,而自动化的rake则是一个较好的选择,下面来浅谈Ruby on Rails下的rake与数据库数据迁移操作,需要的朋友可以参考下
rails操作 一些基本的操作,配置 连接数据库等方法
布里洛Brillo是Rails数据库清理器和加载器,用于为开发机器制作生产数据库的轻量级副本,并且混淆了敏感信息。 大多数配置是通过YAML完成的:指定要备份的模型,想要与它们的关联以及应该混淆哪些字段(以及如何混淆...
教程内容涵盖了Ruby语言基础、Rails框架搭建、Web应用开发、数据库操作、部署与维护等方面的知识。 适用人群: 本资源适用于对Web开发和Ruby on Rails框架感兴趣的初学者和有一定编程基础的开发者。 能学到什么: ...
1、获取数据 获取第一条、最后一条记录 代码如下: Model.first Model.first(options) Model.find(:first, options) Model.last ...对一组数据进行相同操作 代码如下: User.all.each do |user|
ruby中操作oracle数据库使用的oci8技术相关的gems包,包括3个版本
重定向ActiveRecord(Rails)读取到副本数据库,同时确保所有写入都转到主数据库。 状态 这是Rocket Job原始库的略微修改,只是将其从active_record_slave重命名为active_record_replica 。 为了更清楚地将库与...
FMDB 是基于sqlite的数据库操作类,稳定,但用起来还是不够简洁,PYFMDB是基于FMDB的更高层次的数据库操作类。程序介绍 PYFMDB分为三部分,PYFMDB 基于FMDB负责数据库底层操作处理,PYTable是自定义Table的基类,...
可以将其视为存储在数据库中的全局哈希,它使用类似于ActiveRecord的简单方法进行操作。 跟踪您不想硬编码到Rails应用程序中的所有全局设置。 您可以存储任何类型的对象。 字符串,数字,数组或任何对象。 安装 ...
找不到指定的模块。... 您可能感兴趣的文章:Ruby rails 页面跳转(render和redirect_to)Rails link_to 详解rails常用数据库查询操作、方法浅析学习Ruby你需要了解的相关知识(rvm, gem, bundle, rake,
可以将其视为存储在数据库中的全局哈希,它使用类似ActiveRecord的简单方法进行操作。 跟踪您不想硬编码到Rails应用程序中的所有全局设置。 您可以存储任何类型的对象。 字符串,数字,数组,布尔值或任何对象。 ...
安装建议您在基于Debian的系统上安装Danbooru,因为APT上提供了... 使用rails控制台测试Rails数据库连接。 运行Post.count以确保Rails可以连接到数据库。 如果失败,则需要确保您的Danbooru配置文件正确。 测试Nginx以
railsblog 这是一个通过rails guide上的浅显教程做的基于rails的一个blog,支持发文,删文,评论,删评论,修改文章,并且需要登录,...不解为什么rails还在坚持删除请求用delete而不是用post再在数据库进行逻辑操作。
17.7 在迁移任务之外操作数据库结构 231 17.8 管理迁移任务 231 第18章 ActiveRecord第一部分:基础 233 18.1 表和类 233 18.2 字段和属性 234 18.3 主键与ID 237 18.4 连接数据库 238 18.5 CRUD 242 18.6 聚合与...
rails_ops 该Gem引入了Rails的附加服务层: Operations ... Rails Ops的模型操作需要ActiveRecord,但与数据库/适配器无关安装将以下内容添加到Rails应用程序的Gemfile : gem 'rails_ops' 创建具有以下内容的初始化程
您可能已经注意到,Rails约定中存在一种趋势,即用于呈现表单的逻辑与管理数据库记录变更的操作是分开的。 例如: 控制器中的new动作只是呈现new表单 create操作实际上是处理将表单数据插入数据库的过程 以类似的...
尽管它能够访问 Rails 项目中所有定义环境的数据库,但它仅用于开发。安装要在您的项目中使用 pancakes,只需将以下行添加到您的 Gemfile gem 'pancakes' 然后打开您的终端应用程序,转到 Rails 项目的根目录并运行...
您为操作数据库编写一次性脚本。 然后您将在您的环境中对其进行测试。 如果没问题,现在你将它执行到生产环境。 顺便说一句,脚本应该提交到哪里? 当然剧本已经完成了角色(安息吧..) 但。 任何机会。 客户要求...