Pull to refresh

ActiveRecord vs SQL

Reading time 2 min
Views 7.7K

Привет, %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

Tags:
Hubs:
+24
Comments 49
Comments Comments 49

Articles