我想向我的用户表添加一列,并为所有这些添加slug。问题是我的DB中有超过100万用户。
我看过各种博客解释了不同的方法,但我不想在生产数据库中冒险冒险。
我发现的方法:
-
以下方法建议将代码添加到迁移中的slug 文件本身。
class AddStatusToUser < ActiveRecord::Migration class User < ActiveRecord::Base end def up add_column :users, :status, :string User.find_each do |user| user.status = 'active' user.save! end end def down remove_column :users, :status end end
-
我已经写了此方法,该方法由 rake 任务:以下一个问题是它已经运行了4天,到目前为止仅生成了40万个sl。我想快速做,但不知道如何做。
find_in_batches :
产生查找选项发现的每批记录 大批。每个批次的大小由:batch_size选项设置;这 默认值为1000。
您可以控制批处理处理的起点 提供:开始选项。如果您愿意,这特别有用 多个处理相同处理队列的工人。你(们)能做到 工人1处理ID 0至10,000和工人2之间的所有记录 处理10,000及以后(通过设置:开始选项 工人)。
不可能设置订单。自动设置为 登上主键(" ID ASC")以进行批处理排序 工作。这也意味着该方法仅与基于整数的 主键。您也无法设置极限,用于控制 批量尺寸。
为了避免DB性能问题,我为1000个用户提供了每年的睡眠时间为2秒钟。我应该删除睡眠方法吗?我应该只运行User.find_each(&:save)
还是方法1?
task :add_slug_to_all_users => :environment do
i=0
batchSize = 1000
puts "started at :#{Time.now}"
# find_in_batches method provides the users in batches of 1000
# so that the update is not triggered for all the rows at once which may lock the table completely.
User.where("slug is null and email is not null").find_in_batches(batch_size: batchSize) do |users|
sleep(2)
users.each {|u| u.save!; i+=1;}
puts "updated #{i} records at: #{Time.now}"
end
puts "Completed the Task at: #{Time.now}n"
end
更新1 :我正在使用友好的gem生成slugs。
更新2 :我已经运行SHOW CREATE TABLE users
,我得到了:
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`first_name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`last_name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`p_views` int(11) DEFAULT '0',
`p_desc` text COLLATE utf8_unicode_ci,
`p_title` text COLLATE utf8_unicode_ci,
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
`t_zone` varchar(255) COLLATE utf8_unicode_ci DEFAULT 'UTC',
`college` varchar(500) COLLATE utf8_unicode_ci DEFAULT NULL,
`degree` text COLLATE utf8_unicode_ci,
`p_no` varchar(15) COLLATE utf8_unicode_ci DEFAULT NULL,
`slug` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_phone_number` (`p_no`),
UNIQUE KEY `index_users_on_phone_no` (`p_no`),
UNIQUE KEY `index_users_on_slug` (`slug`),
KEY `use_index_on_college` (`college`(255))
) ENGINE=InnoDB AUTO_INCREMENT=2194 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci |
请注意,我已经从上述结果中删除了大多数字段。列slug
以URL友好方式存储 first_name 和 last_name 的组合。
例如如果用户的名称是:
id first_name last_name
1 Arun Kumar
2 Arun Kumar
slug 生成的看起来像这样:
id slug
1 arun-kumar
2 arun-kumar1
在这种情况下,通用第三方软件只能阻碍。您最好去SQL完成工作。
如果" slug"是一个简单的序列编号,那么添加AUTO_INCREMENT
将是明显的解决方案,并且是永久解决方案。也就是说,所有未来的添加都会自动生成slug。可以用一个语句来完成:
ALTER TABLE t
ADD COLUMN slug INT UNSIGNED AUTO_INCREMENT,
INDEX(slug);
slug
成为PRIMARY KEY
可能会更好(请提供SHOW CREATE TABLE
。),但这可能需要在桌子上严重锁定;因此,普通指数更好。测试它。它可能"足够快"。
下一个想法是pt-online-schema-change
(请参阅Percona.com),它是有效地执行ALTERs
的特殊工具,其影响几乎为零。它涉及添加TRIGGER
来捕获写作和复制的块。略有影响需要复制"最后一点点"。最终的RENAME TABLE real TO old, new TO real;
是原子,基本上是瞬时的。它甚至动态地调整了"睡眠"。这是一个很好的工具,拥有多年的经验。
但是,PTOSC可能无法添加与PRIMARY KEY
一样关键的东西,因此我对普通INDEX
的建议(上)。
设置值(通过UPDATE
),一次是正确的方法。我已经写了块提示;这是针对DELETE
的,但可以适用于UPDATE
。
不知道find_in_batches()
中的"封面"是什么,我不能说它是好是坏。我确实知道OFFSET
几乎总是很糟糕。"记住你离开的地方"通常好多了。但是,如果您还没有UNIQUE
或PRIMARY
密钥,很难做到这一点。PRIMARY
由于其聚类而更好。(请提供SHOW CREATE TABLE
,所以我不必猜测。)
如果您的示例代码每次都在表的开头开始,那么它与使用OFFSET
一样糟糕 - 每次迭代都会比以前较慢,因为它跳过了越来越多的行。
添加列后,请确保检查对表的所有引用-SELECT *
现在将有一个列(一个不使用*
的原因之一)。UPDATEs
和INSERTs
可能与丢失的列一起使用,但您需要检查。
update
有两个步骤 - 添加slug
列,然后填充它。您已经完成了第一步。
要执行第二步,我建议使用AUTO_INCREMENT PRIMARY KEY
一次逐步浏览100行。100足够低,以至于没有太多侵入性。AI PK将覆盖整个桌子,并且非常有效,因此您不需要缓慢的OFFSET
或搜索未扣除的集合。我在这里讨论有效的分解。它是用DELETE
编写的,但这些技术适用于UPDATE
。