今天开始写几篇关于Java Performance Tuning 与 MySQL Performance Tuning的文章,作为最近几天自己学习的一个总结与备忘。
我现在有这么一个txt文本(貌似是一个词库),txt中的内容大约有20万行的数据,每一条数据的格式大致为:连续的几个E文字母开头,后接n(1<=n)个词组,例如:
aaab 或散或聚 葡萄牙队 七七节 芳菲节 斯蒂芬阶
我现在要做的就是把这些有空格隔开的词组全部都单独提取出来,存放进我的MySQL数据库中,另外,文本当中存在一些重复出现的词组。
当然,我首先要在MySQL数据库中建立一个表word,表中只有一个字段wordname (PK)
另外,我使用的是5.1.26-rc版本的MySQL,采用的是InnoDB store engine,编码格式采用UTF8,其他的参数暂时全部采用默认值。
完成这个事情的方法有很多。首先来看一下第一种方法。
方法一:
1、程序启动;
2、把txt文本当中的数据全部装载进入内存当中
3、利用BufferReader.readLine()方法每次从文本当中读取一行数据,放入一个临时String变量str_temp当中;
4、对str_temp跟据“空格符”进行分割,没分割出一个词组,就执行一行SQL语句:
- executeUpdate("insert into word(wordname) values(/"" + value +"/")");
5、回到步骤3,知道所有的数据全部装入MySQL中为止。
以上步骤实现起来十分的简单,我们只需要在程序当中加上一个try……catch快处理插入失败抛出的SQLException即可避免因为往数据库插入了重复值而导致的程序运行终止。
但是,方法一虽然实现起来十分简单,但是其效率确实十分的低下。我耐着性子等待程序跑完,结果这段小小的代码足足跑了将近14个小时才结束……这样的效率实在是让人无法接受!
如何提高这个程序的运行数度呢?我们来看看方法二。
方法二:
我们首先来分析一下方法一。他为什么会这么慢?
我们知道,往MySQL当中插入N行数据大概有这么两种方式:
一:
insert into tablename(fieldname) values(value1);
insert into tablename(fieldname) values(value2);
……
insert into tablename(fieldname) values(valueN);
即连续执行N次insert语句。在方法一中我才用的就是这种插入方式。
二:
insert into tablename(fielname) values(value1),(value2),……,(valueN);
即使用values列表批量往MySQL中插入数据。
一般情况下,采用第二种方式的数据插入效率要远远高于采用第一种方式。但是,他存在一个问题,就是如果在values列表当中,其中只要出现一个非法的value,就会导致这个插入操作失败,列表当中的其他合法的value都将不会被插入。
现在,我考虑使用第二种数据插入方式来从写我的程序。首先,我要解决掉上面提到的非法value问题。就我的这个txt文本数据的实际情况来说,出现的非法value应该都是属于“重复值”问题(我设置的字段属性为PK)。解决方法也不难,我当时采用的步骤是:
1、对这些无序的数据进行排序;
2、便利这些数据,去除掉重复出现的数据。
在这里由于我把数据全部放在一个String[] 当中,因此,我并没有执行严格意义上的删除操作,因为那样会引发数组的一位操作,那将会是一场灾难。我的方法是仅仅将重复值标记为null。另外,关于排序,我当时使用的“快排”。事实上,在我的这个程序中,“快排”的效率还是比较令人满意的。测试了一下,对我这一百多万个词组进行排序与去重两步操作,耗时不到10秒,相比于方法一的14个小时,这10时间几乎可以忽略。当然,更好的排序方式还是有的。
于是,这个方法的具体实现步骤变为:
1、程序启动;
2、把txt文本当中的数据全部装载进入内存当中
3、对数据根据“空格符”与“换行符”进行分割,存入一个String[]当中
4、对这个String[]进行排序与去重操作
5、利用这个有序的String[]拼装SQL语句,把数据批量插入到MySQL当中。
这里有个地方需要注意:执行第5步操作时,MySQL可能“报错”,原因是MySQL服务器默认把max_allowed_packet值设为1M,而这在我的这个应用当中显然是不够的(我估算一下,这个值至少需要将近40M的内存空间才能满足)。解决方法是,在MySQL的my.ini文件当中的[mysqld]区域加入一行:
max_allowed_packet=64M
然后重启一下MySQL,执行show variables like '%max_allowed_packet%'; 进行查询,如果max_allowed_packet值已经改变,则可以重新执行依据方法二实现的程序。
我测试了一下,方法二的程序运行时间仅仅为不到40秒,在多次测试下,最快时间能够达到30秒!(至于多次测试下的时间差距产生的原因,我将在后续的文章中讨论)
从14个小时到不需40秒,这样大幅度的性能提高仅仅是因为对数据插入的SQL语句做了一些改变!so crazy!
问题:这个时间还能提高吗?
回答:of course!
下一篇文章,将讨论如何继续提高这个程序的效率。那将会设计到一些在性能优化上用到的一些工具以及对JVM参数、MySQL参数的一些调整。