知道transaction对数据库插入速度有这样大的影响后,接下来就是要降低事务数,最好将一百万条插入操作都放在一个transaction中完成。于是我在程序里加了两行代码,第一行是调用mysql_autocommit()将auto commit关闭,这样MySQL就不会将每一次的插入操作都当成一个transaction了。第二行是在程序最后加上mysql_commit()手动提交transaction,就这两行代码导致插入时间大幅减少。我用time命令记录了程序的运行时间,结果如下:
real 1m8.693s
user 0m5.615s
sys 0m10.081s
接下来的尝试就是使用MySQL的multiple insert也就是一条insert语句插入多条数据,语法像这样:INSERT INTO x (a, b) VALUES (1, 2), (2, 3), (3, 5); 。这个方法也是MySQL官网建议的提高插入速度的方法之一。我想做的事就是用一条insert语句插入一百万条数据。为了达到这个目的我就要先构造这样的一个insert语句,方法很简单就是先malloc一个很大的字符数组,然后不停的使用strcat()来向这个字符数组中填值,也就是说要调用一百万次strcat()来构造这样的一个insert语句。当我跑这个重新改后的程序时发现构造这样的一个insert语句非常慢,无法在有限的时间内完成。后来发现问题出在strcat()上,实现这个方法的算法很低效,这个算法是设计来让人们调用一次的而不是一百万次的,根据时间复杂度调用一百万次strcat()就会非常耗时,具体关于strcat()问题可以参见Joel的文章。问题的解决办法就是自己写一个高效的strcat()方法,这个在Joel的文章里也给出来了。把这个问题解决后构造一个insert语句就是瞬间的事了,在这里算法课上反复强调的时间复杂度得到了充分的体现。把这个问题解决后又出现了一个新的问题那就是我一跑程序就告诉我"MySQL server has gone away",上网找了一下发现原因是发送的insert语句太大了超出了max_allowed_packet的大小,解决方案就是把MySQL配置文件里的max_allowed_packet大小改到足够大就可以了,顿时我就觉得为什么MySQL不能直接告诉我包过大呢,非要给出一个等于没说的信息,太不友好了。解决了所有问题后这种方案终于能跑了,插入时间也进一步减小了,对于这种方案能降低插入时间的原因我猜是不是因为减少了网络中包传输的次数。我用time命令记录了程序的运行时间,结果如下:
real 0m10.971s
user 0m0.514s
sys 0m0.008s
接下来做的就是换个数据库引擎把transaction的开销给消除了,我用了MyISAM引擎,这是个非事务型引擎。改完引擎后再一次发现transaction的开销好大啊。我用time命令记录了程序的运行时间,结果如下:
real 0m3.863s
user 0m0.514s
sys 0m0.010s
然后还尝试了一下MySQL的必杀技load data infile,这种方法可以使插入的效率提高数倍,很适合大批量数据的插入。程序运行结果如下:
real 0m2.481s
user 0m0.400s
sys 0m0.015s
最后用openmp写了一个多线程的程序来同时插入,由于测试机子是四核的所以开了四个线程。然后分别在两种数据库引擎下都做了实验,结果如下:
MyISAM:
real 0m2.897s
user 0m0.686s
sys 0m0.019s