mysql索引详解
索引的定义
MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构.可以得出索引的本质就是数据结构
你可以简单理解为"排序好的快速查找数据结构"
在数据之外,数据库还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构的基础上实现高级查找算法,这种数据结构就是索引
一般来说索引本身很大,不适合全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上
我们平常所说的索引,如果没有特别指明,都是指B树(多路搜索树,并不一定是二叉的)结构组织的索引,其中聚集索引,次要索引,覆盖索引
复合索引,前缀索引,唯一索引默认都是使用B+树索引,统称索引.当然,除了B+树这种类型的索引之外,还有哈稀索引(hash index)等
优势
类似大学图书馆建书目录索引,提高数据检索的效率,降低数据库的IO成本
通过索引列对数据进行排序,降低数据排序成本,降低了CPU的消耗
可以加速表和表之间的连接
劣势
实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引也是要占内存空间的
虽然索引大大提高了查询速度,同时都会降低更新表的速度,如对表进行insert,update和delete
因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引的字段,都会调整因为更新所带来的键值变化后的索引信息
索引只是高效的一个因素,如果你的MySQL有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询方法
索引的分类
单值索引:即一个索引只包含单个列,一个表不\可以有多个单列索引
唯一索引:索引列的值必须唯一,但允许有控制,例如手机号,银行卡号等值必须是唯一
复合索引:即一个索引包含多个列,例如手机号和银行卡号一起,如果一个表中的数据在查询时有多个字段总是同时出现则这些字段就可以作为复合索引
基本语法
创建:create [unique] index indexName on tbname(columnname(lenght))
更改:alter table tbname add [unique] index [indexName] on (columnname(lenght))
更改:alter table tbname add [unique] fulltext [indexName] on (columnname(lenght))#指定索引为fulltext,用于全文索引
删除:drop index [indexName] on tbname;
查看:show index from tbname\G;
索引结构
BTree索引
Hash索引
full-text全文索引
R-Tree索引
哪种情况需要创建索引
1.主键自动建立唯一索引
2.频繁作为查询条件的字段应该创建索引
3.查询中与其他表关联的字段,外键关系建立索引
4.频繁更新的字段不适合创建索引,因为每次更新不单单是更新数据还会更新索引
5.Where条件里用得到的字段适合创建索引
6.单键/组合索引的选择问题,在高并发下倾向创建组合索引
7.查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
8.查询中统计或者分组字段
哪种情况不需要建索引
1.表记录太少(一般生产环境下,三百万条记录性能就可能开始下降,官方说的是五百万到八百万)
2.经常增删改的表
3.某个数据列的值包含许多重复的内容
性能分析
1.MySQL Query Optimizer(查询优化器)
1.1MySQL中有专门负责优化select语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的query提供它认为最有的执行计划(它认为最优的数据检索方式,但不见得是DBA认为是最优的,这部分是最耗时间的)
1.2当客户端向MySQL请求一条query,命令解析其模块完成请求分类,区别是select并转发给MySQL Query Optimizer时,MySQL Query Optimizer首先会对整条Query进行优化,处理掉一些常量表达式的预算,直接转换成常量值,并对query中的查询条件进行简化和转换,如去掉一些无用或显而易见的条件,结构调整等,然后分析query中的Hint信息(如果有),看显示Hint信息是否可以完全确定该query的执行计划,如果没有Hint或Hint信息不足以完全确定执行计划,则会读取所涉及对象的统计信息,根据query进行写相应的计算分析,然后再得出最后的执行计划。
2.MySQL常见瓶颈
CPU:CPU在饱和的时候一般发生在数据装入内存或从磁盘读取数据时候
IO:磁盘IO瓶颈发生在装入数据远大于内存容量的时候
服务器硬件的性能瓶颈,top,free,iostat和vmstat来查看系统的性能状态
3.Explain
3.1怎么用
explain+SQL语句
执行计划包含以下的信息
id | select_type | table | type | possible_keys | key | key_len | ref | rows | extra |
列 | 名称 | 含义 |
---|---|---|
id |
select_id |
该 SELECT 标识符 |
select_type |
none | 该 SELECT 类型 |
table |
table_name |
输出行表 |
partitions |
partitions |
匹配的分区 |
type |
access_type |
联接类型 |
possible_keys |
possible_keys |
可能的索引选择 |
key |
key |
实际选择的索引 |
key_len |
key_length |
所选键的长度 |
ref |
ref |
与索引比较的列 |
rows |
rows |
估计要检查的行 |
filtered |
filtered |
按表条件过滤的行百分比 |
Extra |
none | 附加信息 |
id id代表查询标识符。 select_type select_type代表查询类型 table table代表每个查询包含子查询 partitions partitions代表分区表中的命中情况,非分区表,该项为null type type表示查询方式的好坏。 从好到差依次为:system,const,eq_ref,ref,fulltext,ref_or_null,index_merge,unique_subquery,index_subquery,range,index,ALL system:The table has only one row (= system table)表中只有一行数据或者是空表。 const:使用唯一索引或者主键,返回记录一定是1行记录的等值where条件时,通常type是const。其他数据库也叫做唯一索引扫描。 eq_ref:出现在连表语句中,驱动表的连接字段是逐渐或唯一性索引,且必须为not null,唯一索引和主键是多列时,只有所有的列都用作比较时才会出现eq_ref。 ref:ref没有eq_ref严格,没有要求连接表的顺序,也不要求包含主键或唯一性索引,常见与普通索引的连表中。 fulltext:全文索引检索,要注意,全文索引的优先级很高,若全文索引和普通索引同时存在时,mysql不管代价,优先选择使用全文索引。 ref_or_null:与ref方法类似,只是增加了null值的比较。实际用的不多。 index_merge:表示查询使用了两个以上的索引,最后取交集或者并集,常见and ,or的条件使用了不同的索引,官方排序这个在ref_or_null之后,但是实际上由于要读取所有索引,性能可能大部分时间都不如range。 unique_subquery:用于where中的in形式子查询,子查询返回不重复值唯一值。形如:value IN (SELECT primary_key FROM single_table WHERE some_expr) index_subquery:用于in形式子查询使用到了辅助索引或者in常数列表,子查询可能返回重复值,可以使用索引将子查询去重。形如:value IN (SELECT key_column FROM single_table WHERE some_expr) range:索引范围扫描,常见于使用 =, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, IN()或者like等运算符的查询中。 index:索引全表扫描,把索引从头到尾扫一遍,常见于使用索引列就可以处理不需要读取数据文件的查询、可以使用索引排序或者分组的查询。 all:这个就是全表扫描数据文件,然后再在server层进行过滤返回符合要求的记录。 possible_keys 查询可能使用到的索引都会在这里列出来 key 查询真正使用到的索引,select_type为index_merge时,这里可能出现两个以上的索引,其他的select_type这里只会出现一个。 key_len key_len说明MySQL会用到的索引长度,单列索引用到整个索引的长度,index_merge中算出具体用到的索引长度,该字段只会算where条件用用到索引,order by和group by不在统计范围内。 ref 如果是使用的常数等值查询,这里会显示const,如果是连接查询,被驱动表的执行计划这里会显示驱动表的关联字段,如果是条件使用了表达式或者函数,或者条件列发生了内部隐式转换,这里可能显示为func。 rows 这里是执行计划中估算的扫描行数,不是精确值。 filtered 5.7之后的版本默认就有这个字段,不需要使用explain extended了。这个字段表示存储引擎返回的数据在server层过滤后,剩下多少满足查询的记录数量的比例,注意是百分比,不是具体记录数。 extra 查询计划器给出额外的信息说明。 you want to make your queries as fast as possible, look out for Extra column values of Using filesort and Using temporary, or, in JSON-formatted EXPLAIN output, for using_filesort and using_temporary_table properties equal to true. extra辅助信息中的using filesort和using temporary,这两项非常消耗性能。
filesort:排序时无法使用到索引时,就会出现这个。常见于order by和group by语句中
temporary:表示使用了临时表存储中间结果。临时表可以是内存临时表和磁盘临时表,执行计划中看不出来,需要查看status变量,used_tmp_table。
导致索引失效的案例
1.建什么索引用什么索引,顺序也最好保持一致
2.最佳左前缀索引名称命名(如字段name,age,city,则索引命名应该是nameAgeCity或者xxx_nameAgeCity,顺序很重要)
3.不在索引列上做任何操作(计算,函数,or,类型转换),会导致索引失效而转向全表扫描
4.存储引擎不能使用索引中范围条件右边的列(如name='lin' and age>25 and city='qingdao',则age后面的索引会实效)
5.尽量使用覆盖索引(只访问索引的查询(索引列和要查询的列一致)),减少select *
6.MySQL在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描
7.is null,is not null 也无法使用索引
8.like以通配符在这('%abc','%abc%')两种情况会索引实效变成全表扫描,'abc%'则不会,若要'%abc','%abc%'不失效,建议使用覆盖索引,且查询的字段要少于索引或者与索引一致,不使用select *。如为name,age,city建了索引,请这么使用:select name或者select age,或者select city或者select name,age,city。如果select name,age,city,email则会全表扫描
9.字符串不加引号索引失效,
10.少用or,用他来连接时索引会失效
11.select * from A where exists (select 1 from where b.id=A.id)#当A表的数据系小于B表时,用exists优于in
12.使用join代替子查询
数据类型选择
1.数字类型
Float和double选择(尽量选择float)
区分开TINYINT / INT / BIGINT,能确定不会使用负数的字段,建议添加 unsigned定义
能够用数字类型的字段尽量选择数字类型而不用字符串类型的
2.字符类型
char,varchar,TEXT的选择:非万不得已不要使用 TEXT 数据类型,定长字段,建议使用 CHAR 类型(填空格),不定长字段尽量使用 VARCHAR(自动适应长度,超过阶段),且仅仅设定适当的最大长度
3.时间类型
按选择优先级排序DATE(精确到天)、TIMESTAMP、DATETIME(精确到时间)
4.ENUM
对于状态字段,可以尝试使用 ENUM 来存放
5.避免使用NULL字段
很难查询优化且占用额外索引空间
字符编码
同样的内容使用不同字符集表示所占用的空间大小会有较大的差异,所以通过使用合适的字符集,可以帮助我们尽可能减少数据量,进而减少IO操作次数。
1.纯拉丁字符能表示的内容,选择 latin1 字符编码
2.中文可选用utf-8
3.MySQL的数据类型可以精确到字段,所以当我们需要大型数据库中存放多字节数据的时候,可以通过对不同表不同字段使用不同的数据类型来较大程度减小数据存储量,进而降低 IO 操作次数并提高缓存命中率
order by优化
重点:使用order by就需要看会不会产生filesort
mysql支持两种方式排序,index和filesort,index效率高
尽量使用Index方式排序,避免使用FileSort方式排序
尽可能在索引列上完成排序操作,遵照索引列的最佳左前缀
如果不在索引列上,filesort有两种算法:双路排序和单路排序
双路排序:取一堆数据,要对磁盘进行两次扫描(mysql4.1之前,后来的版本增加了单路排序)
单路排序:从磁盘读取查询需要的所有列,按照order by列在buffer对他们进行排序,然后扫描排序后的列表进行输出,效率快,但会使用更多的内存空间,因此注意调整sort_buffer_size和max_lenght_for_sort_data参数值(增大值),避免创建tmp文件从而进行多次I/O
group by优化
和order by差不多
先排序后分组,遵照索引列的最佳左前缀
当无法使用索引列时,增大max_lenght_for_sort_data和sort_buffer_size的值
where高于having,能在where限定的条件就不要去having限定了
优化总结
1.开启慢查询日志
2.expain+慢SQL分析
3.show profile查询sql在mysql服务器里面的执行细节和生命周期情况
4.sql数据库服务器的参数调优