batch insert 1 million datas into mysql

最近尝试插入1百万条数据进db,以mysql为例。

1. 顺序insert

先写了个无脑的for循环作为base-line,插1万条耗时1m53s,根本不敢插1百万。

foreach(var student in students){
    var sql = string.Format("insert into student ... ");
    cmd.CommandText = sql;
    cmd.ExecuteNonQuery();
}

2. batch insert

上面这种方式有2个问题:

  1. 在DB端,每次执行都会以1个单独的事务执行;
  2. 在网络上,传输的次数过多、每次传输的效率较差。

相应的解决方法是:

  1. 在执行前后套BeginTransaction/Commit,保证所有的insert都是在一个大事务里; // 光是这样,1万条只要不到2s,1百万条只要75s

  2. 每1万条数据,拼接成1个大sql,只要不超过 max_allowed_packet=1M 的默认限制即可。具体多少行拼成1条,视字段多少而定,拼成的sql如下。 // 这样的效果也很显著,1百万条只要13s

     insert into table (fields...) values (1...), (2...), ... , (10000...);
    
  3. 可以修改mysql的默认设置,在my.ini里添加如下配置。但试下来效果并不明显,改成10M、每次拼接10万条数据,总时间仍为13s,可以想见这时瓶颈已经不是传输时间了,而是对表的操作。

     [mysqld]
     max_allowed_packet=10M // 1M default
    

3. MultiThread insert

试了下在方法2(batch insert)的基础上,采用4个线程同时insert,1百万条数据耗时16s,反而慢了。估计时间都耗在创建connection、单表加锁上了。在这个场景下,MultiThread对解决问题无益。

4. ibdata1无限增长的问题

每个Student对象大约是50字节,每insert1百万大约是50M。多insert几次后执行删除操作,发现ibdata1文件反而接近翻倍的增长。解决方法如下:

  • 关闭mysqld服务
  • 删除ibdata1、ib_logfile0/1、对应的database文件夹
  • my.ini的[mysqld]里添加 innodb_file_per_table=1 ,这样就会给每个表创建一个单独的ibd文件

最后是Demo的源码,如果你有更快的方法,不妨留言~