本次分享java和mysql的一些小优化知识,希望能受益,很多中小型公司没有DBA这个岗位,需要程序员身兼多职才能解决项目开发和运营阶段的一些问题。
我个人认为,一个程序员在开发中,要时刻想着代码的执行效率,努力成为一个有“洁癖”的程序员,可供程序利用的资源(内存、CPU时间、网络带宽等)是有限的,优化的目的就是让程序用尽可能少的资源完成预定的任务。优化通常包含两方面的内容:减小代码的体积,提高代码的运行效率。虽然java虚拟机有自动回收机制,但是在GC的时候也会对服务器性能有影响,所以程序员要做到代码上优化也要做到DB上优化,不要想着代码review的时候检查出来,也不要想着出问题的时候再优化DB,这些每一人都可以在自己去做的时候注意,每个人在写的过程中,自己的代码逻辑要清晰,自己写数据库的查询语句,要自己去检验是否使用了索引的优化等,分享出来也是让大家共同学习与进步,减少一些没必要的工作。
如果你对代码有“洁癖”,那么你会看不惯代码的不规范、低效率的逻辑,恨不得马上要修改,为什么很多公司都让我的文档小组长或者更大的leader去review程序员的代码(我就遇到过,为什么这么写,还有更好的方式吗,列出你能想到的方法,最后选择一种较好的),因为怕线上出问题,到时候可不好排查。说几点代码上应该注意的:
①减少执行过程中创建的对象,浪费内存和封装时间,这是其中一点,除非是业务很难你不得不这么做,你可以和大家讨论,利用工厂模式去创建dao和service层的对象。
②避免嵌套循环,循环次数是两个循环的乘积,效率慢。
③一个是使用“+”拼字符串,一个是使用“+”拼sql和hql。编写代码的时候,用“+”拼字符串确实很方便。要拼的字符串内容包含在变量里,在用“+”拼字符串的代码行加个断点调试一下就会知道,“+”实际上就是创建了一个StringBuilder对象然后调用append方法最后再调用toString方法,这就是一个“+”做的全部工作。如果n个“+”参数计算,整个拼接过程创建了n个字符串对象,和n个StringBuilder对象,调用了n次toString方法,这就是用“+”拼字符串效率低的原因所在,即耗堆内存又消耗时间。
StringBuffer 和StringBuilder的区别:
java.lang.StringBuffer线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。StringBuilder。与该类相比,通常应该优先使用 java.lang.StringBuilder类,因为它支持所有相同的操作,但由于它不执行同步,所以速度更快。为了获得更好的性能,在构造 StirngBuffer 或 StirngBuilder 时应尽可能指定它的容量。当然,如果你操作的字符串长度不超过 16 个字符就不用了。 相同情况下使用 StirngBuilder 相比使用 StringBuffer 仅能获得 10%-15% 左右的性能提升,但却要冒多线程不安全的风险。而在现实的模块化编程中,负责某一模块的程序员不一定能清晰地判断该模块是否会放入多线程的环境中运行,因此:在多线程模式下,StringBuffer;在单线程模式下,使用StirngBuilder 。
④SQL语言不要拼接,而且直接用?来传递参数,这样mybatis或者hibernate才能把相同的sql加到缓存中。
⑤尽量减少对变量的重复计算
例如:
for(int i = 0;i < list.size; i ++) {
…
}
⑥慎用异常
异常对性能不利。抛出异常首先要创建一个新的对象。Throwable接口的构造函数调用名为fillInStackTrace()的本地(Native)方法,fillInStackTrace()方法检查堆栈,收集调用跟踪信息。只要有异常被抛出,VM就必须调整调用堆栈,因为在处理过程中创建了一个新的对象。异常只能用于错误处理,不应该用来控制程序流程。
⑦不要在循环中使用:
Try {
} catch() {
}
应把其放置在最外层。
⑧不用new关键词创建类的实例
用new关键词创建类的实例时,构造函数链中的所有构造函数都会被自动调用。但如果一个对象实现了Cloneable接口,我们可以调用它的clone()方法。clone()方法不会调用任何类构造函数。
在使用设计模式(Design Pattern)的场合,如果用Factory模式创建对象,则改用clone()方法创建新的对象实例非常简单。例如,下面是Factory模式的一个典型实现:
public static Credit getNewCredit() {
return new Credit();
}
改进后的代码使用clone()方法,如下所示:
private static Credit BaseCredit = new Credit();
public static Credit getNewCredit() {
return (Credit) BaseCredit.clone();
}
上面的思路对于数组处理同样很有用。
for (val = 0; val < 100000; val +=5) {
alterX = val * 8; myResult = val * 2;
}
for (val = 0; val < 100000; val += 5) {
alterX = val << 3; myResult = val << 1;
}
修改后的代码不再做乘以8的操作,而是改用等价的左移3位操作,每左移1位相当于乘以2。相应地,右移1位操作相当于除以2。虽然移位操作速度快,但可能使代码比较难于理解,所以最好加上一些注释。
for(Entry entry : paraMap.entrySet()){
String appFieldDefId = entry.getKey();
String[] values = entry.getValue();
.......
}
Java Type MySQL Type
String char(255)
String varchar(1024)
String text
Blob blob
Boolean boolean
Byte tinyint(4)
Short smallint(6)
Integer int(11)
Long bigint(20)
Float float
Double double
Date datetime
Timestamp timestamp
建表时,对于int类型的,如果不需要存取负值,最好加上unsigned。
2.char和varchar的区别和选择
varchar往往用来保存可变长度的字符串。简单的说,我们只是给其固定了一个最大值,然后系统会根据实际存储的数据量来分配合适的存储空间。为此相比char字符数据而言,其能够比固定长度类型占用更少的存储空间。
通常情况下,varchar数据类型能够节约磁盘空间,为此往往认为其能够提升数据库的性能。不过这里需要注意的是,这往往是一把双刃剑。其在提升性能的同时,往往也会产生一些副作用。如因为其长度是可变的,为此在数据进行更新时可能会导致一些额外的工作。如在更改前,其字符长度是10位(varchar规定的最长字符数假设是50位),此时系统就只给其分配10个存储的位置(假设不考虑系统自身的开销)。更改后,其数据量达到了20位。由于没有超过最大 50位的限制,为此数据库还是允许其存储的。只是其原先的存储位置已经无法满足其存储的需求。此时系统就需要进行额外的操作。如根据存储引擎不同,有的会 采用拆分机制,而有的则会采用分页机制。
char数据类型与varchar数据类型不同,其采用的是固定长度的存储方式。简单的说,就是系统总为其分配最大的存储空间。当数据保存时,即使其没有达到最大的长度,系统也会为其分配这么多的存储空间。显然,这种存储方式会造成磁盘空间的浪费。这里需要提醒的一点是,当字符位数不足时,系统并不会采用空格来填充。相反,如果在保存char值的时候,如果其后面有空值,系统还会自动过滤其空格。而在进行数据比较时,系统又会将空格填充到字符串的末尾。
显然,varchar与char两种字符型数据类型相比,最大的差异就是前者是可变长度,而后者则是固定长度。在存储时,前者会根据实际存储的数据来分配最终的存储空间。而后者则不管实际存储数据的长度,都是根据char规定的长度来分配存储空间。这是否意味着char的数据类型劣于varchar呢?其实不然。否则的话,就没有必要存在char字符类型了。虽然varchar数据类型可以节省存储空间,提高数据处理的效率。但是其可变长度带来的一些负面效应,有时候会抵消其带来的优势。为此在某些情况下,还是需要使用char数据类型。
使用建议
1、根据字符的长度来判断,考虑其长度是否相近来确定选择char还是varchar,如何字段的长度基本都是一样或者其长度总是近似的可以选用char。合理给char分配也能做到varchar的按需分配,也能节省磁盘空间。
2、是从碎片角度进行考虑用varchar可变长度的字符型数据时,数据库管理员要时不时的对碎片进行整理。如执行数据库导出导入作业,来消除碎片。
3、即使使用varchar数据类型,也不能够太过于慷慨!比如你只使用到90个字符,varchar(100)与varchar(200)真的相同吗?结果是否定的。虽然他们用来存储90个字符的数据,其存储空间相同,但是对于内存的消耗是不同的。
varchar虽然磁盘空间是根据需要来分配,但是内存却不是。我认为如果一个字符串的长度是固定的,就选择char,如果是不确定的,比如一些战斗详情,每一场的数据量都不确定,那么就选择varchar;如玩家的名字,不经常修改,可以使用char;如玩家的聊天内容,长度不固定,但是需要合理指定varchar的大小。
3.datetime和timestamp的区别和选择
datetime类型适合用来记录数据的原始的创建时间,因为无论你怎么更改记录中其他字段的值,datetime字段的值都不会改变,除非你手动更改它。
timestamp类型适合用来记录数据的最后修改时间,因为只要你更改了记录中其他字段的值,默认值为CURRENT_TIMESTAMP(),timestamp字段的值会被自动更新,注意如果一个表中存在多个timestamp字段,MySQL默认只更新第一个,也可以在业务代码上修改。
时间范围上datetime比timestamp大
建议
如果对时间操作比较多的业务,最好是存long型,保存毫秒数就行,不然java代码里就会出现很多对象转换的代码,影响效率。
4.给一个表添加字段的方法。
一般的情况大家都是直接:
ALTER TABLE `表名` ADD `字段名` int(11) NOT NULL DEFAULT 200;
但是如果这个表已经存在了这个字段,就会报错,下面提供一种安全的方式来解决,避免给线上表增加字段时出错而浪费维护时间,手动维护也慢而且容易继续出错。
建立存储过程,执行后删除存储过程
use jack_udb0;
DROP PROCEDURE IF EXISTS schema_change;
DELIMITER //
CREATE PROCEDURE schema_change() BEGIN
DECLARE jack_udb0 VARCHAR(100);
SELECT DATABASE() INTO jack_udb0;
//判断表字段是否存在
IF NOT EXISTS (SELECT * FROM information_schema.columns WHERE table_schema=jack_udb0 AND table_name = 'user_data' AND column_name = 'daily_max_attack') THEN
ALTER TABLE `user_data`
ADD COLUMN `daily_max_attack` int(11) NOT NULL DEFAULT 0;
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.columns WHERE table_schema=jack_udb0 AND table_name = 'user_data' AND column_name = 'limit_refresh') THEN
ALTER TABLE `user_data`
ADD COLUMN `limit_refresh` int(11) NOT NULL DEFAULT 0;
END IF;
END//
DELIMITER ;
CALL schema_change();
DROP PROCEDURE schema_change;
SELECT * from TABLE1 SELECT * FROM TABLE1
CREATE TABLE SALES(
ID INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
NAME VARCHAR(100) NOT NULL,
PRICE FLOAT NOT NULL, SALE_COUNT INT NOT NULL,
SALE_DATE DATE NOT NULL,
PRIMARY KEY(ID),
INDEX (NAME),
INDEX (SALE_DATE) );
SELECT * FROM SALES WHERE NAME = “name” ORDER BY SALE_DATE DESC;
ALTER TABLE SALES DROP INDEX NAME, ADD INDEX (NAME, SALE_DATE)
CREATE TABLE `actor_info` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '演员ID',
`actor_name` char(50) NOT NULL COMMENT '演员姓名',
`actor_level` int(11) NOT NULL DEFAULT '0' COMMENT '演员等级',
`act_time` datetime NOT NULL COMMENT '出演时间',
PRIMARY KEY (`id`),
KEY `act_key` (`actor_name`,`act_time`),
KEY `act_key2` (`actor_name`,`actor_level`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='演员信息表';
只有where条件正常的情况下,才会走索引;或者这种情况select的是索引列,explain select id,shop_id,goods_id from shop order by shop_id,goods_id;
比如user_fairpvp_crew_data,user_id= 70有810条数据是全表数据,term_id =1有800条,term_id =2有10条;
EXPLAIN select * from user_fairpvp_crew_data where user_id = 70 and term_id =2;//用了索引
EXPLAIN select * from user_fairpvp_crew_data where user_id = 70 and term_id =1;//没用索引,数据占比大,产生数据倾斜,MySQL优化器会裁定选择走全表扫描
查看SQL语句索引是否生效,希望写SQL后,要自己去检测一下