一条SQL的执行过程:
1.客户端发送一条查询给服务器
2.服务器通过权限检查之后,先查询缓存,如果有就立即返回缓存中的结果,否则进入下一步;
3.服务器用解析器进行sql解析,生成解析树,预处理器进行预处理, 再由优化器根据sql所涉及到的数据表的统计信息进行计算,生成对应的执行计划
4.mysql根据优化器生成的执行计划,调用存储引擎API来执行查询;
5.将结果返回给客户端
sql执行最大的瓶颈在于磁盘IO,即数据读取;不同的sql写法,会照成不同的执行计划的执行,而不同的执行计划在IO上将面临不一样的数据量, 从而造成性能差距;所以要选择匹配的执行计划,来减少查询中产生的IO.
schema表结构对性能的影响
三范式说的是:1.数据库表的每一列不可再分,2.每行被唯一区分 3.表中不包含其他表中已包含的非主关键字信息(不允许有冗余数据)
1.适当的冗余能提高查询性能
2.大表拆小表 一个数据库中, 一般不会设计属性过多的表, 不会有超过千万数据的表(按逻辑拆,按业务拆), 有大数据的列单独拆成小表
3.按照需求展示更合理的表结构, 只处理需要处理的列
索引优化
索引是什么:
1.数据库文件存储的位置: my.ini配置文件中dataDir对应的数据目录中, 每一个数据库对应一个文件夹
2.1MYISM引擎:每一个表(table_name)–>
table_name.MYI:存放的是数据表对应的索引信息和索引内容
table_name.FRM:存放的是数据表的结构信息
table_name:MYD:存放的是数据表的内容
2.2InnoDB引擎:每一张表(table_name)–>
table_name:frm:存放的是数据表的结构信息
数据文件和索引文件都是统一放在ibdata文件中;
3.索引文件都是额外存在的,对索引的查询和维护都是需要消耗IO的;
索引的结构:
1.默认的情况下,创建一个表,设置了主键,会自动为这个主键创建一个unique索引
2.索引类型:
2.1.normal普通索引(允许一个索引值后面关联多个行值),
2.2.unique唯一索引(允许一个索引值后面只能有一个行值);
2.3 FullText全文索引(只支持MYISAM引擎,因为性能较低, 一般不使用)
3.索引的实现:
3.1 b-tree(平衡树), 保存数据是按照一定顺序保存数据的, 在innodb中, 主键索引是内容中直接保存数据的地址, 其他索引是索引内容中保存指向主键索引的引用;
3.2 hash,就是把索引值做hash运算,放到hash表中, 一般memory引擎使用,适用于精确值的比较, 比如 = ,in,<>, 当大量索引hash值相同,性能比较低,也无法使用索引排序
4.索引的创建:
4.1.更新太频繁的字段不适合创建索引, 因为索引也有维护成本
4.2.不会出现在where子句中的字段不创建索引
4.3.索引与索引之间是独立的, 增删改查都是单独维护, 一次查询至多也只会采用一个索引, 所以不是越多越好
索引的失效/使用限制/常见问题:
1.BLOB和TEXT类型的列只能创建前缀索引
2.使用 != 或 <> 的时候 mysql无法使用索引
3.过滤字段使用了函数运算,比如abs(), 失效
4.join语句中join条件字段类型不一致的时候, 会失效
5.like操作如果以通配符开始, 会失效(因为字符串可以所为索引,创建索引是按字母顺序排序, 比如 where name like ‘李%’ 这是可以的, 但是 ‘_文’/’%文’ 这样是不可以的)
6.使用非等值查询时, 会失效
复合索引: 多列的值组成的索引, 并且多列的所有是有顺序的, 比如order by name, age;
一个查询至多使用一个索引,如果使用单值索引(一个列一个索引), 在数据量大的情况就不能很好的区分数据, 在实际应用中,基本都是用的符合索引
mysql的执行计划和执行明细状态(explain+profiling)
explain:查看mysql执行一条sql所选择的执行计划;
1,使用方式:
explain SQL;
2,返回结果:
1,ID:执行查询的序列号;
2,select_type:使用的查询类型
1,dependent subquery:子查询中内层的第一个SELECT,依赖于外部查询的结果集;
2,dependent unoin:子查询中的unoin,且为unoin 中从第二个SELECT 开始的后面所有SELECT,同样依赖于外部查询的结果集;
3,primary:子查询中的最外层查询,注意并不是主键查询;
4,simple:除子查询或者UNION 之外的其他查询;
5,subquery:子查询内层查询的第一个SELECT,结果不依赖于外部查询结果集;
6,uncacheable subquery:结果集无法缓存的子查询;
7,union:UNION 语句中第二个SELECT 开始的后面所有SELECT,第一个SELECT 为PRIMARY
8,unoin result:unoin 中的合并结果;
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 信息;
profiling: 用来准确定位一条sql的性能瓶颈, 观察cpu, io等情况;
1.开启profiling: set profiling=1;
2.执行query, 在profiling过程中所有的query都可以记录下来;
3.查看记录的query: show profiles
4.选择要查看的profile: show profile cpu, block io for query 5;
status是执行sql的详细过程:
Duration: 执行的具体实际;
CPU_user: 用户的CPU时间
CPU_system: 系统CPU时间
Block_ops_in:io输入次数
Block_ops_out: io输出次数
sql优化的过程:
1.任何sql的优化,都先从explain语句开始, 它能得到该sql选择的执行计划
2.定位性能瓶颈,使用profile定位
优先选择优化高并发低消耗的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个连接查询慢一点;
3.选择小结果驱动大的结果集 (join)
4.在索引中完成排序 (order by , group by )
5.使用最小columns: 减少网络传输数据量;另外mysql的排序是将索引的column数据全部取出,在排序缓存区排序,再返回结果,如果column数据量特别大,排序区容量不够,返回多次请求方式.
6.让where条件使用自己预期的执行计划;
7.避免复杂的join和子查询,子查询效率较低,把复杂的sql拆分成简单的sql
在mysql中使用Nested Loop Join来实现join;
JOIN的优化原则:
A JOIN B:通过A表的结果集作为循环基础,一条一条的通过结果集中的数据作为过滤条件到下一个表中查询数据,然后合并结果;
1,尽可能减少Join 语句中的Nested Loop 的循环总次数,用小结果集驱动大结果集;
2,优先优化Nested Loop 的内层循环;
3,保证Join 语句中被驱动表上Join 条件字段已经被索引;
4,扩大join buffer的大小;
mysql的预编译:
我们在使用JDBC中的Prepared Statement接口, 就有预编译功能.
预编译分三步:执行预编译语句, 设置变量, 执行语句.
当客户发送一条SQL语句给服务器后,服务器总是需要校验SQL语句的语法格式是否正确,然后把SQL语句编译成可执行的函数,最后才是执行SQL语句.
预编译优化:
默认使用PreparedStatement是不能执行预编译的,这需要在url中给出useServerPrepStmts=true参数
当使用不同的PreparedStatement对象来执行相同的SQL语句时,还是会出现编译两次的现象,这是因为驱动没有缓存编译后的函数key,导致二次编译。如果希望缓存编译后函数的key,那么就要设置cachePrepStmts参数为true。例如:jdbc:mysql://localhost:3306/test?useServerPrepStmts=true&cachePrepStmts=true