MySQLで大量のデータを挿入して、SELECTパーフォマンスを比較する
By Huy Van
大量のデータを扱う機会があるため、データが非常に多いときMySQLのパフォーマンスはどうなるかを検討した。
- 検討するテーブル:
- 検討したいquery:
SELECT * FROM activity_records WHERE date BETWEEN '2014-01-01' AND '2014-04-10';
なのでdate
コラムにindexをつけた。このテーブルに5億ぐらいのレコードがあるとき、レスポンス時間はどのぐらいなのか検討したい。
- 環境:
SSD: 256GB
テーブルに5億レコードを挿入する
この記事は大変参考になりました:Mass inserting data in Rails without killing your performance
単純の(5*10**8).times { Model.create(options) }
なら20日間がかかるのに対し、1つのINSERTを大量のデータをまとめ(bulk insertと呼ばれる)すると10時間しかかからない。
最終のrakeタスクのコードは以下となる
# insert_data.rake
namespace :insert_data do
desc "Insert dummy activity records"
task run: :environment do
TOTAL_RECORDS = 500000000
RECORDS_PER_TIME = 50000
CONN = ActiveRecord::Base.connection
start_time = Time.now
(TOTAL_RECORDS/RECORDS_PER_TIME).times do |i|
inserts = Array.new
RECORDS_PER_TIME.times do |j|
date = (Time.new(2014, 1, 1, 00, 00, 00) + i * RECORDS_PER_TIME * 0.01 + j * 0.01).to_s(:db)
created_at = Time.now.to_s(:db)
updated_at = Time.now.to_s(:db)
inserts.push "(1, #{rand(1..3)}, '#{date}', '#{created_at}', '#{updated_at}')"
end
sql = "INSERT INTO activity_records (`user_id`, `hogehoge_id`, `date`,`created_at`,`updated_at`) VALUES #{inserts.join(", ")}"
CONN.execute sql
end_time = Time.now
puts "Inserted #{RECORDS_PER_TIME*(i+1)} records in #{(end_time - start_time)} seconds"
end
end
end
途中でディスクフルになったが、ほぼ5億レコードになった。
mysql> SELECT COUNT(*) FROM activity_records;
+-----------+
| COUNT(*) |
+-----------+
| 480101568 |
+-----------+
1 row in set (2 min 8.37 sec)
パーフォマンスを比較
mysql> SELECT * FROM activity_records WHERE date = '2014-02-15';
(結果は省略)
100 rows in set (0.00 sec)
mysql> SELECT * FROM activity_records WHERE date BETWEEN '2014-02-15' AND '2014-02-16' limit 10;
(結果は省略)
10 rows in set (0.00 sec)
mysql> SELECT * FROM activity_records WHERE date BETWEEN '2014-02-15' AND '2014-02-16';
(結果は省略)
5550000 rows in set (25.96 sec)
抽出結果が少ない場合はすぐ結果が出ますが、抽出結果が多い(555万)の場合は遅い(25.96秒)。このqueryをプロフィリングすると
mysql> show profile for query 23;
+----------------------+-----------+
| Status | Duration |
+----------------------+-----------+
| starting | 0.000095 |
| checking permissions | 0.000012 |
| Opening tables | 0.000020 |
| init | 0.000031 |
| System lock | 0.000008 |
| optimizing | 0.000011 |
| statistics | 0.000105 |
| preparing | 0.000014 |
| executing | 0.000003 |
| Sending data | 25.960623 |
| end | 0.000012 |
| query end | 0.000006 |
| closing tables | 0.000032 |
| freeing items | 0.000053 |
| logging slow query | 0.000008 |
| cleaning up | 0.000020 |
+----------------------+-----------+
16 rows in set, 1 warning (0.01 sec)
何も言えないです。やはり大量データを抽出するというqueryを避けた方が良い。今考えられるのはbatch処理と別の統計テーブルを用意することです。
おまけ
5億レコードの容量はどのぐらいなのか調べてみると、
mysql> show table status like 'activity_records'\G
*************************** 1. row ***************************
Name: activity_records
Engine: InnoDB
Version: 10
Row_format: Compact
Rows: 446974130
Avg_row_length: 49
Data_length: 21990735872
Max_data_length: 0
Index_length: 22140682240
Data_free: 5242880
Auto_increment: 480151569
Create_time: 2015-01-30 15:55:56
Update_time: NULL
Check_time: NULL
Collation: utf8_general_ci
Checksum: NULL
Create_options:
Comment:
1 row in set (0.00 sec)
データサイズとインデックスサイズの合計で42GBぐらいです! だから、MacのSSDがフルになっているね。検討が終わったら全部消します!