高性能MySql
1、索引是对DB优化最有效的方式
varchar(10)定义的是字符的个数,如果是utf-8的话,最大是3X10个字节
二、索引类型
1、MySql的索引是在存储引擎层实现的,各个存储引擎的的索引方式也是不同的
2、B-Tree索引
MyISAM索引通过数据的物理位置引用被索引的行(数据存储位置变化时需要更新索引),INNODB则根据主键引用被索引的行(没有主键默则根据默认的策略生成主键)。
B+树是平衡树
聚簇索引和非聚簇索引的分析:http://www.cnblogs.com/Arlen/articles/1605626.html
主键索引是聚簇索引,二级索引是非聚簇索引。
INNODB索引的使用
1、全值匹配
2、匹配最左前缀
3、匹配列前缀
4、匹配范围值
5、精确匹配某一列并范围匹配另一列
6、索引覆盖
7、因为索引树的节点是有序的,所以除了按值查找之外,索引还能用于order by和group by操作。
一般,如果B树可以按照某种方式找到值,那么也能按照这种方式排序。
限制:
1、必须匹配最左前缀
2、不能跳过索引中的列
3、如果某个列使用了范围查询,则其右边的所有列都有不能使用索引了。
4、order by 要么都是升序,要么都是降序
5、如果是多个表,只有全部是第一个表才能使用
3、Hash 索引
在MySql里,只有Memory引擎显式的支持hash索引。
哈希索引自身只存储对应的哈希值和行索引,而不存储字段值,所以不能使用索引的值来避免读取行。
哈希不支持部分索引列的查找,只支持全部匹配
哈希索引只支持列的全值匹配,只支持等值比较查询。
INNODB里面内置了自适应hash索引
4、全文索引
5、空间数据索引
6、在INNODB里面模拟hash索引来优化
如url列的内容很大,创建的索引占用的磁盘块数也很大。如果按照url列来等值查找数据(where url=www.yunzhu.com),则下面的优化方法较好:
加一列hash_url,该列存储的是url的MD5值。同时对hash_url创建索引,然后这样查找:where url=www.yunzhu.com and
hash_url=MD5(www.yunzhu.com)
则mysql会优先走hash_url索引,找到对应的行然后比较url是否相同。
原理:hash_url索引占的磁盘块数很小,而且是是整数比较(比字符串比较快很多),所以会很快。
三、高性能索引策略
1、不是独立的列(索引列是表达式或者函数的入参)
2、前缀索引(原理:给索引瘦身)
有时候某个列值是text或者varchar类型,而且值很大。这个时候如果直接对该列创建索引,磁盘块数将会很大,可以采取前缀索引进行优化。
前缀索引:只对某个列的前几位做索引,而不是所有位。无法使用前缀索引进行order by和group by
创建前缀索引语法:alter table mytable add key city(n) 则创建的索引对对city的前n个字符创建索引。
如何选择n
实验找出选择性较高的n。
不能进行索引排序
3、多列索引
问题:index_merge
在每个列上都创建索引,并不能提高mysql的查询性能。
当出现服务器对多个索引做相交操作的时候(多个and条件),通常意味着需要一个包含所有相关列的索引,而不是多个独立的单列索引。
当服务器出现对多个索引做相交操作的时候(多个or条件),需要消耗大量的CPU资源去重,排序等操作上。
4、选择合适的索引列顺序(下面的两种条件都需要综合考虑)
1、在不考虑排序和分组的时候,将选择性较高的列放在前面通常是很好的。
考虑下面的一种极端的情况:
虽然列A的选择性很高,但是A的列有个值B重复率很大,如果where A=B来查询的话,则效率会很低。此时应该由上层的程序来控制不走这个索引。
极端的情况下B值的重复lv很高,高到和不走索引是一样的。
2、需要根据运行频率最高的查询来调整索引列的顺序。
三、聚簇索引
不是一种索引方式而是一种存储方式,代表主键和数据紧凑的存储在一起。思考下聚簇索引的优点和缺点:
优点:1、覆盖索引查询可以直接使用主键值
2、因为索引和数据存储在同一个树上,因此查找快。
3、聚簇
缺点:1、主键需要递增,否则会造成页分裂和碎片等。
MySql的数据文件就是索引文件,非叶子节点存储的是主键,叶子节点存储了全部的数据。
非聚簇索引也叫辅助索引。查询的时候一般是先根据辅助索引查询到主键之后再根据聚簇索引查询到所有的数据行。
如果没有定义主键,INNODB会选择一个唯一的非空索引代替。如果没有这样的索引,INNODB会隐式的定义一个主键作为聚簇索引。
聚簇索引的插入速度严重依赖插入顺序。
更新聚簇索引的成本好高,因为需要将索引移到新的位置。
基于聚簇索引的表在插入新行,或者主键被更新导致需要移动行的时候,可能导致列分裂的问题。
使用optimize table命令来重建表并进行优化页的填充。
INNODB应该尽可能的按照主键的顺序插入数据,并且尽可能的使用单调增加的聚簇值来插入新行,避免更新主键值。 (否则需要移动聚簇索引,同时造成页分裂,造成碎
片)。
四、索引覆盖
如果二级索引能够覆盖查询,则可以避免对主键索引的二次查询
在发起一个覆盖查询的时候,explain的extrea列可以看到using index的信息
很多的查询语句可以通过部分走索引覆盖而进行优化。
可以通过扩展索引来实现索引覆盖从而进行优化
五、使用索引排序
如果explain的type值为index,则说明mysql可能使用了索引扫描来排序。
MySql可以使用同一个索引既满足排序,也能用于查找行。
只有当索引的列顺序和order by自居的顺序完全一致,并且所有的列的排序方向都一致时,MySql才能使用索引来对结果进行排序。
如果查询需要关联多个表,只有当order by子句引用的字段全部为第一个表时(优化执行器选择执行的第一个表,不是sql的第一个select表),才能使用索引做排序。
order by子句和查找型查询的限制是一样的:需要满足索引的最左前缀要求,否则mysql都需要执行排序操作,而无法利用索引排序。
六、 kengyu索引
有时候坑与索引能在一定的程度上优化查询。
七、连接查询
Inner Join
Natural Join
Left Outer Join
Right Outer Join
Full Outer Join
Cross Join
八、查询性能优化
1、 主要是两个方面进行性能优化
1、客户端到服务器端之间的性能优化(减少数据包的传输等)
1、客户端是否请求了过多的行和列(列少的话可能会走覆盖索引,同时能减小数据包的大小)
2、客户端可以先查缓存减少查db的次数
2、mysql服务器端和存储引擎端的优化
主要是根据3个指标
1、响应时间(请求锁时间,排队时间,IO时间)
2、扫描的行数和返回的行数(一般这个比例是10:1甚至更大)
2、where条件的利用好坏,从好到坏排序
1、where用于索引
2、服务器层做过滤
3、切分查询(讲一个大的查询切分成小的)
为什么切分查询
1、现在的网络带宽比较大,所以将一个查询切分成多个是可行的
2、一次执行大量数据的删除和查询,会一次占用很多的资源和锁,给DB造成很大的压力。这样做可以将DB的压力分散到不同的时间。
3、将一个大的查询(如:连接查询)切分成小的查询能更好的利用缓存和操作缓存。
4、在应用层做关联,可以更好的对 数据库进行切分,更容易做到高性能和可扩展。
5、在应用层做关联查询,在一个事务中,可以把某个请求结果存储起来,然后再找个事务的执行过程中就不断的重复去DB查了,这样就减少了请求DB的次数。
如:如果直接请求mysql执行一个个大sql,返回的结果是没有子查询的结果的,如果下次需要子查询的结果还需要如DB查。
4、客户端/服务器端的通信协议
1、半双工,这导致两方对通讯过程不能有很好的控制能力,在发送出去报文之后就只能干等着了。
2、客户端用一个单独的数据包将查询发送给服务器。max_allowed_packet就规定了这个数据包的大小,客户端发送完请求之后只能等着了。
3、服务器端的响应由多个数据包组成,客户端只有接收到完整的全部结果之后,服务器端才会释放资源。
4、客户端默认是先缓存全部的结果集,使得mysql服务器端能尽快的释放掉资源。客户端可以通过参数决定自己是不是缓存全部的结果集。jdbc也有api能设置。
5、服务器得到第一条数据的时候就可以返回数据了,这样客户端也可以同时处理数据了。服务器不是先查询到所有的数据然后才返回数据,是查询到第一条有效数据就返回数
据,这样可以避免即使服务器端需要返回大量的数据,也不会占用大量的内存。mysql会针对每一行使用通讯协议进行包装然后写到socket,当然tcp可能会把多行以一个批次传输。问题:客户端可以指定是否缓存服务器端的数据,那么这个时候服务器端返回数据是不是就
受到限制了?
SQL_BUFFER_RESULT forces the result to be put into a temporary table. This helps MySQL free the table locks early and helps in cases where it takes a long time to send the result set to the client. This option can be used only for top-level SELECT statements, not for subqueries or following UNION. 这样能尽快的释放锁,但是会占用mysql的大量内存和资源 |
九、其他
1、mysql在生成执行计划的时候,5.6版本之前如果一个语句里面含有子查询时,子查询是需要执行的,但是5.6解除了这个限制。
2、当扫描大量的行数时,可以采取下面的措施(p201)
1、使用索引覆盖
2、使用汇总表
3、重写查询
3、查看各个连接的状态
show full processlist
在command列能看到各个查询现在的状态
1、sleep
2、query
3、locked
4、analysing and statistics
5、copying to tmp table[on disk]
group by,sort,union的时候会使用临时文件
6、sorting result
7、sending data
十、explain和explain extend
explain extand能额外的查到mysql执行优化器优化之后的sql语句
1、type(访问类型)
all 聚簇索引去扫描整个表(limit时不会扫描整个表),此时磁盘是顺序访问的。
1、只有聚簇索引 2、不能走索引覆盖
index 索引扫描整个表(使用索引排序的时候会走)
1、extra列里面有using index时只是磁盘顺序读取所有的索引,并不会再根据聚簇索引去磁盘随机读取其他列的数据
2、extra列里面没有using index时,此时还需要根据聚簇索引去随机读取其他列,比较好性能。
range
使用索引范围查询
IN,OR也是显示的range,但是和正常的>这种范围查询有区别
ref(非唯一性索引或者使用索引前缀查询)
索引等值查询,但是可能查询多多行
eq_ref(唯一性索引,例如主键id)
索引等值查询,但是只可能查询到一条语句。
const,system
不用执行能根据SQL和mysql统计信息就能得到结果,如:select id from XXX where id=1;
NULL
不用执行就能得到结果
分析:all和index?
all和index都是扫表。当是索引覆盖查询,或者能利用索引排序的时候才会走
2、rows
大概显示会扫描到多少行(如果索引覆盖查询的话,一个索引也算一行,所以这里rows指的不是扫描行同时得到所有的列)
rows也不能表明limit,如select * from XX limit 1,实际上mysql只会查询扫描一列就结束。
3、key_len(所使用索引的最大程度)
索引的长度,能根据这个值确定最终sql在联合索引里面到底使用了前几个列走索引
4、possible_keys
可能走的索引,具体的例子见案例分析的第6个
5、key
优化器从possible_keys里最终选择的索引(根据IO次数选择合适的,具体的例子见案例分析的第6个)
6、id
根据sql里面的id的顺序由1递增排列
7、explain的执行结果顺序
explain的执行结果里面的id可能不是递增的,explain显示的列的顺序就是mysql的执行顺序。
8、select_type
1、simple
2、SUBQUERY(DEPENDENT_SUBQUERY)
select (select XX from XX) from XX
括号里面的是SUBQUERY
3、UNION
4、DERIVED
from (select XXX from XXX) 括号里面的是DERIVED
5、PRIMARY
最外层循环
9、table
1、DERIVED
2、真实表
3、UNION之后的表
<UNION 1,2>
10、extra
using index (说明只使用该索引,不用再根据索引获得的id去查询聚簇索引获得数据,也就是索引覆盖)
,using where (不带where的也可能出现using where,说明查询可收益于不同的索引)
using tempory(临时表)
using filesore(可能是在内存或者磁盘进行排序)
十一、案例分析
1、status命令
能查看mysql的版本号和服务器的运行快照信息,以及mysql客户端的信息
2、show create table;
查看建表语句
3、索引覆盖案例
表结构:
CREATE TABLE `test` ( `id` int(11) NOT NULL, `name` varchar(10) DEFAULT NULL, PRIMARY KEY (`id`), KEY `name` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
select * from test;
执行结果如下:
可以看出执行优化器选择了索引覆盖查询。
注意:任何的索引默认主键都是索引的一部分
当sql没有指定order by的时候,查询出的结果是无序的。
select * from test order by id;
执行结果如下:
可以看出优化器选择了使用索引排序
4、聚簇索引和不是聚簇索引的选择
select count(*) from test;
可以看出count(*)也是扫描整个索引
count(*)因为只用统计数据,肯定走索引覆盖(聚簇索引或者非聚簇索引)。
selct count(*)和select a,b基本类似,但是count(*)一般可以走索引覆盖,extra 肯定包含using index。
CREATE TABLE `AA` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `gmt_create` datetime NOT NULL COMMENT '创建时间', `gmt_modified` datetime NOT NULL COMMENT '修改时间', `appkey` varchar(32) NOT NULL COMMENT , `std_api_id` varchar(64) NOT NULL COMMENT , `cfg_h_group` varchar(128) NOT NULL COMMENT , `cfg_key` varchar(128) NOT NULL COMMENT , `cfg_value` varchar(300) NOT NULL COMMENT '配置项value', `creator` varchar(32) NOT NULL COMMENT '创建人', `modifier` varchar(32) NOT NULL COMMENT '修改人', `cfg_v_group` varchar(32) NOT NULL COMMENT , PRIMARY KEY (`id`), KEY `index_appkey_std_api_id` (`appkey`,`std_api_id`), KEY `index_appkey_h_group` (`appkey`,`cfg_h_group`) ) ENGINE=InnoDB AUTO_INCREMENT=490004 DEFAULT CHARSET=utf8 COMMENT=''
explain select count(*) from AA
该表存在聚簇索引还有两个联合索引,因为肯定要全部扫描所有的行(但不一定是所有的列),优化执行器会比较怎么扫描读的磁盘块数(IO数)更少。
1、聚簇索引:叶子部分存储的是全部的列,所以pass。所以一般count(*)都不会走聚簇索引扫描
2、联合索引 选择联合索引数据量较小的,所以最后优化执行器选择了下面的执行计划
1
|
|
1 |
2
|
|
SIMPLE |
3
|
|
AA |
4
|
|
index |
5
|
|
6
|
|
index_appkey_std_api_id |
7
|
|
292 |
8
|
|
9
|
|
61181 |
10
|
|
Using index |
6、索引的选择
explain select count(*) from AA where appkey='aa';
从执行计划可以看出,优化执行器锁定两个索引(从possible_keys上看出),但是两个联合索引都只能选择使用第一列,所以在两个联合索引中选择索引占的数据较小的那个索
引,最终生成了下面的执行计划
1
|
|
1 |
2
|
|
SIMPLE |
3
|
|
AA |
4
|
|
ref |
5
|
|
index_appkey_std_api_id,index_appkey_h_group |
6
|
|
index_appkey_std_api_id |
7
|
|
98 |
8
|
|
const |
9
|
|
1 |
10
|
|
Using where; Using index |
7、 select (select XX from XX) from XX类型分析
返回的行数? 等于select count(*) from XX;
select (select id from s) from test;
本质上是个循环嵌套查询
8、select常量
返回多少行数? select count(*) from XX;
从执行计划可以看出,这种sql肯定走索引覆盖(using index,因为查的是常量)。分析这种sql时和普通的sql一样对待,只是查的列比较特殊,肯定走索引覆盖
9、连接查询(只支持左外连接,右外连接,内连接(等值连接))
虽然没有 join 关键字,但是下面的查询也是连接查询。
下面的查询没有where语句,实际是得到一个笛卡尔乘积,执行引擎在执行的时候是递归执行,随意所以外层循环的数量尽可能少,所以查执行table s,然后再执行table
test。table S只有主键,所以type是all。table test可以进行索引覆盖,所以type是index。
10、虽然select可能是全部数据,或者部分数据但是联合索引能做到覆盖查询,而且可以使用索引排序。
11、IN查询
注意:using where
12、derived深入分析
1、生成derived表的查询是可以走索引的
2、primary对derived的查询时不能走索引的,只能走where。
十二、具体的优化
1、limit分页
limit 10000,10,此时mysql需要读取很多的行去获得偏移量为10000的数据,但是之前读取过的10000个数据是无用的。
方案1:
select film_id,description from sakila.film order by title limit 50,5
可以看出需要全表扫描,然后再根据title排序,然后在取偏移量为50的5条数据。性能弱爆了。
优化成:select film.film_id,film.dscription from sakila.film inner join (select film_id from sakila.film order by title limit 50,5)as lim using(fim_id);
mysql虽然查的前50个没有用,但是因为using index,只是浪费了50次查一个索引,没有查到一个索引得到主键再去查所有的数据。这里实际上用了延迟技术。
方案2:
想办法使得limit A,B中的A变小
2、UNION
UNION肯定会使用临时表存放下来然后再发送数据给客户端,而实际上是可以直接发送数据给客户端的。
一般尽量使用UNION ALL,否则mysql需要对UNION的结果排除重复的
3、最大值最小值
优化方法:因为最大值和最小值只可能是一个值,所以可以使用limit1来进行优化。
极端的情况下会进行扫表然后才得到一个最大或者最小值,通过改写SQL能改变这种情况。
查索引上的最大值和最小值时,可以直接得到。
4、优化count查询
1、count(A),其中A可以是表达式,可以是列,也可以是*。*代表查询全部的行数,列代表查询非NULL值的个数,表达式代表有值的个数。
2、MYISAM在查询不带where条件的count(*)时是const类型,但是如果带where或者count(A)不能转换成count(*)和其他的引擎的执行一样。
在MYISAM:select count(*) from a where id>5;则执行时会扫描很多的行数。可以优化成:
select count(*) from a - select count(*) from a where id<=5;
3、使用近似值,汇总表等
5、优化连接查询
1、A和B连接时,只需要在一个表上建立索引就可以了。
2、确保group by 和order by 中的表达式只涉及到一个表中的列,只有这样mysql才能使用索引优化这个过程
6、优化group by和distinct
当没有办法使用索引的时候,mysql会使用临时表或者文件排序来分组。
使用分组的时候select的列尽量是和group by 有关的字段。
十三、其他
1、建议执行优化器
SE INDEX
在你查询语句中表名的后面,添加 USE INDEX 来提供你希望 MySQ 去参考的索引列
表,就可以让 MySQL 不再考虑其他可用的索引。
Eg:SELECT * FROM mytable USE INDEX (mod_time, name) ...
IGNORE INDEX
如果你只是单纯的想让 MySQL 忽略一个或者多个索引,可以使用 IGNORE INDEX 作
为 Hint。
Eg:SELECT * FROM mytale IGNORE INDEX (priority) ...
FORCE INDEX
为强制 MySQL 使用一个特定的索引,可在查询中使用 FORCE INDEX 作为 Hint。
Eg:SELECT * FROM mytable FORCE INDEX (mod_time) ...