【技术随笔】Mysql数据库亿级数据表的快速迁移,详细教程及避坑指南

迁移mysql大量数据的详细教程及避坑指南

  • 问题说明
  • 操作思路
  • 操作方法
    • 生成数据库文件
    • 批量创建空数据表
    • 批量导入数据
    • 单个导入数据
    • 暂时关闭auto_commit和数据检查:
  • 效果对比
  • 恢复mysql设置
  • 避坑指南
    • 无法dump数据到文件:
    • 报错mysqlimport命令不存在
    • 无法读取本地文件,提示Local-infile被禁用

问题说明

做量化交易需要大量的金融数据,通常保存在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;

暂时关闭auto_commit和数据检查:

  1. 暂时禁用autocommit:
mysql> SET autocommit=0;

每次导入一张表之后都要手动commit:

mysql> COMMIT;
  1. 暂时禁用外键检查:(导入新数据库时)
mysql> SET foreign_key_checks=0;
  1. 暂时禁用除主键外的唯一性检查(导入新数据库时):
mysql> SET unique_checks=0;
  1. 暂时禁用Innodb_redo_log,(当使用的engine为Innodb时)
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设置

数据导入结束后别忘记恢复之前修改的设置:

mysql> SET autocommit=0;
mysql> SET foreign_key_checks=0;
mysql> SET unique_checks=0;
mysql> ALTER INSTANCE ENABLE INNODB REDO_LOG;

避坑指南

以下坑我已经踩过了,遇到同样坑的同学,可以照下面的指南避坑:

无法dump数据到文件:

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命令不存在

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;

无法读取本地文件,提示Local-infile被禁用

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

你可能感兴趣的:(随笔笔记,mysql,数据库,mysql,android)