数据库访问性能优化(二)

1.2、只经过索引访问数据

有些时候,咱们只是访问表中的几个字段,而且字段内容较少,咱们能够为这几个字段单独创建一个组合索引,这样就能够直接只经过访问索引就能获得数据,通常索引占用的磁盘空间比表小不少,因此这种方式能够大大减小磁盘IO开销。程序员

如:select id,name from company where type='2';算法

若是这个SQL常用,咱们能够在type,id,name上建立组合索引sql

create index my_comb_index on company(type,id,name);数据库

有了这个组合索引后,SQL就能够直接经过my_comb_index索引返回数据,不须要访问company表。浏览器

仍是拿字典举例:有一个需求,须要查询一本汉语字典中全部汉字的个数,若是咱们的字典没有目录索引,那咱们只能从字典内容里一个一个字计数,最后返回结果。若是咱们有一个拼音目录,那就能够只访问拼音目录的汉字进行计数。若是一本字典有1000页,拼音目录有20页,那咱们的数据访问成本至关于全表访问的50分之一。缓存

切记,性能优化是无止境的,当性能能够知足需求时便可,不要过分优化。在实际数据库中咱们不可能把每一个SQL请求的字段都建在索引里,因此这种只经过索引访问数据的方法通常只用于核心应用,也就是那种对核心表访问量最高且查询字段数据量不多的查询。性能优化

1.3、优化SQL执行计划

SQL执行计划是关系型数据库最核心的技术之一,它表示SQL执行时的数据访问算法。因为业务需求愈来愈复杂,表数据量也愈来愈大,程序员愈来愈懒惰,SQL也须要支持很是复杂的业务逻辑,但SQL的性能还须要提升,所以,优秀的关系型数据库除了须要支持复杂的SQL语法及更多函数外,还须要有一套优秀的算法库来提升SQL性能。服务器

目前ORACLESQL执行计划的算法约300种,并且一直在增长,因此SQL执行计划是一个很是复杂的课题,一个普通DBA能掌握50种就很不错了,就算是资深DBA也不可能把每一个执行计划的算法描述清楚。虽然有这么多种算法,但并不表示咱们没法优化执行计划,由于咱们经常使用的SQL执行计划算法也就十几个,若是一个程序员能把这十几个算法搞清楚,那就掌握了80%SQL执行计划调优知识。网络

因为篇幅的缘由,SQL执行计划须要专题介绍,在这里就很少说了。并发

 

2、返回更少的数据

2.1、数据分页处理

通常数据分页方式有:

2.1.1、客户端(应用程序或浏览器)分页

将数据从应用服务器所有下载到本地应用程序或浏览器,在应用程序或浏览器内部经过本地代码进行分页处理

优势:编码简单,减小客户端与应用服务器网络交互次数

缺点:首次交互时间长,占用客户端内存

适应场景:客户端与应用服务器网络延时较大,但要求后续操做流畅,如手机GPRS,超远程访问(跨国)等等。

2.1.2、应用服务器分页

将数据从数据库服务器所有下载到应用服务器,在应用服务器内部再进行数据筛选。如下是一个应用服务器端Java程序分页的示例:

List list=executeQuery(“select * from employee order by id”);

Int count= list.size();

List subList= list.subList(10, 20);

 

优势:编码简单,只须要一次SQL交互,总数据与分页数据差很少时性能较好。

缺点:总数据量较多时性能较差。

适应场景:数据库系统不支持分页处理,数据量较小而且可控。

 

2.1.3、数据库SQL分页

采用数据库SQL分页须要两次SQL完成

一个SQL计算总数量

一个SQL返回分页后的数据

优势:性能好

缺点:编码复杂,各类数据库语法不一样,须要两次SQL交互。

 

oracle数据库通常采用rownum来进行分页,经常使用分页语法有以下两种:

 

直接经过rownum分页:

select * from (

         select a.*,rownum rn from

                   (select * from product a where company_id=? order by status) a

         where rownum<=20)

where rn>10;

数据访问开销=索引IO+索引所有记录结果对应的表数据IO

 

