Привет, %username%!
Недавно я начал изучать Ruby on Rails и передо мной встала задача — реализовать импорт данных из CSV файла в MySQL таблицу. Задача не сложная и код я написал довольно быстро, но вот только я был очень удивлен тем, что пока он выполнялся, я успел налить себе кофе и выкурить сигарету.
— Что-то здесь не так! — подумал я и начал копать.
Для тестов я создал модель test_object, таблицу в базе:
create_table :test_objects do |t| t.column :field1, :integer t.column :field2, :integer t.column :field3, :integer t.column :field4, :integer t.column :field5, :integer end
и написал небольшой скрипт:
values = [] 5000.times do values.push({:field1 => rand(10000), :field2 => rand(10000), :field3 => rand(10000), :field4 => rand(10000), :field5 => rand(10000)}) end values.each do |item| TestObject.new(item).save end
Время выполнения: ~30 секунд в development окружении, ~22 секунды — production. Многовато…
Потом вспомнил про плагин ar-extension, на который я наткнулся в поисках реализации sql-запросов вида «INSERT… ON DUPLICATE KEY UPDATE» на рельсах. Он позволяет вставлять данные в таблицу одним запросом.
require 'ar-extensions' require 'ar-extensions/adapters/mysql' require 'ar-extensions/import/mysql' .... objs = [] values.each do |item| objs.push(TestObject.new(item)) end TestObject.import objs
Время выполнения: ~14 секунд в development окружении, ~12 секунд — production. Уже лучше, но все равно много.
Затем я решил попробовать отказаться от использования ActiveRecord в этом куске кода, и использовать простой sql-запрос
sql = ActiveRecord::Base.connection() values.each do |item| sql.execute("INSERT INTO `test_objects` (`field1`, `field2`, `field3`, `field4`, `field5`) VALUES ('#{item[:field1]}', '#{item[:field2]}', '#{item[:field3]}', '#{item[:field4]}', '#{item[:field5]}')") end
Время выполнения: ~5 секунд в development окружении, ~3.5 секунды — production.
А если использовать транзакции
sql = ActiveRecord::Base.connection() sql.execute("START TRANSACTION") values.each do |item| sql.execute("INSERT INTO `test_objects` (`field1`, `field2`, `field3`, `field4`, `field5`) VALUES ('#{item[:field1]}', '#{item[:field2]}', '#{item[:field3]}', '#{item[:field4]}', '#{item[:field5]}')") end sql.execute("COMMIT")
Время выполнения: ~2 секунды в development окружении, ~0.8 секунд — production! Намного быстрее, чем при использовании ActiveRecord!
Если использовать bulk-insert (спасибо CWN и Ventura):
objs = [] values.each do |item| objs.push("('#{item[:field1]}', '#{item[:field2]}', '#{item[:field3]}', '#{item[:field4]}', '#{item[:field5]}')") end sql.execute("INSERT INTO `test_objects` (`field1`, `field2`, `field3`, `field4`, `field5`) VALUES " + objs.join(','))
Время выполнения: ~0.1-0.2 секунды в production окружении!
Вывод: ActiveRecord — чертовски удобная штука, и я ни в коем случае не призываю отказаться от его использования, но в тех участках кода, где не нужны его широкие возможности и важно быстродействие, лучше использовать обычные SQL-запросы.
UPD: Добавлено время работы в production окружении и тест с использованием bulk-insert