在上一篇的MySQL优化的文章中,主要是把MySQL的一些基本知识进行了详细的讲解,并且还对可能影响性能的因素进行了讲解,并还没有进入真正的优化操作,但是基础很重要,针对上一篇文章中的索引的知识,一定一定要明白,如果不是很懂的,可以看一下上一篇文章,虽然文字比较多,偏于理论,但确实都是干货,能真正的看完的话,我想对于整个数据库的结构都会有一定的了解的。所以,紧接着上面的内容,这里就进一步的进行说明一下。。因为自己也不是专门进行数据库开发的,有些地方可能不是说明很清楚,如果有问题的地方,欢迎进行留言指正,欢迎大家进行交流,共同进步~!!
一:分析MySQL的执行计划(explain关键字)
- 通过这个可以提前了解到MySQL在执行不同的语句的时候,进行的执行处理的一些基本内容,这样的话,对于我们写SQL语句肯定就有一定的影响。打个比方,比如我们这个表里面有多个索引,但是想看到由于where条件的限制,这条MySQL语句到底是使用的哪一个索引进行的呢?如果预期和实际有所偏差,那么我们就根据SQL语句进行修改,那么这样是不是就能够更加充分的使用索引呢?
好了,来看一下实际的操作吧~!要不然,就要晕了~!
(1)SQL语句:select * from person ;
查询执行计划语句:explain select * from person ;
别慌,这上面到底是啥呀。。。都是些上面呢?别急呀,再来几个,你就会发现奇迹出现了~!!
(2)SQL语句:select * from person where id = 2;
执行查询计划语句:explain select * from person where id =2;
查询计划结果:“
(3)SQL语句:select * from person where name = '小明'; -------------------------------------PS:列”name“是有一个普通索引,并且名字为nameindex
执行查询计划语句:explain select * from person where name ='小明';
查询计划结果:
好了,上面的三条信息,其实可以作为一个教案了,就是包含了有索引,无索引,有条件,无条件的查询操作了,当然如果有什么其他的where限制,这都是根据表的具体结构来进行的。。。。咳咳,是不是看不懂上面每个字段的内容是啥呢?下面我就整理了关于每个字段的详细描述,你只要看了,然后根据上面的表格,再加上自己亲手进行实验测试的话,就很好理解的。
返回结果详细字段信息:
1,ID:执行查询的序列号;
2,select_type:使用的查询类型
1,DEPENDENT SUBQUERY:子查询中内层的第一个SELECT,依赖于外部查询的结果集;
2,DEPENDENT UNION:子查询中的UNION,且为UNION 中从第二个SELECT 开始的后面所有SELECT,同样依赖于外部查询的结果集;
3,PRIMARY:子查询中的最外层查询,注意并不是主键查询;
4,SIMPLE:除子查询或者UNION 之外的其他查询;
5,SUBQUERY:子查询内层查询的第一个SELECT,结果不依赖于外部查询结果集;
6,UNCACHEABLE SUBQUERY:结果集无法缓存的子查询;
7,UNION:UNION 语句中第二个SELECT 开始的后面所有SELECT,第一个SELECT 为PRIMARY
8,UNION RESULT:UNION 中的合并结果;
3,table:这次查询访问的数据表;
4,type:对表所使用的访问方式:
1,all:全表扫描
2,const:读常量,且最多只会有一条记录匹配,由于是常量,所以实际上只需要读一次;
3,eq_ref:最多只会有一条匹配结果,一般是通过主键或者唯一键索引来访问;
4,fulltext:全文检索,针对full text索引列;
5,index:全索引扫描;
6,index_merge:查询中同时使用两个(或更多)索引,然后对索引结果进行merge 之后再读取表数据;
7,index_subquery:子查询中的返回结果字段组合是一个索引(或索引组合),但不是一个主键或者唯一索引;
8,rang:索引范围扫描;
9,ref:Join 语句中被驱动表索引引用查询;
10,ref_or_null:与ref 的唯一区别就是在使用索引引用查询之外再增加一个空值的查询;
11,system:系统表,表中只有一行数据;
12,unique_subquery:子查询中的返回结果字段组合是主键或者唯一约束;
5,possible_keys:可选的索引;如果没有使用索引,为null;
6,key:最终选择的索引;
7,key_len:被选择的索引长度;
8,ref:过滤的方式,比如const(常量),column(join),func(某个函数);
9,rows:查询优化器通过收集到的统计信息估算出的查询条数;
10,Extra:查询中每一步实现的额外细节信息
1,Distinct:查找distinct 值,所以当mysql 找到了第一条匹配的结果后,将停止该值的查询而转为后面其他值的查询;
2,Full scan on NULL key:子查询中的一种优化方式,主要在遇到无法通过索引访问null值的使用使用;
3,Impossible WHERE noticed after reading const tables:MySQL Query Optimizer 通过收集到的统计信息判断出不可能存在结果;
4,No tables:Query 语句中使用FROM DUAL 或者不包含任何FROM 子句;
5,Not exists:在某些左连接中MySQL Query Optimizer 所通过改变原有Query 的组成而使用的优化方法,可以部分减少数据访问次数;
6,Select tables optimized away:当我们使用某些聚合函数来访问存在索引的某个字段的时候,MySQL Query Optimizer 会通过索引而直接一次定位到所需的数据行完成整个查询。当然,前提是在Query 中不能有GROUP BY 操作。如使用MIN()或者MAX()的时候;
7,Using filesort:当我们的Query 中包含ORDER BY 操作,而且无法利用索引完成排序操作的时候,MySQL Query Optimizer 不得不选择相应的排序算法来实现。
8,Using index:所需要的数据只需要在Index 即可全部获得而不需要再到表中取数据;
9,Using index for group-by:数据访问和Using index 一样,所需数据只需要读取索引即可,而当Query 中使用了GROUP BY 或者DISTINCT 子句的时候,如果分组字段也在索引中,Extra 中的信息就会是Using index for group-by;
10,Using temporary:当MySQL 在某些操作中必须使用临时表的时候,在Extra 信息中就会出现Using temporary 。主要常见于GROUP BY 和ORDER BY 等操作中。
11,Using where:如果我们不是读取表的所有数据,或者不是仅仅通过索引就可以获取所有需要的数据,则会出现Using where 信息;
12,Using where with pushed condition:这是一个仅仅在NDBCluster 存储引擎中才会出现的信息,而且还需要通过打开Condition Pushdown 优化功能才可能会被使用。控制参数为engine_condition_pushdown 。
二:分析MySQL执行明细状态(profile关键字)
SQL语句:
set profiling = 1; //开启执行明细
select * from person; //进行SQL语句
select * from person where id = 1; //进行SQL语句
set profiling = 0; //关闭执行明细
show profiles; //显示执行明细过程中的详细内容
show profile cpu; //显示CPU的执行情况
通过上面的几条语句,就可以得到上图的结果,从这里是不是很明显的就可以知道每条SQL语句所花费的时间了呢?这样就可以看到哪些语句比较花时间,可以重点进行优化操作。总结起来就是:
Query Profiler是MYSQL5.1之后提供的一个很方便的用于诊断Query执行的工具,能够准确的获取一条查询执行过程中的CPU,IO等情况;
1,开启profiling:set profiling=1;
2,执行QUERY,在profiling过程中所有的query都可以记录下来;
3,查看记录的query:show profiles;
4,选择要查看的profile:show profile cpu, block io for query 1 (表示的就是查看第一条query的更详细的信息);
status是执行SQL的详细过程;
Duration:执行的具体时间;
CPU_user:用户CPU时间;
CPU_system:系统CPU时间;
Block_ops_in:IO输入次数;
Block_ops_out:IO输出次数;
profiling只对本次会话有效;
三:Join连接优化
(1),JOIN的原理:
在mysql中使用Nested Loop Join来实现join;
A JOIN B:通过A表的结果集作为循环基础,一条一条的通过结果集中的数据作为过滤条件到下一个表中查询数据,然后合并结果;
(2),JOIN的优化原则:
1,尽可能减少Join 语句中的Nested Loop 的循环总次数,用小结果集驱动大结果集;
注意说的是,结果集,不是说用表的数据大小来进行衡量的,所以,当我们进行join连接的时候,最好能够让join前面的数据集是小的,而后面的结果集为大的,这样可以提高效率。
2,优先优化Nested Loop 的内层循环;
这个与上面类似,就是说,当内层比较复杂的时候,那么就想运用其他的方法来对内层进行优化。比如下面的第三点原则。
3,保证Join 语句中被驱动表上Join 条件字段已经被索引;
4,扩大join buffer的大小;--------因为联表查询,那么中间集是放在内存的缓存的,所以这个大一点是有优势的。
四:SQL优化原则
[原则一:选择需要优化的SQL]
1,选择需要优化的SQL:不是所有的SQL都需要优化,在优化的过程中,首选更需要优化的SQL;
怎么选择?优先选择优化高并发低消耗的SQL;
1,1小时请求1W次,1次10个IO;
2,1小时请求10次,1次1W个IO;
考虑:
1,从单位时间产生的IO总数来说,相同的;
2,针对一个SQL,如果我能把10个IO变成7个IO,一小时减少3W个IO;
针对第二个SQL,如果能把1W个IO变成7K个IO,一小时减少3W个IO;
3,从优化难度上讲,1W->7K难的多;
4,从整体性能上来说,第一个SQL的优化能够极大的提升系统整体的性能;第二个SQL慢一点,无非也就是10个连接查询慢一点;
2,定位性能瓶颈;
1,SQL运行较慢有两个影响原因,IO和CPU,明确性能瓶颈所在;
2,明确优化目标;
[原则二:从Explain和Profile入手]
1,任何SQL的优化,都从Explain语句开始;Explain语句能够得到数据库执行该SQL选择的执行计划;
2,首先明确需要的执行计划,再使用Explain检查;
3,使用profile明确SQL的问题和优化的结果;
[原则三:永远用小结果集驱动大的结果集]
[原则四:在索引中完成排序]
[原则五:使用最小Columns]
1,减少网络传输数据量;
2,特别是需要使用column排序的时候.为什么?MYSQL排序原理,是把所有的column数据全部取出,在排序缓存区排序,再返回结果;如果column数据量大,排序区容量不够的时候,就会使用先column排序,再取数据,再返回的多次请求方式;
[原则六:使用最有效的过滤条件]
1,过多的WHERE条件不一定能够提高访问性能;
2,一定要让where条件使用自己预期的执行计划;
[原则七:避免复杂的JOIN和子查询]
1,复杂的JOIN和子查询,需要锁定过多的资源,MYSQL在大量并发情况下处理锁定性能下降较快;
2,不要过多依赖SQL的功能,把复杂的SQL拆分为简单的SQL;
3,MySQL子查询性能较低,应尽量避免使用;
五:innodb的buffer和事务问题
(1),Innodb_buffer_pool_size:innodb的缓存,可以用于缓存索引,同时还会缓存实际的数据;
innodb_buffer_pool_size 参数用来设置Innodb 最主要的Buffer(Innodb_Buffer_Pool)的大小,对Innodb 整体性能影响也最大,可以按需要设置大一些;
(2),innodb中的事务处理:
1,理解Innodb事务机制:
1,事务在buffer中对数据进行修改;
2,事务的变化记录在事务日志中;
3,在合适的时机同步事务日志中的数据到数据库中;
2,所以什么时候提交事务日志文件,对系统性能影响较大,可以通过设置innodb_flush_log_at_trx_commit来修改事务日志同步时机:
1,innodb_flush_log_at_trx_commit = 0,每1秒钟同步一次事务日志文件;
2,innodb_flush_log_at_trx_commit = 1,默认设置,每一个事务完成之后,同步一次事务日志文件;
3,innodb_flush_log_at_trx_commit = 2,事务完成之后,写到事务日志文件中,等到日志覆盖再同步数据;
注意,设置为0,速度最快,但不够安全mql奔溃会导致上一秒事务丢失;设置为1性能最差,但最安全,设置2不能完全保证数据是写到数据文件中,如果宕机,可能会有数据丢失现象,但性能最高;
六:MySQL的主从配置
(1)主从配置的原因~
所以,针对这个原因,就有如下几条原则:
1,就算MYSQL拆成了多个,也必须分出主和从,所有的写操作都必须要在主MYSQL 上完成;
2,所有的从MYSQL的数据都来自于(同步于)主MYSQL;
3,既然涉及到同步,那一定有延迟;有延迟,就一定可能在读的时候产生脏数据;所以,能够在从MYSQL上进行的读操作,一定对实时性和脏数据有一定容忍度的数据;比如,登陆日志,后台报表,首页统计信息来源;文章;资讯;SNS消息;
4,[一定注意]:在MYSQL主从时,如果一个业务(service中的一个方法)中,如果既有R操作,又有W操作,因为W操作一定要在主MYSQL上,所以在一个事务中所有的数据来源都只能来自于一个MYSQL
七:主从数据同步的原理
针对上面的图,下面给一个详细的文字叙述:
1,要完成主从同步,就必须让在Master上执行的所有的DML和DDL能够正确的在Salve上再执行一遍;MYSQL选择使用文件来记录SQL;
2,要完成主从同步,第一个事情就是把在主服务器上的bin-log(二进制文件)打开,bin-log文件就可以记录在MYSQL上执行的所有的DML+DDL+TCL;
3,MYSQL使用被动注册的方式来让从MYSQL请求同步主MYSQL的binlog;原因:被动请求的方式,主的MYSQL不需要知道有哪些从的MYSQL,我额外添加/去掉从MYSQL服务器,对主MYSQL服务器的正常运行没有任何影响;
4,第二步,从MYSQL后台一个线程发送一个请求,到主服务器请求更新数据;最重要的数据(我这次请求,请求你bin-log的哪一行数据之后的数据)
5,第三步,主MYSQL后台一个线程接收到从MYSQL发送的请求,然后读取bin-log文件中指定的内容,并放在从MYSQL的请求响应中;
6,第四步,从MYSQL的请求带回同步的数据,然后写在从MYSQL中的relay-log(重做日志)中;relay-log中记录的就是从主MYSQL中请求回来的哪些SQL数据;
7,第五步,从MYSQL后台一个线程专门用于从relay-log中读取同步回来的SQL,并写入到从MYSQL中,完成同步;
8,MYSQL的主从同步是经过高度优化的,性能非常高;
八:实现主从数据库的配置
第一步:添加一个新的MYSQL实例
1,复制mysql安装目录;
2,修改新mysql的相关配置(端口,安装目录,数据目录)
3,为新的mysql创建数据目录(从mysql的安装目录中拷贝data文件夹);
4,为新的mysql创建windows系统服务mysqld install MySQL2 --defaults-file="E:\MySQL\mysql_base\ini\my.ini"
5,检查创建的系统服务,并启动测试;
第二步:配置主数据库服务器:
1,因为主数据库之前的内容没有放在binlog中,所以要同步这些数据,只能通过主数据库备份来完成;
2,配置主数据库;
1,server-id://给数据库服务的唯一标识,一般为大家设置服务器Ip的末尾号,在一个集群中,这个id是不能重复的;
2,log-bin=master-bin://开启二进制文件;后面设置的这个master-bin就是二进制文件的名字前缀(名字);
3,log-bin-index=master-bin.index//开启二进制文件的索引;名字一般为log-bin.index
3,启动主数据库,执行show master status命令;
只要能够看到正常的查询结果,说明主服务器已经配置完成;
结果中,注意两个重点:
1,File:二进制文件的文件名;
2,Position:当前文件已经记录到的位置;
第三步:,配置从数据库服务器;
1,server-id;
2,relay-log=slave-relay-bin
3,relay-log-index=slave-relay-bin.index
4,启动从服务器;
5,回复主数据库的备份;-------具体指的就是,因为在配置主的时候,我们把需要进行同步的数据库进行了备份,而要达到从数据库与主数据库同步,那么,首先需要在数据库中创建一个名字与主数据库一样的数据库,然后在把备份的内容,在从数据库进行恢复,这样的话,就能够实现从数据库与主数据库的数据开始的相同。(数据库备份内容恢复应该就不需要讲了吧。这个用数据库客户端(比如Mysql workbench 或者SQLyog 或者Navicat)就一下可以实现的)
6:,让从数据库指定Master库:---------------这一步非常重要
change master to master_host='127.0.0.1', //Master 服务器Ip
master_port=3306,//Master服务器的端口
master_user='root',//Master服务器的账户(其实应该是一个专门用于数据同步的账户)
master_password='admin',//Master服务器的同步账户密码
master_log_file='master-bin.000001',//Master服务器产生的日志------这个是根据主数据库配置完成后,启动服务的时候,就会新增这一个文件,注意,这要进行匹配对应,否则就会有问题
master_log_pos=0;//指定请求同步Master的bin-log的哪一行数据之后的内容;
8,启动从MYSQL;
第四步:这样就实现了两个数据库之间的关系,到这里就配置完成了。
九:实际例子--------演示,如何运用主从数据库来对项目系统进行读写分离(也就是写都在主数据库,而读都在从数据库,千万不可以写在从数据库,因为这样的话,主数据库是不会去同步从数据库中的数据的,那么这样数据就不是一致性的了。具体原因可以参考我上面的知识点)
测试环境:SpringMvc+Spring+Mybatis---------------主要是后台介绍下环境就够了,前端就不管了
需求:在后台的登陆日志中,让登陆日志的查询从从数据库中查询,其他业务还是都先使用主数据库;(这个足够说明读写分离来运用主从数据库的例子了吧。而且Web项目中,这个功能是必不可少的。)
实现原理分析:
原理文字解析:
(1),一个service方法必须定位到一个唯一的数据库上;
(2),引入路由DataSource之后:
1,在应用中,需要自己去确定(告诉路由DS)这次要访问的真实的目标Datasource;
2,让路由DS知道有哪些真实的Datasource和他们对应的名字;
3,让路由DS根据我传入的名字去返回真实的DS;
(3),完成读写分离:
1,认识Spring中的AbstractRountingDataSource;
在Spring中提供了AbstractRountingDataSource来完成路由DS的功能;
1,targetDataSources:用于配置真实的datasource,这个属性是一个Map,Map的key就是DS的名字,Map的value是真实的DS对象;
2,defaultTargetDataSource:如果路由DS没有找到你当前请求的DS,直接使用默认的DS;
3,abstract Object determineCurrentLookupKey():这个方法是需要我们自己实现的,这个方法需要返回一个值,这个值就是DS的名字;这个方法就是让应用(我们)提供给Spring怎么找DS名字的逻辑;
实现步骤:
第一步:在Spring配置文件中,配置需要使用的主从数据库的信息
注意:这里在Spring中,配置了一个properties文件的扫描,然后有数据库配置信息就放里面,所以,这两个数据库信息都是从properties文件中读出来的。
第二步:创建一个继承AbstractRountingDataSource的对象,并在Spring中配置
Spring配置文件添加如下信息:
注意,再 后面的SqlSessionFactory配置中必须使用的datasource必须是路由datasource,而不是主从数据库配置的其中一个,要不然这样就永远只会使用那一个数据库信息了;
第三步:添加设置当前线程需要访问的数据是哪一个(其实这个和第二步是相互套接的,所以顺序可以交换都没关系)
第四步:在service层调用查询登陆日志的方法。
第五步:进行登陆功能的测试即可。(这个没什么复杂的功能)
一:逻辑就是首先登陆系统,然后系统中的登陆日志功能里面就相应的多加了一条登陆信息,这时候可以去看一下主从数据库里面的登陆日志表是不是数据都是同步的(答案肯定是的,会发现数据都添加了进去,保持一致)。
二:在登陆日志功能界面进行日志信息的查询(这就是读操作),就可以查询到所有的登陆日志信息,好了,问题来了,这怎么确认就是以从数据库中读取出来的数据,而不是主数据库里面读的呢?------------很简单,我们明白,从数据库数据改变是不会影响到主数据库的,所以把里面一条数据修改一下呗,然后再查询,如果显示的数据是修改的,那么就说明是从数据库中的读取,否则相反。。。。(如下图所示)
测试结果:从图中的结果可以看出来,我们配置的主从数据库在系统中进行了实质性的应用,这是一个非常好的对缓解系统性能的一个方法,并且越来越多的分布式原理,也是对这个进行了更为深层次的扩展。当然,在上面的代码中,只是一个非常简单的测试,如果想运用到系统中,有一个更好的扩展性的话,那么就将设置使用数据库的类型,通过AOP或者@Annotation是实现对datasource的控制会更有效果哦。。。
上面这些内容就是关于MySQL性能的一些优化了,当然,这只是部分,还有很多可以进行优化的策略,但是说的这些都是比较简单的优化,但是又很实质性作用的方面,其中关于索引,主从配置,这些都是很重要很重要的,所以一定要掌握~!!!!如果我还碰到了什么优化比较好的方法,就会持续进行更新的,欢迎你的阅读~!(别忘记了,前面还有一篇关于MySQL的优化哦~!)