采用rowid分页语法

优化原理是经过纯索引找出分页记录的ROWID,再经过ROWID回表返回数据,要求内层查询和排序字段全在索引里。

create index myindex on product(company_id,status);

 

select b.* from (

         select * from (

                   select a.*,rownum rn from

                            (select rowid rid,status from product a where company_id=? order by status) a

                   where rownum<=20)

         where rn>10) a, product b

where a.rid=b.rowid;

数据访问开销=索引IO+索引分页结果对应的表数据IO

 

实例:

一个公司产品有1000条记录,要分页取其中20个产品,假设访问公司索引须要50IO2条记录须要1个表数据IO

那么按第一种ROWNUM分页写法,须要550(50+1000/2)IO,按第二种ROWID分页写法,只须要60IO(50+20/2);

 

2.2、只返回须要的字段

经过去除没必要要的返回字段能够提升性能,例:

调整前:select * from product where company_id=?;

调整后:select id,name from product where company_id=?;

 

优势:

1、减小数据在网络上传输开销

2、减小服务器数据处理开销

3、减小客户端内存占用

4、字段变动时提早发现问题,减小程序BUG

5、若是访问的全部字段恰好在一个索引里面,则可使用纯索引访问提升性能。

缺点:增长编码工做量

因为会增长一些编码工做量,因此通常需求经过开发规范来要求程序员这么作,不然等项目上线后再整改工做量更大。

若是你的查询表中有大字段或内容较多的字段,如备注信息、文件内容等等,那在查询表时必定要注意这方面的问题,不然可能会带来严重的性能问题。若是表常常 要查询而且请求大内容字段的几率很低,咱们能够采用分表处理,将一个大表分拆成两个一对一的关系表,将不经常使用的大内容字段放在一张单独的表中。如一张存储 上传文件的表:

T_FILEID,FILE_NAME,FILE_SIZE,FILE_TYPE,FILE_CONTENT

咱们能够分拆成两张一对一的关系表:

T_FILEID,FILE_NAME,FILE_SIZE,FILE_TYPE

T_FILECONTENTID, FILE_CONTENT

         经过这种分拆,能够大大提少T_FILE表的单条记录及总大小,这样在查询T_FILE时性能会更好,当须要查询FILE_CONTENT字段内容时再访问T_FILECONTENT表。

 

3、减小交互次数

3.1batch DML

数据库访问框架通常都提供了批量提交的接口,jdbc支持batch的提交处理方法,当你一次性要往一个表中插入1000万条数据时,若是采用普通的executeUpdate处理,那么和服务器交互次数为1000万次,按每秒钟能够向数据库服务器提交10000次估算,要完成全部工做须要1000秒。若是采用批量提交模式,1000条提交一次,那么和服务器交互次数为1万次,交互次数大大减小。采用batch操做通常不会减小不少数据库服务器的物理IO,可是会大大减小客户端与服务端的交互次数,从而减小了屡次发起的网络延时开销,同时也会下降数据库的CPU开销。

 

假设要向一个普通表插入1000万数据,每条记录大小为1K字节,表上没有任何索引,客户端与数据库服务器网络是100Mbps,如下是根据如今通常计算机能力估算的各类batch大小性能对比值:

 

 

 单位:ms

No batch

Batch=10

Batch=100

Batch=1000

Batch=10000

服务器事务处理时间

0.1

0.1

0.1

0.1

0.1

服务器IO处理时间

0.02

0.2

2

20

200

网络交互发起时间

0.1

0.1

0.1

0.1

0.1

网络数据传输时间

0.01

0.1

1

10

100

小计

0.23

0.5

3.2

30.2

300.2

平均每条记录处理时间

0.23

0.05

0.032

0.0302

0.03002

 

 

从上能够看出,Insert操做加大Batch能够对性能提升近8倍性能,通常根据主键的UpdateDelete操做也可能提升2-3倍性能,但不如Insert明显,由于UpdateDelete操做可能有比较大的开销在物理IO访问。以上仅是理论计算值,实际状况须要根据具体环境测量。

 

