Tăng hiệu suất insert hoặc update khối lượng lớn dữ liệu với gem activerecord-import trong Rails
Bài đăng này đã không được cập nhật trong 3 năm
1. Vấn đề đặt ra
Giả sử bạn có file dữ liệu chứa khoảng 1000 đối tượng bản ghi cần insert
vào hệ thống hoặc update
lại nếu đã tồn tại bản ghi.
Nếu bạn thực hiện insert hoặc update từng bản ghi, mỗi lần như vậy bạn phải kết nối với Database
do ActiveRecord
trong rails không hỗ trợ insert hàng loạt record mà phải thực hiện tuần tự. Điều này làm cho chức năng của bạn thực hiện mất nhiều thời gian và giảm hiệu suất của ứng dụng vì phải sinh ra 1000 câu lệnh SQL
để thực hiện việc ghi dữ liệu.
2. Giải pháp
ActiveRecord-import
là một thư viện dùng để insert một số lượng lớn các bản ghi vào trong cơ sở dữ liệu sử dụng ActiveRecord
.
Sử dụng ActiveRecord-import
cho phép sử dụng 1 câu lệnh insert
cùng lúc nhiều values
, điều này giúp bạn cải thiện được suất hiệu của chức năng này.
3. Cài đặt
Khai báo gem file
# Gemfile
gem "activerecord-import", "~> 0.15.0"
Sau khi đã thêm gem, thực hiện lệnh bundle install
để cài gem
4. Sử dụng insert
Import sử dụng Columns và Arrays
Phương thức import
có thể lấy một array các tên cột và array gồm các array khác. Mỗi array con đại diện cho một bản ghi và danh sách các giá trị theo thứ tự như các cột đã chỉ định. Đây là cơ chế import nhanh nhất và cũng nguyên thủy nhất.
columns = [ :name, :address ]
values = [ ["Vendor1", "Hanoi"], ["Vendor2", "Danang"] ]
Vendor.import columns, values
Import sử dụng Hashes
Phương thức import
có thể nhận một array gồm các hash. Các keys tương ứng với tên cột trong cơ sở dữ liệu.
values = [{name: "Vendor1", address: "Hanoi"}, {name: "Vendor2", address: "Danang"}]
Vendor.import values
Import sử dụng Hashes và chỉ rõ tên Column
Phương thức import
có thể lấy một array các tên cột và một array các đối tượng hash. Các tên cột được sử dụng để xác định các trường cần import vào database.
Ví dụ sau sẽ chỉ nhập sách với trường name
vendors = [
{name: "Vendor1", address: "Hanoi"},
{name: "Vendor2", address: "Danang"}
]
columns = [ :name ]
Vendor.import columns, vendors
# Kết quả insert trong table vendors
# name | address
#---------|--------
# Vendor1 | NULL
# Vendor2 | NULL
Import sử dụng ActiveRecord models
Phương thức import
có thể lấy một array các object model. Các thuộc tính sẽ được lấy ra từ mỗi model bằng cách so khớp với các thuộc tính của model
vendors = [
Vendor.new(name: "Vendor1", address: "Hanoi"),
Vendor.new(name: "Vendor1", address: "Hanoi")
]
Vendor.import vendors
Import sử dụng ActiveRecord Models và chỉ rõ tên Column
Phương thức import
có thể lấy một array các object model. Các tên cột được sử dụng để xác định các trường cần import vào database.
vendors = [
Vendor.new(name: "Vendor1", address: "Hanoi"),
Vendor.new(name: "Vendor1", address: "Hanoi")
]
columns = [:name]
Vendor.import columns, vendors
# Kết quả insert trong table vendors
# name | address
#---------|--------
# Vendor1 | NULL
# Vendor2 | NULL
5. Sử dụng update
Trong một số trường hợp bạn muốn cập nhật lại thông tin nếu bản ghi đã tồn tại thì sử dụng phương thức on_duplicate_key_update
, bằng cách chỉ rõ các colunm thuộc tính mà bạn cần cập nhật lại dữ liệu vào database.
Ví dụ bên dưới sẽ cập nhật thông tin address của các bản ghi vendor trong trường hợp đã trùng name
vendors_update = []
list_name_vendor.each do |name_vendor|
vendor = Vendor.find_by name: name_vendor
if vendor.present?
vendor.address = "Address update"
vendors_update << vendor
end
end
# MySQL version
Vendor.import vendors_update, on_duplicate_key_update: [:address]
# PostgreSQL version
Vendor.import vendors_update, on_duplicate_key_update: {conflict_target: [:id], columns: [:address]}
# PostgreSQL shorthand version (conflict target must be primary key)
Vendor.import vendors_update, on_duplicate_key_update: [:address]
6. Một số tùy chọn
Thiết lập validate khi sử dụng
# Thực hiện import nhưng bỏ qua check validate trong model
Vendor.import columns, values, validate: false
#Import có thực hiện check validate trong model
Vendor.import columns, values, validate: true
# Mặc định nếu không khai báo thì sẽ thực hiện check validate trong model
Vendor.import columns, values
Thiết lập số lượng bản ghi cần import cho mỗi câu lệnh
Trong trường hợp các bản ghi quá lớn ,để tránh trường hợp quá tải chúng ta có thể sử dụng batch_size
để điều khiển số lượng bản ghi được import :
Ví dụ sử dụng 2 câu SQL để import 20 bản ghi vendors
Vendor.import vendors, batch_size: 10
Cùng xem kết quả SQL trong trường hợp này:
Callback trong activerecord-import
Có một lưu ý khi bạn sử dụng activerecord-import khi import dữ liệu nó sẽ bỏ qua các method callback liên quan đến create, update và delete bản ghi đó. Sỡ dĩ nó bỏ qua callback là vì với một khối lượng lớn bản ghi được import vào thì việc truy cập ActiveRecord trong bộ nhớ là không cần thiết. Tuy nhiên, trường hợp bạn muốn gọi callback thì có thể tham khảo ví dụ sau:
vendors.each do |vendor|
vendor.run_callbacks(:save) {false}
vendor.run_callbacks(:create) {false}
end
Vendor.import(vendors)
Thao tác này sẽ chạy before_create và before_save callbacks cho mỗi bản ghi vendor. Đối số false truyền vào đây mục đích để ngăn chặn việc thực thi after_save, điều này sẽ không có ý nghĩa trước khi nhập hàng loạt.
7. Kết luận
Bài viết này giới thiệu cho các bạn về gem activerecord-import hi vọng sẽ giúp ích được các bạn trong một vài trường hợp import dữ liệu lớn trong Rails. Nguồn tham khảo: https://github.com/zdennis/activerecord-import
All rights reserved