在我的上一篇文章《Java与MySQL优化之旅(一)——从14小时到40秒》之中,我使一个用于往MySQL中插入数据的应用的运行时间从14小时下降到了不到40秒的时间。今日,我来看看还怎样把这个应用的运行时间缩短。
我首先分析了一下当前程序。当前的程序功能主要分为两部分:1、待插入词组的去重操作;2、批量往MySQL中插入词组(每次插入10000行)。对于第一部分,时间消耗约为10秒,对于第二部分时间消耗约为30秒,占去了总时间的四分之三。于是,很容易想到,我要提升程序的效率,应该先从第二部分入手尝试。
其实,第二部分消耗的时间,基本上都是被MySQL数据库消耗掉的。那么,如何提高MySQL的速度呢?
当前,程序操作的MySQL数据表采用的是InnoDB存储引擎。我们知道,在MySQL除了提供InnoDB存储引擎之外,还提供了一个MyISAM存储引擎,而且MyISAM在执行一些与事务关系不大的操作时其效率往往要远远高于InnoDB。对于当前程序使用的这个数据表来说,在往里面插入数据之前,他基本上就只是一张空表。而且在往其中插入数据到达过程当中,比不会有其他的线程对数据表执行查询等操作。因此,在执行程序之前首先把数据表使用的存储引擎转换成MyISAM将会是一个对性能十分有好处的行为。在命令行中执行以下命令:
- ALTER TABLE tablename ENGINE = MyISAM;
在完成了存储引擎的转换之后,重新执行程序,发现程序的运行时间下降到了15秒左右。再查看了一下输出的时间信息,发现程序消耗在插入数据上面的时间已经下降到了6秒左右。
至此,影响程序性能的部位从“数据库操作”转移到了“原始数据去重”上面了。那么,“数据去重”这部分应该如何提高速度呢?
注意到,在程序执行“数据去重”操作时,做了这么几步工作:
1、对无序数据进行排序(这里使用的是“快排”);
2、一次遍历有序数组,执行去重操作。
而在此之前,程序首先要把数据一次性装载进内存当中。
事实上,程序执行以上步骤的真正目的其实仅仅是要从已经装载进入内存当中的数据中去掉重复出现的数据而已。那么能否能够去掉程序当中“对无序数据排序”这么一步操作呢?
答案是肯定的。
JavaAPI当中为开发者提供了一个Hashtable<K,V>类,可以考虑把数据作为Key放入到一个Hashtable当中,利用Key的唯一特性去掉重复数据:
- String strtemp = null;
- try {
- strtemp = m_BufferedReader.readLine();
- } catch (IOException e) {
- e.printStackTrace();
- }
- int len = 0, i = 0, j = 0;
- Integer count = 0;
- String key = null;
- while (strtemp != null) {
- len = strtemp.length();
- i = 0;
- j = 0;
- while (i < len) {
- j = strtemp.indexOf(" ", i);
- if (j > 0) {
- key = strtemp.substring(i, j);
- if (m_Hashtable1.get(key) == null) {
- m_Hashtable1.put(key, count);
- }
- i = j + 1;
- } else {
- key = strtemp.substring(i);
- if (m_Hashtable1.get(key) == null) {
- m_Hashtable1.put(key, count);
- }
- break;
- }
- }
- try {
- strtemp = m_BufferedReader.readLine();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
运行一下程序,程序报错:OutOfMemoryError
使用JDK6提供的jconsole.exe工具看一下,发现是明显的堆内存不够用。程序运行时,JVM仅仅为程序申请了12M的对内存空间,而据我个人估算,程序正常运行大概需要超过100M的对内存空间,这明显不够用。
既然如此,索性使用-Xms以及-Xmx参数指定JVM的初始堆内存大小与最大堆内存大小:
java -Xms256m -Xmx256m
重新执行程序,程序运行成功,时间消耗为7秒左右。其中,“数据准备”过程,包括数据的装载以及“去重”仅仅消耗不到2秒的时间。
呵呵,在回国头来看看“数据库操作”部分,因为他又重新成为了性能提高的重点~~
当前程序往数据库中插入数据采用的是insert into tablename(fieldname) values(value1),(value2),……,(valueN)的方式。而事实上,往MySQL当中批量插入大量数据的更快的方式是利用有效的数据源文件执行load data infile命令。这样的插入性能提升将极为恐怖。
于是,修改程序,把hashtable当中的Key以空格符隔开放入一个临时txt文件当中(当然,这需要损耗一些时间):
- sb = new StringBuilder(1400000);
- if (sb == null)
- System.out.println("sb is null");
- Set<String> m_Set = null;
- m_Set = m_Hashtable1.keySet();
- m_Hashtable1 = null;
- if (m_Set == null)
- System.out.println("set is null");
- for (String str : m_Set) {
- sb.append(str + " ");
- }
- if (sb.length() != 0) {
- int index = sb.length() - 1;
- sb.delete(index, index + 1);
- }
- BufferedWriter bw = null;
- try {
- bw = new BufferedWriter(new OutputStreamWriter(
- new FileOutputStream(new File ("E://Code//Java//temp2//wtest.txt")), "UTF-8"));
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (UnsupportedEncodingException e) {
- e.printStackTrace();
- }
- try {
- bw.write(sb.toString());
- } catch (IOException e) {
- e.printStackTrace();
- }
然后执行load data infile命令插入数据:
- try {
- stmt.executeUpdate("load data infile "E://Code//Java//temp2//wtest.txt/" into table word character set UTF8 lines terminated by ' '");
- } catch (SQLException e) {
- e.printStackTrace();
- }
再次执行程序,运行时间不到5秒,其中数据库的操作耗时1秒左右,其余操作(包括把数据从原始文件装入到数据整理到写入临时文件)耗时3秒左右。
呵呵,大功告成。
整个程序的运行时间从原来的14小时到40秒再到现在的不足5秒!哈哈!