一、web服务器。
------------------------------------------------------无资源。
二、应用服务器开始:
Tomcat6.0
1.配置文件,并发操作。
<connector port=”8080” protocol=”HTTP/1.1”
keepAliveTimeout=”15000” -----长连接最大保持时间(毫秒)
maxKeepAliveRequests=”1” ----最大长连接个数(1表示禁用,-1表示不限制个数,默认100个,一般设置为100~200)
maxHttpHeaderSize=8192 ----请求头信息的最大程度,超过此长度的部分不予处理。一般为8K
maxThreads=”30000” -------最多同时处理的线程数,Tomcat使用线程来处理接收的每个请求。这个值表示Tomcat可以创建的最大的线程数。
minSpareThreads=”512” ------最小空闲线程数,Tomcat初始化时创建的线程数
maxSpareThreads=”2048” -----最大空闲连接数,一旦创建的线程超过这个值,Tomcat就会关闭不再需要的socket线程The default value is 50
minProcessors ------最小连接线程数,用于提高系统处理性能,默认值为10
maxProcessors -----最大连接线程数,就是并发处理的最大请求数,默认值为75
redirectPort=”8443”
acceptCount=”35000” -----指定当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理,默认为10个
debug=”0”
connectionTimeout=”40000” -----网络连接超时,单位:毫秒。通常可设置为30000毫秒
disableUploadTimeout=”true” -----上传时是否使用超时机制
enableLookups=”true” -----是否反查域名,为了提高处理能力,应该设置为false
URIEncoding=”UTF-8” -----指定Tomcat容器的URL编码格式
/>
提高tomcat并发能力的一些方法:
1. Apache+Tomcat结合起来用Apache负责静态页面,Tomcat负责动态页面,同时减少connectionTimeout的时间,以应对并发量大线程回收来不及的情况。
2. 压力过大的问题,可以做负载均衡,一个Tomcat无论如何也不可能担当如此多的线程负载,而且J VM过大,其内存管理成本将显著加大。2G的内存,做3-4个Tomcat实例,更为科学合理。
3. 数据库连接池,不少人,都推荐使用C3P0,能提高访问数据库的并发性能好几倍。(有博文说使用tomcat自带的jdbc-pool更好,无测试)
4. 采用Tomcat集群可以最大程度的发挥服务器的性能,可以在配置较高的服务器上部署多个Tomcat,也可以在多台服务器上分别部署Tomcat,Apache和Tomcat整合的方式还是JK方式。经过验证,系统对大用户量使用的响应方面,Apache+3Tomcat集群>Apache+2Tomcat集群>Apache集成Tomcat>单个Tomcat..并且采用Apache+多Tomcat集群的部署方式时,如果一个Tomcat出现宕机,系统可以继续使用,所以在硬件系统性能足够优越的情况下,需要尽量发挥软件的性能,可以采用增加Tomcat集群的方式。
5. 打开KeepAlive支持
KeepAliveon,KeepAliveTimeout 15 MaxKeepAliveRequests 1000
根据实际经验,通过Apache和Tomcat集群的方式提高系统性能的效果十分明显,这种方式可以最大化的利用硬件资源,通过多个Tomcat的处理来分担单Tomcat时压力。
Webserver允许的最大连接数还受制于操作系统的内核参数设置,通常Windows是2000左右,Liinux是1000个左右。
二、数据库方面(mysql)
http://isky000.com/database/mysql-perfornamce-tuning-cache-parameter
第一种缓存参数优化:
宏观库参数:
1.尽可能地将磁盘IO转化为内存IO.(因为从内存中读取数据时间是微妙级别,从磁盘中读取数据时间是毫秒级别------相差1000倍)
转化关注点一:
Query_cache_size作用于整个Mysql Instance,主要用来缓存MySQL中的ResultSet,就是一条SQL语句执行的结果集,所以仅仅只针对select语句。当我们打开了Query Cache功能后,如果满足Query Cache的要求,MySQL 会直接根据预先设定好的HASH算法将接受到的select语句以字符串方式进行hash,然后到Query Cache 中直接查找是否已经缓存。也就是说,如果已经在缓存中,该select请求就会直接将数据返回,从而省略了后面所有的步骤(如 SQL语句的解析,优化器优化以及向存储引擎请求数据等),极大的提高性能。
当然,Query Cache 也有一个致命的缺陷,那就是当某个表的数据有任何任何变化,都会导致所有引用了该表的select语句在Query Cache 中的缓存数据失效。所以,当我们的数据变化非常频繁的情况下,使用Query Cache 可能会得不偿失。
Query_cache的使用需要多个参数的配合,其中最为关键的是query_cache_size和query_cache_type,前者设置用于缓存ResultSet的内存大小,后者设置在何场景下使用QueryCache。在以往的经验来看,如果不是用来缓存基本不变的数据的,query_cache_size 一般256MB是一个比较合适的大小。当然,这可以通过计算Query Cache 的命中率来调整。(公式)((Qcache_hits/(Qcache_hits+Qcache_inserts)*100)))
Query_cache_type 可以设置为0(OFF),1(ON)或者2(DEMOND),分别表示完全不使用query_cache,除显式要求不使用query_cache(使用sql_no_cache)之外的所有的select都使用query cache,只有显式要求才使用query cache (sql_cache)。
转化关注点二:
Binlog_cache_size (global)
Binlog Cache 用来打开二进制日志(binlog)记录的功能环境,是mysql用来提高binlog记录效率而设计的一个用于短时间内临时缓存binlog数据的内存区域。
一般来说,如果我们的数据库没有什么大事务,写入也不是特别频繁,2MB~4MB是一个合适的选择。但是如果我们的数据库大事务较多,写入量比较大,可以适当调高binlog_cache_size。同时,我们可以通过binlog_cache_use 以及binlog_cache_disk_use来分析设置的binlog_cache_size是否足够,是否有大量的binlog_cache由于内存大小不够而使用临时文件(binlog_cache_disk_use)来缓存了。
转化关注点三:
key_buffer_size(global)
key Buffer 用来设置用于缓存MyISAM存储引擎中索引文件的内存区域大小。如果我们有足够的内存,这个缓存区域最好是能够存放下我们所有的MyISAM引擎表的所有索引,以尽可能提高性能。
此外,当我们在使用MyISAM 存储的时候有一个及其重要的点需要注意,由于 MyISAM 引擎的特性限制了他仅仅只会缓存索引块到内存中,而不会缓存表数据库块。所以,我们的 SQL 一定要尽可能让过滤条件都在索引中,以便让缓存帮助我们提高查询效率。
转化关注点四:
Bulk_insert_buffer_size (thread)
可key_buffer_size一样,这个参数同样也仅作用于使用MyISAM存储引擎,用来缓存批量插入数据的时候临时缓存写入数据。当我们使用如下几种数据写入语句的时候,会使用这个内存区域来缓存批量结构的数据以帮助批量写入数据文件:
Insert ….select…..
Insert…..values (),(),()……..
Load data infile …. Into ….. (非空表)
转化关注点五:
Innodb_buffer_pool_size(global)
当我们使用InnoDB存储引擎的时候,innodb_buffer_pool_size 参数可能是影响我们性能的最为关键的一个参数了,他用来设置用于缓存 InnoDB 索引及数据块的内存区域大小,类似于 MyISAM 存储引擎的
key_buffer_size 参数,当然,可能更像是 Oracle 的 db_cache_size。简单来说,当我们操作一个 InnoDB 表的时候,返回的所有数据或者去数据过程中用到的任何一个索引块,都会在这个内存区域中走一遭。 和key_buffer_size 对于 MyISAM 引擎一样,innodb_buffer_pool_size 设置了 InnoDB 存储引擎需求最大的一块内存区域的大小,直接关系到 InnoDB存储引擎的性能,所以如果我们有足够的内存,尽可将该参数设置到足够打,将尽可能多的 InnoDB 的索引及数据都放入到该缓存区域中,直至全部。
我们可以通过 (Innodb_buffer_pool_read_requests –
Innodb_buffer_pool_reads) / Innodb_buffer_pool_read_requests * 100% 计算缓存命中率,并根据命中率来调整 innodb_buffer_pool_size 参数大小进行优化。
转化关注点六:
Innodb_additional_mem_pool_size(global)
设置了InnoDB存储引擎用来存放数据字典信息以及一些内部数据结构的内存空间大小,所以当我们一个MySQL Instance中的数据库对象非常多的时候,是需要适当调整该参数的大小以确保所有数据字典都能存放在内存中提高访问效率的。
这个参数大小是否足够还是比较容易知道的,因为当过小的时候,MySQL 会记录 Warning 信息到数据库的 error log 中,这时候你就知道该调整这个参数大小了。
转化关注点七:
Innodb_log_buffer_size(global)
这是InnoDB存储引擎的事务日志所使用的缓冲区。类似于BinlogBuffer,InnoDB在写事务日志的时候,为了提高性能,也是先将信息系融入Innodb Log Buffer中,当满足innodb_flush_log_trx_commit参数所设置的相应条件(或者日志缓冲区写满)之后,才会将日志写到文件(或者同步到磁盘)中。可以通过innodb_log_buffer_size参数设置其可以使用的最大内存空间。
注:innodb_flush_log_trx_commit 参数对 InnoDB Log 的写入性能有非常关键的影响。该参数可以设置为0,1,2,解释如下:
0:log buffer中的数据将以每秒一次的频率写入到log file中,且同时会进行文件系统到磁盘的同步操作,但是每个事务的commit并不会触发任何log buffer 到log file的刷新或者文件系统到磁盘的刷新操作;
1:在每次事务提交的时候将log buffer 中的数据都会写入到log file,同时也会触发文件系统到磁盘的同步;
2:事务提交会触发log buffer 到log file的刷新,但并不会触发磁盘文件系统到磁盘的同步。此外,每秒会有一次文件系统到磁盘同步操作。
此外,MySQL文档中还提到,这几种设置中的每秒同步一次的机制,可能并不会完全确保非常准确的每秒就一定会发生同步,还取决于进程调度的问题。实际上,InnoDB 能否真正满足此参数所设置值代表的意义正常 Recovery 还是受到了不同 OS 下文件系统以及磁盘本身的限制,可能有些时候在并没有真正完成磁盘同步的情况下也会告诉 mysqld 已经完成了磁盘同步。
转化关注点八:
Innodb_max_dirty_pages_pct(global)
这个参数和上面的各个参数不同,他不是用来设置用于缓存某种数据的内存大小的一个参数,而是用来控制在 InnoDB Buffer Pool 中可以不用写入数据文件中的Dirty Page 的比例(已经被修改但还没有从内存中写入到数据文件的脏数据)。这个比例值越大,从内存到磁盘的写入操作就会相对减少,所以能够一定程度下减少写入操作的磁盘IO。
但是,如果这个比例值过大,当数据库 Crash (崩溃)之后重启的时间可能就会很长,因为会有大量的事务数据需要从日志文件恢复出来写入数据文件中。同时,过大的比例值同时可能也会造成在达到比例设定上限后的 flush 操作“过猛”而导致性能波动很大。
以上七个关注点参数是MySQL用来减少磁盘IO的主要参数,对MySQL性能有至关重要的影响。
下面是作者根据以往MySQL的使用经验得到的相关参数的建议值:
1.query_cache_type : 如果全部使用innodb存储引擎,建议为0(OFF),如果使用MyISAM 存储引擎,建议为2(DEMOND),同时在SQL语句中显式控制是否是要你gquery cache
query_cache_size: 根据 命中率:(Qcache_hits/(Qcache_hits+Qcache_inserts)*100))进行调整,一般不建议太大,256MB可能已经差不多了,大型的配置型静态数据可适当调大
2.binlog_cache_size: 一般环境2MB~4MB是一个合适的选择,事务较大且写入频繁的数据库环境可以适当调大,但不建议超过32MB
key_buffer_size: 如果不使用MyISAM存储引擎,16MB足以,用来缓存一些系统表信息等。如果使用 MyISAM存储引擎,在内存允许的情况下,尽可能将所有索引放入内存,简单来说就是“越大越好”
3.bulk_insert_buffer_size: 如果经常性的需要使用批量插入的特殊语句(上面有说明)来插入数据,可以适当调大该参数至16MB~32MB,不建议继续增大,某人8MB
4.innodb_buffer_pool_size: 如果不使用InnoDB存储引擎,可以不用调整这个参数,如果需要使用,在内存允许的情况下,尽可能将所有的InnoDB数据文件存放如内存中,同样将但来说也是“越大越好”
5.innodb_additional_mem_pool_size: 一般的数据库建议调整到8MB~16MB,如果表特别多,可以调整到32MB,可以根据error log中的信息判断是否需要增大
6.innodb_log_buffer_size: 默认是1MB,写入的频繁的系统可适当增大至4MB~8MB。当然如上面介绍所说,这个参数实际上还和另外的flush参数相关。一般来说不建议超过32MB
7.innodb_max_dirty_pages_pct: 根据以往的经验,重启恢复的数据如果要超过1GB的话,启动速度会比较慢,几乎难以接受,所以建议不大于 1GB/innodb_buffer_pool_size(GB)*100 这个值。当然,如果你能够忍受启动时间比较长,而且希望尽量减少内存至磁盘的flush,可以将这个值调整到90(也就是90%不用从内存到数据文件的写入),但不建议超过90
第二中表结构优化:
库表设计优化:
1·是表设计中每条记录所占的空间量减小。
很多人都将 数据库设计范式 作为数据库表结构设计“圣经”,认为只要按照这个范式需求设计,就能让设计出来的表结构足够优化,既能保证性能优异同时还能满足扩展性要求。殊不知,在N年前被奉为“圣经”的数据库设计3范式早就已经不完全适用了。这里我整理了一些比较常见的数据库表结构设计方面的优化技巧,希望对大家有用。
由于MySQL数据库是基于行(Row)存储的数据库,而数据库操作 IO 的时候是以 page(block)的方式,也就是说,如果我们每条记录所占用的空间量减小,就会使每个page 中可存放的数据行数增大,那么每次 IO 可访问的行数也就增多了。反过来说,处理相 同行数的数据,需要访问的 page 就会减少,也就是 IO 操作次数降低,直接提升性能。 此外,由于我们的内存是有限的,增加每个page中存放的数据行数,就等于增加每个内存块的缓存数据量,同时还会提升内存转换中数据命中的几率,也就是缓存命中率。
2·数据类型选择(原则性问题)。---------------------------
数据库操作中最为耗时的操作就是 IO 处理,大部分数据库操作 90% 以上的时间都花在了 IO 读写上面。所以尽可能减少 IO 读写量,可以在很大程度上提高数据库操作的性能。
我们无法改变数据库中需要存储的数据,但是我们可以在这些数据的存储方式方面花一些心思。下面的这些关于字段类型的优化建议主要适用于记录条数较多,数据量较大的场景,因为精细化的数据类型设置可能带来维护成本的提高,过度优化也可能会带来其他的问题:
1. 数字类型:非万不得已不要使用DOUBLE,不仅仅只是存储长度的问题,同时还会存在精确性的问题。同样,固定精度的小数,也不建议使用DECIMAL,建议乘以固定倍数转换成整数存储,可以大大节省存储空间,且不会带来任何附加维护成本。对于整数的存储,在数据量较大的情况下,建议区分开 TINYINT / INT / BIGINT的选择,因为三者所占用的存储空间也有很大的差别,能确定不会使用负数的字段,建议添加unsigned定义。当然,如果数据量较小的数据库,也可以不用严格区分三个整数类型。
2. 字符类型:非万不得已不要使用 TEXT 数据类型,其处理方式决定了他的性能要低于char或者是varchar类型的处理。定长字段,建议使用 CHAR 类型,不定长字段尽量使用 VARCHAR,且仅仅设定适当的最大长度,而不是非常随意的给一个很大的最大长度限定,因为不同的长度范围,MySQL也会有不一样的存储处理。
3. 时间类型:尽量使用TIMESTAMP类型,因为其存储空间只需要 DATETIME 类型的一半。对于只需要精确到某一天的数据类型,建议使用DATE类型,因为他的存储空间只需要3个字节,比TIMESTAMP还少。不建议通过INT类型类存储一个unix timestamp 的值,因为这太不直观,会给维护带来不必要的麻烦,同时还不会带来任何好处。
4. ENUM & SET:对于状态字段,可以尝试使用 ENUM 来存放,因为可以极大的降低存储空间,而且即使需要增加新的类型,只要增加于末尾,修改结构也不需要重建表数据。如果是存放可预先定义的属性数据呢?可以尝试使用SET类型,即使存在多种属性,同样可以游刃有余,同时还可以节省不小的存储空间。
5. LOB类型:强烈反对在数据库中存放 LOB 类型数据,虽然数据库提供了这样的功能,但这不是他所擅长的,我们更应该让合适的工具做他擅长的事情,才能将其发挥到极致。在数据库中存储 LOB 数据就像让一个多年前在学校学过一点Java的营销专业人员来写 Java 代码一样。
3·字符编码。-----------------------------------------------------------------------------------------------------
字符编码
字符集直接决定了数据在MySQL中的存储编码方式,由于同样的内容使用不同字符集表示所占用的空间大小会有较大的差异,所以通过使用合适的字符集,可以帮助我们尽可能减少数据量,进而减少IO操作次数。
6. 纯拉丁字符能表示的内容,没必要选择 latin1 之外的其他字符编码,因为这会节省大量的存储空间
7. 如果我们可以确定不需要存放多种语言,就没必要非得使用UTF8或者其他UNICODE字符类型,这回造成大量的存储空间浪费
8. MySQL的数据类型可以精确到字段,所以当我们需要大型数据库中存放多字节数据的时候,可以通过对不同表不同字段使用不同的数据类型来较大程度减小数据存储量,进而降低 IO 操作次数并提高缓存命中率
4·适当拆分。-----------------------------------------------------------------------------------------------------
有些时候,我们可能会希望将一个完整的对象对应于一张数据库表,这对于应用程序开发来说是很有好的,但是有些时候可能会在性能上带来较大的问题。
当我们的表中存在类似于 TEXT 或者是很大的 VARCHAR类型的大字段的时候,如果我们大部分访问这张表的时候都不需要这个字段,我们就该义无反顾的将其拆分到另外的独立表中,以减少常用数据所占用的存储空间。这样做的一个明显好处就是每个数据块中可以存储的数据条数可以大大增加,既减少物理 IO 次数,也能大大提高内存中的缓存命中率。
上面几点的优化都是为了减少每条记录的存储空间大小,让每个数据库中能够存储更多的记录条数,以达到减少 IO 操作次数,提高缓存命中率。下面这个优化建议可能很多开发人员都会觉得不太理解,因为这是典型的反范式设计,而且也和上面的几点优化建议的目标相违背。
5·适度冗余。-----------------------------------------------------------------------------------------------------
为什么我们要冗余?这不是增加了每条数据的大小,减少了每个数据块可存放记录条数吗?
确实,这样做是会增大每条记录的大小,降低每条记录中可存放数据的条数,但是在有些场景下我们仍然还是不得不这样做:---------2013-11-6日到此
1. 被频繁引用且只能通过 Join 2张(或者更多)大表的方式才能得到的独立小字段
这样的场景由于每次Join仅仅只是为了取得某个小字段的值,Join到的记录又大,会造成大量不必要的 IO,完全可以通过空间换取时间的方式来优化。不过,冗余的同时需要确保数据的一致性不会遭到破坏,确保更新的同时冗余字段也被更新
6·尽量使用NOT NULL。-----------------------------------------------------------------------
NULL 类型比较特殊,SQL 难优化。虽然 MySQL NULL类型和 Oracle 的NULL 有差异,会进入索引中,但如果是一个组合索引,那么这个NULL 类型的字段会极大影响整个索引的效率。此外,NULL 在索引中的处理也是特殊的,也会占用额外的存放空间。
很多人觉得 NULL 会节省一些空间,所以尽量让NULL来达到节省IO的目的,但是大部分时候这会适得其反,虽然空间上可能确实有一定节省,倒是带来了很多其他的优化问题,不但没有将IO量省下来,反而加大了SQL的IO量。所以尽量确保 DEFAULT 值不是 NULL,也是一个很好的表结构设计优化习惯。
第三个MySQL优化之存储引擎选择:
MySQL 的存储引擎可能是所有关系型数据库产品中最具有特色的了,不仅可以同时使用多种存储引擎,而且每种存储引擎和MySQL之间使用插件方式这种非常松的耦合关系。
由于各存储引擎功能特性差异较大,这篇文章主要是介绍如何来选择合适的存储引擎来应对不同的业务场景。
· MyISAM
o 特性
1. 不支持事务:MyISAM存储引擎不支持事务,所以对事务有要求的业务场景不能使用
2. 表级锁定:其锁定机制是表级索引,这虽然可以让锁定的实现成本很小但是也同时大大降低了其并发性能
3. 读写互相阻塞:不仅会在写入的时候阻塞读取,MyISAM还会在读取的时候阻塞写入,但读本身并不会阻塞另外的读
4. 只会缓存索引:MyISAM可以通过key_buffer缓存以大大提高访问性能减少磁盘IO,但是这个缓存区只会缓存索引,而不会缓存数据
o 适用场景
1. 不需要事务支持(不支持)
2. 并发相对较低(锁定机制问题)
3. 数据修改相对较少(阻塞问题)
4. 以读为主
5. 数据一致性要求不是非常高(是同步要求不高吗?)
o 最佳实践
1. 尽量索引(缓存机制)
2. 调整读写优先级,根据实际需求确保重要操作更优先(应用逻辑内)
3. 启用延迟插入改善大批量写入性能
4. 尽量顺序操作让insert数据都写入到尾部,减少阻塞
5. 分解大的操作,降低单个操作的阻塞时间
6. 降低并发数,某些高并发场景通过应用来进行排队机制
7. 对于相对静态的数据,充分利用Query Cache可以极大的提高访问效率
8. MyISAM的Count只有在全表扫描的时候特别高效,带有其他条件的count都需要进行实际的数据访问
· InnoDB
o 特性
1. 具有较好的事务支持:支持4个事务隔离级别,支持多版本读
2. 行级锁定:通过索引实现,全表扫描仍然会是表锁,注意间隙锁的影响
3. 读写阻塞与事务隔离级别相关
4. 具有非常高效的缓存特性:能缓存索引,也能缓存数据
5. 整个表和主键以Cluster方式存储,组成一颗平衡树
6. 所有Secondary Index都会保存主键信息
o 适用场景
1. 需要事务支持(具有较好的事务特性)
2. 行级锁定对高并发有很好的适应能力,但需要确保查询是通过索引完成
3. 数据更新较为频繁的场景
4. 数据一致性要求较高
5. 硬件设备内存较大,可以利用InnoDB较好的缓存能力来提高内存利用率,尽可能减少磁盘 IO
o 最佳实践
1. 主键尽可能小,避免给Secondary index(辅助索引)带来过大的空间负担
2. 避免全表扫描,因为会使用表锁
3. 尽可能缓存所有的索引和数据,提高响应速度(需要大内存)
4. 在大批量小插入的时候,尽量自己控制事务而不要使用autocommit自动提交
5. 合理设置innodb_flush_log_at_trx_commit参数值,不要过度追求安全性
6. 避免主键更新,因为这会带来大量的数据移动
· NDBCluster
o 特性
1. 分布式:分布式存储引擎,可以由多个NDBCluster存储引擎组成集群分别存放整体数据的一部分
2. 支持事务:和Innodb一样,支持事务
3. 可与mysqld不在一台主机:可以和mysqld分开存在于独立的主机上,然后通过网络和mysqld通信交互
4. 内存需求量巨大:新版本索引以及被索引的数据必须存放在内存中,老版本所有数据和索引必须存在与内存中
o 适用场景
1. 具有非常高的并发需求
2. 对单个请求的响应并不是非常的critical
3. 查询简单,过滤条件较为固定,每次请求数据量较少,又不希望自己进行水平Sharding(数据库扩展性)----(网上说是近似水平分区)
o 最佳实践
1. 尽可能让查询简单,避免数据的跨节点传输
2. 尽可能满足SQL节点的计算性能,大一点的集群SQL节点会明显多余Data节点
3. 在各节点之间尽可能使用万兆网络环境互联,以减少数据在网络层传输过程中的延时
第四个MySQL优化之SQL优化:
优化目标
1. 减少 IO 次数
IO永远是数据库最容易瓶颈的地方,这是由数据库的职责所决定的,大部分数据库操作中超过90%的时间都是 IO 操作所占用的,减少 IO 次数是 SQL 优化中需要第一优先考虑,当然,也是收效最明显的优化手段。
2. 降低 CPU 计算
除了 IO 瓶颈之外,SQL优化中需要考虑的就是 CPU 运算量的优化了。orderby, group by,distinct … 都是消耗 CPU 的大户(这些操作基本上都是 CPU 处理内存中的数据比较运算)。当我们的 IO 优化做到一定阶段之后,降低 CPU 计算也就成为了我们 SQL 优化的重要目标
优化方法
改变 SQL 执行计划
明确了优化目标之后,我们需要确定达到我们目标的方法。对于 SQL 语句来说,达到上述2个目标的方法其实只有一个,那就是改变 SQL 的执行计划,让他尽量“少走弯路”,尽量通过各种“捷径”来找到我们需要的数据,以达到 “减少 IO 次数” 和 “降低 CPU 计算” 的目标
常见误区
1. count(1)和count(primary_key) 优于 count(*)
很多人为了统计记录条数,就使用 count(1) 和count(primary_key) 而不count(*) ,他们认为这样性能更好,其实这是一个误区。对于有些场景,这样做可能性能会更差,应为数据库对 count(*) 计数操作做了一些特别的优化。
2.count(column) 和 count(*) 是一样的
这个误区甚至在很多的资深工程师或者是 DBA 中都普遍存在,很多人都会认为这是理所当然的。实际上,count(column) 和 count(*) 是一个完全不一样的操作,所代表的意义也完全不一样。
count(column) 是表示结果集中有多少个column字段不为空的记录
count(*) 是表示整个结果集有多少条记录
3.select a,b from … 比 select a,b,c from … 可以让数据库访问更少的数据量
这个误区主要存在于大量的开发人员中,主要原因是对数据库的存储原理不是太了解。
实际上,大多数关系型数据库都是按照行(row)的方式存储,而数据存取操作都是以一个固定大小的IO单元(被称作 block 或者 page)为单位,一般为4KB,8KB… 大多数时候,每个IO单元中存储了多行,每行都是存储了该行(改记录)的所有字段(lob等特殊类型字段除外)。
所以,我们是取一个字段还是多个字段,实际上数据库在表中需要访问的数据量其实是一样的。
当然,也有例外情况,那就是我们的这个查询在索引中就可以完成,也就是说当只取 a,b两个字段的时候,不需要回表,而c这个字段不在使用的索引中,需要回表取得其数据。在这样的情况下,二者的IO量会有较大差异。(也就是说,需不需要去表里查询去)
4.order by 一定需要排序操作
我们知道索引数据实际上是有序的,如果我们需要的数据和某个索引的顺序一致,而且我们的查询又通过这个索引来执行,那么数据库一般会省略排序操作,而直接将数据返回,因为数据库知道数据已经满足我们的排序需求了。
实际上,利用索引来优化有排序需求的 SQL,是一个非常重要的优化手段
延伸阅读:MySQL ORDER BY 的实现分析 ,MySQL 中 GROUP BY 基本实现原理 以及 MySQL DISTINCT 的基本实现原理 这3篇文章中有更为深入的分析,尤其是第一篇
5.执行计划中有 filesort 就会进行磁盘文件排序
有这个误区其实并不能怪我们,而是因为 MySQL 开发者在用词方面的问题。filesort 是我们在使用 explain 命令查看一条 SQL 的执行计划的时候可能会看到在 “Extra” 一列显示的信息。
实际上,只要一条 SQL 语句需要进行排序操作,都会显示“Using filesort”,这并不表示就会有文件排序操作。
延伸阅读:理解 MySQL Explain 命令输出中的filesort,我在这里有更为详细的介绍
基本原则
1.尽量少 join
MySQL 的优势在于简单,但这在某些方面其实也是其劣势。MySQL 优化器效率高,但是由于其统计信息的量有限,优化器工作过程出现偏差的可能性也就更多。对于复杂的多表 Join,一方面由于其优化器受限,再者在 Join 这方面所下的功夫还不够,所以性能表现离 Oracle 等关系型数据库前辈还是有一定距离。但如果是简单的单表查询,这一差距就会极小甚至在有些场景下要优于这些数据库前辈。(所以要进行字段冗余设计)
2.尽量少排序
排序操作会消耗较多的 CPU 资源,所以减少排序可以在缓存命中率高等IO能力足够的场景下会较大影响 SQL的响应时间。
对于MySQL来说,减少排序有多种办法,比如:
§ 上面误区中提到的通过利用索引来排序的方式进行优化
§ 减少参与排序的记录条数
§ 非必要不对数据进行排序
§ …
3.尽量避免 select *
很多人看到这一点后觉得比较难理解,上面不是在误区中刚刚说 select 子句中字段的多少并不会影响到读取的数据吗?
是的,大多数时候并不会影响到 IO 量,但是当我们还存在 order by 操作的时候,select 子句中的字段多少会在很大程度上影响到我们的排序效率,这一点可以通过我之前一篇介绍 MySQL ORDER BY 的实现分析 的文章中有较为详细的介绍。
此外,上面误区中不是也说了,只是大多数时候是不会影响到 IO 量,当我们的查询结果仅仅只需要在索引中就能找到的时候,还是会极大减少 IO 量的。
4.尽量用 join 代替子查询
虽然 Join 性能并不佳,但是和 MySQL 的子查询比起来还是有非常大的性能优势。MySQL 的子查询执行计划一直存在较大的问题,虽然这个问题已经存在多年,但是到目前已经发布的所有稳定版本中都普遍存在,一直没有太大改善。虽然官方也在很早就承认这一问题,并且承诺尽快解决,但是至少到目前为止我们还没有看到哪一个版本较好的解决了这一问题。
5.尽量少 or
当 where 子句中存在多个条件以“或”并存的时候,MySQL 的优化器并没有很好的解决其执行计划优化问题,再加上 MySQL 特有的 SQL 与 Storage 分层架构方式,造成了其性能比较低下,很多时候使用 union all 或者是union(必要的时候)的方式来代替“or”会得到更好的效果。
6.尽量用 union all 代替 union
union 和 union all 的差异主要是前者需要将两个(或者多个)结果集合并后再进行唯一性过滤操作,这就会涉及到排序,增加大量的 CPU 运算,加大资源消耗及延迟。所以当我们可以确认不可能出现重复结果集或者不在乎重复结果集的时候,尽量使用 union all 而不是 union。
7.尽量早过滤
这一优化策略其实最常见于索引的优化设计中(将过滤性更好的字段放得更靠前)。
在 SQL 编写中同样可以使用这一原则来优化一些 Join 的 SQL。比如我们在多个表进行分页数据查询的时候,我们最好是能够在一个表上先过滤好数据分好页,然后再用分好页的结果集与另外的表 Join,这样可以尽可能多的减少不必要的 IO 操作,大大节省 IO 操作所消耗的时间。
8.避免类型转换
这里所说的“类型转换”是指 where 子句中出现 column 字段的类型和传入的参数类型不一致的时候发生的类型转换:
§ 人为在column_name 上通过转换函数进行转换
直接导致 MySQL(实际上其他数据库也会有同样的问题)无法使用索引,如果非要转换,应该在传入的参数上进行转换
§ 由数据库自己进行转换
如果我们传入的数据类型和字段类型不一致,同时我们又没有做任何类型转换处理,MySQL 可能会自己对我们的数据进行类型转换操作,也可能不进行处理而交由存储引擎去处理,这样一来,就会出现索引无法使用的情况而造成执行计划问题。
9.优先优化高并发的 SQL,而不是执行频率低某些“大”SQL
对于破坏性来说,高并发的 SQL 总是会比低频率的来得大,因为高并发的 SQL 一旦出现问题,甚至不会给我们任何喘息的机会就会将系统压跨。而对于一些虽然需要消耗大量 IO 而且响应很慢的 SQL,由于频率低,即使遇到,最多就是让整个系统响应慢一点,但至少可能撑一会儿,让我们有缓冲的机会。
10.从全局出发优化,而不是片面调整
SQL 优化不能是单独针对某一个进行,而应充分考虑系统中所有的 SQL,尤其是在通过调整索引优化 SQL 的执行计划的时候,千万不能顾此失彼,因小失大。
11.尽可能对每一条运行在数据库中的SQL进行 explain
优化 SQL,需要做到心中有数,知道 SQL 的执行计划才能判断是否有优化余地,才能判断是否存在执行计划问题。在对数据库中运行的 SQL 进行了一段时间的优化之后,很明显的问题 SQL 可能已经很少了,大多都需要去发掘,这时候就需要进行大量的 explain 操作收集执行计划,并判断是否需要进行优化。
框架优化部分:
Hibernate部分的优化:
1. hibernate的二级缓存被从属于一个SessionFactory的所有Session对象共享,当程序使用Session加载持久化对象时,Session首先会根据加载数据类和唯一性标识在缓存中查找是否存在此对象的缓存实例。如果存在,直接返回结果;否则继续在二级缓存中查找。如果有,返回结果,如果没有,直接访问数据库。
2. 由于Hibernate本身并未提供二级缓存的产品化实现,所以需要引入第三方插件实现二级缓存的策略。-------现在采用Hibernate的默认的二级缓存插件EHCache配置和使用。
3. 没有用spring管理hibernate配置文件的情况下:
在hibernate自己的配置文件中
Hibernate.cfg.xml中配置开启二级缓存。关键代码如下:
<hibernate-configuration>
<session-factory>
<!—开启二级缓存—>
<property name=”hibernate.cache.use_second_level_cache”>
true
</property>
<!—指定缓存产品提供商-->
<property name=”hibernate.cache.provider_class”>
org.hibernate.cache.EhCacheProvider
</property>
</session-factory>
</hibernate-configuration>
在持久化类的映射文件中需要指定缓存的同步策略,关键代码如下:
<!- -产品信息字段配置信息-->
<hibernate-mapping>
<class name=”com.mr.product.Product” table=”tab_product”>
<!—指定的二级缓存同步策略 -->
<cache usage=”read-only”/>
</class>
</hibernate-mapping>
在项目的classpath根目录下添加缓存配置文件ehcache.xml,此文件可以从Hibernate的zip包下的etc目录中找到,关键代码如下:
<ehcache>
<diskStore path=”java.io.tmpdir”/>
<defaultCache
maxElementsInMemory=”10000”<!—缓存中允许保存的最大数据实例数量-->
eternal=”false” <!—缓存中的数据是否为常量-->
timeToldleSeconds=”120” <!—缓存数据钝化时间-->
timeToLiveSeconds=”120” <!—缓存数据生存时间-->
overflowToDisk=”true” <!—是否启用磁盘缓存-->
/>
</ehcache>
当hibernate和spring整合后,要在spring中applicationContext.xml配置如下:
<property name=”hibernateProperties”>
<props>
<!—此处用来定义hibernate的SessionFactory的属性:不同数据库连接,启动时选择create,update,create-drop -- >
<prop key=”hibernate.dialect”>
org.hibernate.dialect.MySQL5Dialect
</prop>
<prop key=”hibernate.hbm2ddl.auto”>update</prop>
<!—导入提供的第三方插件-- >
<prop key=”hibernate.cache.provider_class”>
org.hibernate.cache.EhCacheProvider
</prop>
<!—开启二级缓存-- >
<prop key=”hibernate.cache.use_second_level_cache”>true</prop>
<!—使用查询缓存-- >
<prop key=”hibernate.cache.use_query_cache”>true</prop>
</props>
</property>
同样在开启二级缓存后,要在相应的映射文件中或者说对相应的表进行操作时使用二级缓存并指定缓存策略,例如:缓存策略指定为read-write:
<?xml version=”1.0”?>
<!DOCTYPE hibernate-mapping PUBLIC
“-//Hibernate/HibernateMapping DTD 3.0//EN”
http://hibernate.sourceforge.net/hibernate-maping-3.0.dtd>
<hibernate-mapping package=”com.bbs.entity”>
<class name=”User”>
<cache usage=”read-write”/>
<id name=”user_id”>
<generator class=”identity”></generator>
</id>
<property name=”user_name”></property>
……………………
</class>
</hibernate-mapping>
在类路径下面进行EHCache的配置,建立ehchache.xml配置缓存的参数。
<ehcache>
<diskStore path=”java.io.tmpdir”/>
<defaultCache maxElementsInMemory=”10000”eternal=”false”
timeToldleSeconds=”120”timeToLiveSeconds=”120” overflowToDisk=”true”
diskPersistent=”false”diskExpiryThreadIntervalSeconds=”120”
memoryStoreEvictionPolicy=”LRU”/>
</ehcache>
以上两种情况都需要添加相应的jar包:
Spring-framework-2.5.6\lib\ehcache\ehcache-1.5.0.jar
还有依赖包:
Spring-framework-2.5.6\lib\concurrent\backport-util-concurrent.jar.
配置完成了。
给hibernate添加数据库连接池:
待查:
三种链接(mysql为例):c3p0---dbcp----proxool
<! — JDBC 驱动程序 -->
<property name=”connection.driver_class”>org.gjt.mm.mysql.Driver</property>
<property name=”connection.url”>
jdbc:mysql://localhost:3306/struts?useUnicode=true&characterEncoding=GBK
</property>
<! — 数据库用户名 -- >
<property name=”connection.username”>root</property>
<!—数据库密码 – >
<property name=”connection.password”>8888</property>
上面一段配置,在c3p0和dbcp中都是必须的,因为hibernate会根据上述配置来生成connections,再交给c3p0或dbcp管理。Proxool待定。
下面是c3p0在hibernate配置文件中的配置:
只需在hibernate.cfg.xml中加入
<property name=”c3p0.min_size”>5</property><!-- 连接池中可用数据库连接的最小数目-- >
<property name=”c3p0.max_size”>30</property><!-- 连接池中所有数据库连接的最大数目-- >
<property name=”c3p0.time_out”>1800</property><!-- 设定数据库连接的超时时间-- >
<property name=”c3p0.max_statement”>50</property><!-- 可以被缓存的PreparedStatement的最大数目 -- >
导入第三方包:c3p0-0.8.4.5.jar
下面是dbcp在hibernate中的配置:
<property name=”dbcp.maxActive”>100</property>
<property name=”dbcp.whenExhaustedAction”>1</property>
<property name=”dbcp.maxWait”>60000</property>
<property name=”dbcp.maxdle”>10</property>
<property name=”dbcp.ps.maxActive”>100</property>
<property name=”dbcp.ps.whenExhaustedAction”>1</property>
<property name=”dbcp.ps.maxWait”>60000</property>
<property name=”dbcp.ps.maxdle”>10</property>
导入包:commons-pool-1.2.jar和commons-dbcp-1.2.1.jar
下面是proxool连接池在hibernate中的配置:
<property name=”proxool.pool_alias”>pool1</property><!--和下面ProxoolConf.xml中的别名对应-->
<property name=”proxool.xml”>ProxoolConf.xml</property><!--下面对应的ProxoolConf.xmlà
<property name=”connection.provider_class”>
net.sf.hibernate.connection.ProxoolConnectionProvider
</property>
然后在hibernate同一个目录下,加一个ProxoolConf.xml配置文件,内容为
<?xml version=”1.0” encoding=”utf-8”?>
<!--the proxool configuration can be embedded within your ownapplication’s.Anything outside the “proxool” tag is ignored.-->
<something-else-entirely>
<proxool>
<alias>pool1</alias><!-- 连接池的别名,注意和hibernate部分要对应-->
<!—proxool 只能管理由自己产生的连接 -- >
<driver-url>
jdbc:mysql://localhost:3306/struts?useUnicode=true&characterEncoding
GBK
</driver-url>
<driver-class>org.gjt.mm.mysql.Driver</driver-class>
<driver-properties>
<property name=”user” value=”root”/>
<property name=”password” value=”8888”/>
</driver-properties>
<!-- proxool自动侦察各个连接状态的时间间隔(毫秒),侦察到空闲的连接就马上回收,超时的销毁 -->
<house-keeping-sleep-time>90000</house-keeping-sleep-time>
<!-- 指因未有空闲连接可以分配而在队列中等候的最大请求数,超过这个请求数的用户连接就不会被接受 -->
<maximum-new-connections>20</maximum-new-connections>
<!-- 最少保持的空闲连接数 -- >
<prototype-count>5</prototype-count>
<!-- 允许最大连接数,超过这个连接数,再有请求时,就排队在队列中等候,最大的等待请求数由maximun-new-connections决定 -- >
<maximum-connection-count>100</maximum-connection-count>
<!-- 最小连接数 -- >
<minimum-connection-count>10</minimum-connection-count>
</proxool>
</something-else-entirely>
(由于在hibernate3.0中,已经不再支持dbcp了,hibernate的作者在hibernate.org中,明确指出在实践中发现dbcp有BUG,在某些种情况会产生很多空连接不能释放,所以抛弃了对dbcp的支持。
由于在hibernate3.0中,已经不再支持dbcp了,hibernate的作者在hibernate.org中,明确指出在实践中发现dbcp有BUG,在某些种情会产生很多空连接不能释放,所以抛弃了对dbcp的支持。我不知是否在dbcp最新版本中解决了这个问题,我以前在一个访问量不大的项目中用过dbcp,运行了一年多都没出现问题。不过在网上的确也有不少网友指出dbcp在大型的应用中会出现不稳定的情况。所以在真相未经证实的情况下,我觉得对dbcp持慎重的态度。
至于c3p0,有评论说它的算法不是最优的,而且,我在matrix中,见到有网友做了一个实验,在同一项目中分别用了几个常用的连接池,然后测试其性能,发现c3p0占用资源比较大,效率也不高。
所以,基于上述原因,我才花两晚的时间去研究proxool的配置,proxool不少行家推荐使用,而且暂时来说,是负面评价是最少的一个。在三星中也有项目是用proxool的)
下面是从容器中获得连接池(tomcat):
Hibernate想要从Tomcat中获取数据源,需要对Tomcat容器与Hibernate分别进行配置。
1.容器的数据源配置待查:(配置好的JNDI”jdbc/feifei”):
获取tomcat6.0数据源的配置
在WebRoot下面建立文件夹META-INF,里面建一个文件context.xml,内容如下:
<context>
<resource
name=”jdbc/feifei”
auth=”container”
type=”javax.sql.DataSource”
maxActive=”50”
maxIdle=”30”
maxWait=”10000”
logAbandoned=”true”
username=”root”
password=”1111”
driverClassName=”com.mysql.jdbc.Driver”
url=”jdbc:mysql://localhost:3306/testdb”
/>
</context>
在tomcat6.0目录的context.xml修改原来的context标签,内容如下:
<context>
<!--Default set of monitored resources -- >
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<Resource
name=”jdbc/feifei”
auth=”container”
type=”javax.sql.DataSource”
maxAcitive=”50”
maxIdle=”30”
maxWait=”10000”
logAbandoned=”true”
username=”root”
password=”11111”
driverClassName=”com.mysql.jdbc.Driver”
url=”jdbc:mysql://localhost:3306/testdb”
/>
</context>
在虚拟目录,也就是Host标签的内嵌组件:context
<context path=”/WebRoot” reloadable=”true” docBase=”E:\workspace\DataSource\WebRoot”>
<resource
Name=”jdbc/feifei”
auth=”Container”
type=”javax.sql.DataSource”
maxActive=”50”
maxIdle=”30”
maxWait=”10000”
logAbandoned=”true”
username=”root”
password=”111111”
driverClassName=”com.mysql.jdbc.Driver”
url=”jdbc:mysql://localhost:3306/testdb”
/>
</context>
配置文件中Resource标签中各属性的含义:
DirverClasssName ----JDBC所有到的数据库驱动类的全名
MaxActive ----连接池在同一时刻内所提供的最大活动连接数
MaxIdle ----连接池在空闲时刻保持的最大连接数
MaxWait ----当发生异常时数据库等待的最大毫秒数(当没有可用的连接时)
Passord ----连接数据库的密码
url -----连接至驱动的URL(为了向后兼容,Drivername也被允许)
user -----数据库用户名
//测试代码:
Context initConect=newInitialContext();
Context envContext=(Context)initContext.lookup(“java:/comp/env”);
DataSource ds=(DataSource)envContext.lookup(“jdbc/test”);
System.out.println(ds.getConnection());
2.在hibernate.cfg.xml中指定使用这个名为”jdbc/feifei”的JNDI
<propertyname=”hibernate.connection.datasource”>
java:/comp/env/jdbc/feifei
</property>
<!—指定这个JNDI名字就可以了,而且必须是完整的 --- >
连接池为我们开发带来了极大的便利,他为我们开发者承担了数据库连接管理的压力,让我们从繁琐的数据库事务中解脱出来,专心研究业务逻辑实现。
出现的问题:
1. session串号问题。
2. 用户登录后,