3.2In List

不少时候咱们须要按一些ID查询数据库记录,咱们能够采用一个ID一个请求发给数据库,以下所示:

for :var in ids[] do begin

  select * from mytable where id=:var;

end;

 

咱们也能够作一个小的优化, 以下所示,用ID INLIST的这种方式写SQL

select * from mytable where id in(:id1,id2,...,idn);

 

经过这样处理能够大大减小SQL请求的数量,从而提升性能。那若是有10000ID,那是否是所有放在一条SQL里处理呢?答案确定是否认的。首先大部份数据库都会有SQL长度和IN里个数的限制,如ORACLEIN里就不容许超过1000个值

另外当前数据库通常都是采用基于成本的优化规则,当IN数量达到必定值时有可能改变SQL执行计划,从索引访问变成全表访问,这将使性能急剧变化。随着SQLIN的里面的值个数增长,SQL的执行计划会更复杂,占用的内存将会变大,这将会增长服务器CPU及内存成本。

评估在IN里面一次放多少个值还须要考虑应用服务器本地内存的开销,有并发访问时要计算本地数据使用周期内的并发上限,不然可能会致使内存溢出。

综合考虑,通常IN里面的值个数超过20个之后性能基本没什么太大变化,也特别说明不要超过100,超事后可能会引发执行计划的不稳定性及增长数据库CPU及内存成本,这个须要专业DBA评估。

 

3.3、设置Fetch Size

当咱们采用select从数据库查询数据时,数据默认并非一条一条返回给客户端的,也不是一次所有返回客户端的,而是根据客户端fetch_size参数处理,每次只返回fetch_size条记录,当客户端游标遍历到尾部时再从服务端取数据,直到最后所有传送完成。因此若是咱们要从服务端一次取大量数据时,能够加大fetch_size,这样能够减小结果数据传输的交互次数及服务器数据准备时间,提升性能。

 

如下是jdbc测试的代码,采用本地数据库,表缓存在数据库CACHE中,所以没有网络链接及磁盘IO开销,客户端只遍历游标,不作任何处理,这样更能体现fetch参数的影响:

String vsql ="select * from t_employee";

PreparedStatement pstmt = conn.prepareStatement(vsql,ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY);

pstmt.setFetchSize(1000);

ResultSet rs = pstmt.executeQuery(vsql);

int cnt = rs.getMetaData().getColumnCount();

Object o;

while (rs.next()) {

    for (int i = 1; i <= cnt; i++) {

       o = rs.getObject(i);

    }

}

 

测试示例中的employee表有100000条记录,每条记录平均长度135字节

 

如下是测试结果,对每种fetchsize测试5次再取平均值:

 

fetchsize

 elapse_times

1

20.516

2

11.34

4

6.894

8

4.65

16

3.584

32

2.865

64

2.656

128

2.44

256

2.765

512

3.075

1024

2.862

2048

2.722

4096

2.681

8192

2.715

 

 

 

数据库访问性能优化(二) 

Oracle jdbc fetchsize默认值为10,由上测试能够看出fetchsize对性能影响仍是比较大的,可是当fetchsize大于100时就基本上没有影响了。fetchsize并不会存在一个最优的固定值,由于总体性能与记录集大小及硬件平台有关。根据测试结果建议当一次性要取大量数据时这个值设置为100左右,不要小于40。注意,fetchsize不能设置太大,若是一次取出的数据大于JVM的内存会致使内存溢出,因此建议不要超过1000,太大了也没什么性能提升,反而可能会增长内存溢出的危险。

注:图中fetchsize128之后会有一些小的波动,这并非测试偏差,而是因为resultset填充到具体对像时间不一样的缘由,因为resultset已经到本地内存里了,因此估计是因为CPUL1,L2 Cache命中率变化形成,因为变化不大,因此笔者也未深刻分析缘由。

 

iBatisSqlMapping配置文件能够对每一个SQL语句指定fetchsize大小,以下所示:

相关文章
相关标签/搜索