程序员有“洁癖”是好事!

    本次分享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。虽然移位操作速度快,但可能使代码比较难于理解,所以最好加上一些注释。


      HashMap的遍历,使用entrySet

for(Entry<String, String[]> entry : paraMap.entrySet()){
    String appFieldDefId = entry.getKey();
    String[] values = entry.getValue();
    .......
}

      现在谈谈MySQL的优化:
            1.业务开发中MySQL的常用字段类型

      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;

     5.开启MySQL的查询缓存。
     MySQL存储引擎包括处理事务安全表InnoDB引擎、MyISAM管理非事务表,MyISAM提供高速存储和检索,以及全文搜索能力。
对于日志log方面的update较少的表,我们使用MyISAM。
    我们可以通过在MySQL安装目录中的my.ini文件设置查询缓冲。设置也非常简单,只需要将query_cache_type设为1即可。在设 置了这个属性后,MySQL在执行任何SELECT语句之前,都会在它的缓冲区中查询是否在相同的SELECT语句被执行过,如果有,并且执行结果没有过 期,那么就直接取查询结果返回给客户端。但在写SQL语句时注意,MySQL的查询缓冲是区分大小写的。如下列的两条SELECT语句:

SELECT * from TABLE1                 SELECT * FROM TABLE1

    上面的两条SQL语句对于查询缓冲是完全不同的SELECT。而且查询缓冲并不自动处理空格,因此,在写SQL语句时,应尽量减少空格的使用,尤其是在SQL首和尾的空格(因为,查询缓冲并不自动截取首尾空格)。
    虽然不设置查询缓冲,有时可能带来性能上的损失,但有一些SQL语句需要实时地查询数据,或者并不经常使用(可能一天就执行一两次)。这样就需要把 缓冲关了。当然,这可以通过设置query_cache_type的值来关闭查询缓冲,但这就将查询缓冲永久地关闭了。在MySQL 5.0中提供了一种可以临时关闭查询缓冲的方法:
SELECT SQL_NO_CACHE field1, field2 FROM TABLE1
    以上的SQL语句由于使用了SQL_NO_CACHE,因此,不管这条SQL语句是否被执行过,服务器都不会在缓冲区中查找,每次都会执行它。
我们还可以将my.ini中的query_cache_type设成2,这样只有在使用了SQL_CACHE后,才使用查询缓冲。
SELECT SQL_CALHE * FROM TABLE1
    6.MySQL联合主键和联合索引的使用。
    建表的一些常识这里不谈,这里说下要注意的地方,当你需要建一个新的表时,和优化一个表时,我们都需要站在业务的角度去分析,具体到业务上。
    需要查询时,首先想到的一般是,能否直接使用表的id主键,如果不行,考虑联合主键的方式,单主键肯定比联合主键好,主键越多效率越低,需要慎重。
    需要排序时,首先想到的一般是,能否利用索引来优化(表的主键也是索引)。
    MySQL的弱点之一是它的排序。虽然MySQL可以在1秒中查询大约15,000条记录,但由于MySQL在查询时最多只能使用一个索引。因此,如果WHERE条件已经占用了索引,那么在排序中就不使用索引了,这将大大降低查询的速度。我们可以看看如下的SQL语句:

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;

    在以上的SQL的WHERE子句中已经使用了NAME字段上的索引,因此,在对SALE_DATE进行排序时将不再使用索引。为了解决这个问题,我们可以对SALES表建立复合   索引:

ALTER TABLE SALES DROP INDEX NAME, ADD INDEX (NAME, SALE_DATE)

    这样再使用上述的SELECT语句进行查询时速度就会大副提升 
    InnoDB默认采用的是B+tree索引,B+tree索引本身就是有序的,以下面的查询为例:
    查出演员AA的所有演出时间:
select * from actor_info where actor_name='AA'order by act_time;
    只需创建多列索引(actor_name, act_time),就能利用B+tree的特性来避免额外排序。
通过B+tree查找到actor_name='AA'的数据后,只需要按序往右继续扫描即可,无需额外排序操作。

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后,要自己去检测一下


  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值