做量化交易需要大量的金融数据,通常保存在mysql数据库中,有时候我们需要将这些金融数据从一台设备迁移到另一台设备,这时就意味着将若干上亿行的数据表迁移过去。在另一篇文章中,我介绍了两种使用mysqldump工具的数据迁移方法,这两种方法的本质是将一张表中的数据通过SQL语句,一条一条地添加到新的数据库中,虽然方便,但是速度奇慢:
下面是我记录的几张数据表迁移的实际耗时:
ID | 表 | 数据行数 | 占用磁盘空间 | 传输耗时 |
---|---|---|---|---|
1 | express表 | 25k行 | 3.5MB | 传输耗时10秒 |
2 | financial表 | 247k行 | 289MB | 传输耗时3分23秒 |
3 | fund_daily表 | 1.6M行 | 129MB | 传输耗时8分6秒 |
4 | index_daily表 | 3.7M行 | 309MB | 传输耗时30分 |
5 | fund_15min表 | 6.6M行 | 1.57GB | 传输耗时1小时30分 |
6 | stock_indicator表 | 11.7M行 | 2.06G | 传输耗时3小时 |
7 | index_hourly表 | 7.6M行 | 536MB | 传输耗时6.5小时 |
8 | index_1min表 | 25.4M行 | 4.22G | 传输耗时44小时 |
9 | fund_5min | 20.5M行 | 1.43GB | 传输耗时52小时 |
可见,必须找到一种足够快的方法,以实现数据高速传输
查询mysql官方文档,得知mysalimport是一种比通常的mysqldump快很多的方法,其思路与mysqldump有很大不同:
mysqldump的操作思路:
- 将数据表dump为一个文件:
- .sql文件,包含创建数据表以及逐行插入所有数据的SQL语句- 使用.sql文件中的SQL语句创建表并逐行插入所有数据
mysqlimport的操作思路:
- 将数据表dump为两个文件:
- 一个.sql文件,代表数据表结构
- 一个.txt / csv/tsv文件,以纯文本形式保存的所有数据- 使用.sql文件创建一个空的数据表
- 使用mysqlimport命令将txt文件中的数据批量导入空数据表中
- 在导入数据表时关闭auto_commit,并暂时关闭一些无关的检查
由于数据是批量导入数据表中的,因此mysqlimport的速度比mysqldump方法快得多。
生成数据表文件使用下面命令:
mysqldump --tab=/path db_name
path
是一个事先创建好的路径,上面命令将db_name中的所有数据表dump到这个路径下,每个数据表都会生成两个文件,一个是表头信息,另一个是数据。
举例如下:
mysqldump -u jackie -p --tab=/tmp test_db
将上述所有文件拷贝到目标服务器后,可以下面命令批量创建空表单(请提前创建空数据库):
cat /path/*.sql | mysql db_name
举例如下
cat /tmp/*.sql | mysql -u jackie -p test_db
使用下面的命令批量导入path路径下的全部数据:
mysqlimport db_name /path/*.txt
举例如下
mysqlimport -u jackie -p test_db /path/*.txt
如果不用mysqlimport,或者某些情况下mysqlimport命令无法使用,可以进入mysql使用LOAD DATA 语句:
mysql> LOAD DATA [LOCAL] INFILE 'file_name' INTO TABLE table_name;
例如:
mysql> LOAD DATA LOCAL INFILE 'fund_hourly.txt' INTO TABLE fund_hourly;
mysql> SET autocommit=0;
每次导入一张表之后都要手动commit:
mysql> COMMIT;
mysql> SET foreign_key_checks=0;
mysql> SET unique_checks=0;
mysql> ALTER INSTANCE DISABLE INNODB REDO_LOG;
效果非常明显:
ID | 表 | 数据行数 | 占用磁盘空间 | mysqldump管道传输耗时 | mysqlimport 导入耗时 |
---|---|---|---|---|---|
1 | stock_hourly | 53M行 | 5.6GB | 52小时 | 53分 |
2 | index_5min | 86.5M行 | 8.6GB | 29小时 | 1小时22分 |
3 | stock_15min | 1.83亿行 | 15GB | 估计100+小时 | 2小时40分 |
可见提速效果能达到20倍以上,对于上亿行数据,提速效果明显。
数据导入结束后别忘记恢复之前修改的设置:
mysql> SET autocommit=0;
mysql> SET foreign_key_checks=0;
mysql> SET unique_checks=0;
mysql> ALTER INSTANCE ENABLE INNODB REDO_LOG;
以下坑我已经踩过了,遇到同样坑的同学,可以照下面的指南避坑:
mysqldump: Can't create/write to file '/xxx.sql' (Errcode: 30 - Read-only file system)
或者
mysqldump: Got error: 1290: The MySQL server is running with the --secure-file-priv option so it cannot execute this statement when executing 'SELECT INTO OUTFILE'
这是由于系统尝试dump文件到没有权限的路径,或者这个路径被认为是不安全的,此时可以查看系统配置变量secure_file_priv:
mysql> SHOW VARIABLES LIKE 'secure_file_priv';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| secure_file_priv | NULL |
+------------------+-------+
1 row in set (0.01 sec)
打开mysql的配置文件,通常是’my.cnf’,在其中添加:
[mysqld]
secure-file-priv = ""
重新启动mysql,再次检查变量:
mysql> SHOW VARIABLES LIKE 'secure_file_priv';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| secure_file_priv | |
+------------------+-------+
1 row in set (0.01 sec)
此时就应该可以dump文件到/tmp
路径了
或者如果该变量已经有值:
mysql> show variables like 'secure_file_priv';
+------------------+-----------------------+
| Variable_name | Value |
+------------------+-----------------------+
| secure_file_priv | /var/lib/mysql-files/ |
+------------------+-----------------------+
则将文件放入上面的路径中即可
mysqlimport: command not found
mysqlimport实际上调用的是LOAD DATA
这个SQL语句,当mysqlimport不能使用时,可以用LOAD DATA
:
LOAD DATA
[LOW_PRIORITY | CONCURRENT] [LOCAL]
INFILE 'file_name'
[REPLACE | IGNORE]
INTO TABLE tbl_name
效果是一样的。例如
LOAD DATA [LOCAL] INFILE 'file_path_name' INTO TABLE table_name;
ERROR 3948 (42000): Loading local data is disabled; this must be enabled on both the client and server sides
检查变量并启用local-infile:
mysql> show variables like 'local_infile';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| local_infile | OFF |
+---------------+-------+
1 row in set (0.00 sec)
mysql> set global local_infile=1;
Query OK, 0 rows affected (0.04 sec)
mysql> show global variables like 'local_infile';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| local_infile | ON |
+---------------+-------+
1 row in set (0.11 sec)
注意此时需要退出mysql后并使用local-infile选项重新打开,才能生效:
mysql --local-infile=1 -u user -p