做为免费又高效的数据库,mysql基本是首选。良好的安全链接,自带查询解析、sql语句优化,使用读写锁(细化到行)、事物隔离和多版本并发控制提升并发,完备的事务日志记录,强大的存储引擎提供高效查询(表记录可达百万级),若是是InnoDB,还可在崩溃后进行完整的恢复,优势很是多。即便有这么多优势,仍依赖人去作点优化,看书后写个总结巩固下,有错请指正。mysql
完整的mysql优化须要很深的功底,大公司甚至有专门写mysql内核的,sql优化攻城狮,mysql服务器的优化,各类参数常量设定,查询语句优化,主从复制,软硬件升级,容灾备份,sql编程,须要的不是一星半点的知识与时间来掌握,做为一名像俺这样的菜鸟开发,强吃这么多消化不了也没意义:没地儿用啊,何况还有运维和dba,还不如把手头的业务写好,也就是写好点的sql,并且不少sql语句优化跟索引仍是有很大关系的。sql
首先,mysql的查询流程大体是:mysql客户端经过协议与mysql服务器创建链接,发送查询语句,先检查查询缓存,若是命中,直接返回结果,不然进行语句解析,有一系列预处理,好比检查语句是否写正确了,而后是查询优化(好比是否使用索引扫描,若是是一个不可能的条件,则提早终止),生成查询计划,而后查询引擎启动,开始执行查询,从底层存储引擎调用API获取数据,最后返回给客户端。怎么存数据、怎么取数据,都与存储引擎有关。而后,mysql默认使用的BTREE索引,而且一个大方向是,不管怎么折腾sql,至少在目前来讲,mysql最多只用到表中的一个索引。数据库
mysql经过存储引擎取数据,天然跟存储引擎有很大关系,不一样的存储引擎索引也不同,如MyISAM的全文索引,即使索引叫一个名字内部组织方式也不尽相同,最经常使用的固然就是InnoDB了(还有彻底兼容mysql的MariaDB,它的默引擎是XtraDB,跟InnoDB很像),这里写的是InnoDB引擎。而索引的实现也跟存储引擎,按照实现方式分,InnoDB的索引目前只有两种:BTREE索引和HASH索引。一般咱们说的索引不出意外指的就是B树索引,InnoDB的BTREE索引,实际是用B+树实现的,由于在查看表索引时,mysql一概打印BTREE,因此简称为B树索引。至于B树与B+树的区别,原谅的俺数据结构没好好学,也是须要补的地方。编程
使用了BTREE索引,意味着全部的索引是按顺序排列存储的(升序),mysql就是这么干的,mysl中的BTREE索引抽象结构以下图(参考高性能mysql)。缓存
结构中,每一层节点均从左往右从小到大排列,key1 < key2 < ... < keyN,对于小于key1或在[key1,key2)或其余的值的节点,在进入叶子节点查找,是一个范围分布,同时,同一层节点之间可直接访问,由于他们之间有指针指向联系(MyISAM的BTREE索引没有)。每次搜索是一个区间搜索,有的话就找到了,没有的话就是空。索引能加快访问速度,由于有了它无需全表扫描数据(不老是这样),根据查找的值,跟节点中的值比较,一般使用二分查找,对于排好序的数值来讲,平均速度几乎是最快的。安全
val指向了哪里,对于InnoDB,它指向的就是表数据,由于InnoDB的表数据自己就是索引文件,这是与MyISAM索引的显著区别,MyISAM的索引指向的是表数据的地址(val指向的是相似于0x7DFF..之类)。好比对于InnoDB一个主键索引来讲,多是这样服务器
InnoDB的索引节点val值直接指向表数据,即它的叶子节点就是表数据,它们连在一块儿,表记录行没有再单独放在其余地方,叶子节点(数据)之间可访问。mysql优化
前面在BTREE的抽象结构中,索引值的节点是放在页中的,这里有两个需注意的问题:数据结构
1. 叶子页、页中的值(上上图),即所谓的页是啥,俺加了个节点注释,即这里的页最小可近似当作是单个节点。咱们知道计算机的存储空间是一块一块的,一般一块用完了再用另外一块,若是上一块只剩余5kb,但这里恰好要申请8kb的空间,就得在一个新的块上申请这个空间,而后之后的申请又接在这个8kb后面,只要这个块的空间足够,那么上一块的5kb一般就成了所谓的“碎片”,电脑用多了会有不少这样零散的碎片空间,所以有碎片整理。在mysql中,这里的页可理解为块存储空间,即索引的树节点是存放在页中的,每一页(称为逻辑页)有固定大小,InnoDB目前是16kb,一页用完了,当继续插入表生成新的索引节点时,就去新的页中存储这个节点,再有新的节点就继续放在这个新的页的节点后面。并发
2. 页分裂问题,一页总要被存满,而后新开一页继续,这种行为被称做页分裂。什么时候开辟新的页,mysql规定了一个分裂因子,达到页存储空间的15/16则存到下一页。页分裂的存在可能极大影响性能维护索引的性能。一般提倡的是,设定一个无心义的整数自增索引,有利于索引存储
若是非自增或不是整数索引,如非自增整数、相似MD5的字符串,以他们做为索引值时,由于待插入的下一条数据的值不必定比上一条大,甚至比当前页全部值都小,须要跑到前几页去比较而找到合适位置,InnoDB没法简单的把新行插入到上一行后面,而找到并插入索引后,可能致使该页达到分裂因子阀值,须要页分裂,进一步致使后面全部的索引页的分裂和排序,数据量小也许没什么问题,数据量大的话可能会浪费大量时间,产生许多碎片。
主键老是惟一且非空,InnoDB自动对它创建了索引(primary key),对于非主键字段上创建的索引,又称辅助索引,索引排列也是顺序排列,只是它还附带一个本条记录的主键值的数据域,不是指向本数据行的指针,在使用辅助索引查找时,先找到对应这一列的索引值,再根据索引节点上的另外一个数据域---主键值,来查找该行记录,即每次查找实际通过查找了两次。额外的数据域存储主键值的好处是,当页分裂发生时,无需修改数据域的值,由于即便页分裂,该行的主键值是不变的,而地址就变了。好比name字段的索引简示以下
包含一列的索引称为单列索引,多列的称为复合索引,由于BTREE索引是顺序排列的,因此比较适合范围查询,可是在复合索引中,还应注意列数目、列的顺序以及前面范围查询的列对后边列的影响。
好比有这样一张表
create table staffs( id int primary key auto_increment, name varchar(24) not null default '' comment '姓名', age int not null default 0 comment '年龄', pos varchar(20) not null default '' comment '职位', add_time timestamp not null default current_timestamp comment '入职时间' ) charset utf8 comment '员工记录表';
添加三列的复合索引
alter table staffs add index idx_nap(name, age, pos);
在BTREE索引的使用上,如下几种状况能够用到该索引或索引的一部分(使用explain简单查看使用状况):
1. 全值匹配
如select * from staffs where name = 'July' and age = '23' and pos = 'dev' ,key字段显示使用了idx_nap索引
2. 匹配最左列,对于复合索引来讲,不老是匹配全部字段列,可是能够匹配索引中靠左的列
如select * from staffs where name = 'July' and age = '23',key字段显示用到了索引,注意,key_len字段(表示本次语句使用的索引长度)数值比上一条小了,意思是它并未使用所有索引列(一般这个长度可估摸着用了哪些索引列,埋个坑),事实上只用到了name和age列
再试试select * from staffs where name = 'July',它也用了索引,key_len值更小,实际只用到了索引中的name列
3. 匹配列前缀,即一个索引中列的前一部分,主要用在模糊匹配,如select * from staffs where name like 'J%',explain信息的key字段表示使用了索引,可是mysql的B树索引不能非列前缀的模糊匹配,如select * from staffs where name like '%y' 或者 like '%u%',听说是因为底层存储引擎的API限制
4. 匹配范围,如select * from staffs where name > 'Mary',但俺在测试时发现>能够,>=却不行,至少在字符串列上不行(测试mysql版本5.5.12),然而在时间类型(timestamp)上却能够,不测试下还真不能肯定说就用到了索引==
出于好奇测了下整型字段的索引(idx_cn(count, name),count为整型),发现整型受限制少不少,下面的都能用到索引,连前模糊匹配的都行
select * from indexTest1 where count > '10' select * from indexTest1 where count >= '10' select * from indexTest1 where count > '10%' select * from indexTest1 where count >= '10%' select * from indexTest1 where count > '%10%' select * from indexTest1 where count >= '%10%'
5. 精确匹配一列并范围匹配右侧相邻列,即前一列是固定值,后一列是范围值,它用了name与age两个列的索引(key_len推测)
如select * from staffs where name = 'July' and age > 25
6. 只访问索引的查询,好比staffs表的状况,索引创建在(name,age,pos)上面,前面一直是读取的所有列,若是咱们用到了哪些列的索引,查询时也只查这些列的数据,就是只访问索引的查询,如
select name,age,pos from staffs where name = 'July' and age = 25 and pos = 'dev' select name,age from staffs where name = July and age > 25
第一句用到了所有索引列,第二句只用了索引前两列,select的字段就最多只能是这两列,这种查询状况的索引,mysql称为覆盖索引,就是索引包含(覆盖)了查询的所有字段。是否是用到了索引查询,在explain中须要看最后一个Extra列的信息,Using index代表使用了覆盖索引,同时Using where代表也使用了where过滤
7. 前缀索引
区别于列前缀(相似like 'J%'形式的模糊匹配)和最左列索引(顺序取索引中靠左的列的查询),它只取某列的一部分做为索引。一般在说InnoDB跟MyISAM的区别时,一个明显的区别是:MyISAM支持全文索引,而InnoDB不行,甚至对于text、blob这种超长的字符串或二进制数据时,MyISAM会取前多少个字符做为索引,InnoDb的前缀索引跟这个相似,某些列,通常是字符串类型,很长,所有做为索引大大增长存储空间,索引也须要维护,对于长字符串,又想做为索引列,一个可取的办法就是取前一部分(前缀),表明一整列做为索引串,问题是:如何确保这个前缀能表明或大体表明这一列?因此mysql中有个概念是索引的选择性,是指索引中不重复的值的数目(也称基数)与整个表该列记录总数(#T)的比值,好比一个列表(1,2,2,3),总数是4,不重复值数目为3,选择性为3/4,所以选择性范围是[1/#T, 1],这个值越大,表示列中不重复值越多,越适合做为前缀索引,惟一索引(UNIQUE KEY)的选择性是1。
好比有一列a varchar(255),以它做前缀索引,好比以7个测试,逐个增长看看选择性值增加到那个数基本不变,就表示能够表明整列了,再结合这个长度的索引列是否存储数据太多,作个权衡,基本就好了。但若是这个选择性原本就小的可怜仍是算了
select count(distinct left(a, 7))/count(*) as non_repeat from tab;
定好一个前缀数目,如9,添加索引时能够这样
alter table tab add index idx_pn(name(9)) --单独前缀索引 alter table tab add index idx_cpn(count, name(9)) --复合前缀索引
以上为常见的使用索引的方式,有这么些状况不能用或不能全用,有的就是上面状况的反例,以key(a, b, c)为例
1. 跳过列,where a = 1 and c = 3,最多用到索引列a;where b = 2 and c = 3,一个也用不到,必须从最左列开始
2. 前面是范围查询,where a = 1 and b > 2 and c = 3,最多用到 a, b两个索引列;
3. 顺序颠倒,where c = 3 and b = 2 and a = 1,一个也用不到;
4. 索引列上使用了表达式,如where substr(a, 1, 3) = 'hhh',where a = a + 1,表达式是一大忌讳,再简单mysql也不认。有时数据量不是大到严重影响速度时,通常能够先查出来,好比先查全部有订单记录的数据,再在程序中去筛选以'cp1001'开头的订单,而不是写sql过滤它;
5. 模糊匹配时,尽可能写 where a like 'J%',字符串放在左边,这样才可能用获得a列索引,甚至可能还用不到,固然这得看数据类型,最好测试一下。
排序对索引的影响
order by是常常用的语句,排序也遵循最左前缀列的原则,好比key(a, b),下面语句能够用到(测试为妙)
select * from tab where a > 1 order by b select * from tab where a > 1 and b > '2015-12-01 00:00:00' order by b select * from tab order by a, b
如下状况用不到
1. 非最左列,select * from tab order by b;
2. 不按索引列顺序来的,select * from tab where b > '2015-12-01 00:00:00' order by a;
3. 多列排序,但列的顺序方向不一致,select * from tab a asc, b desc。
初步了解以上内容后,就知道了索引冗余了,好比有了(a,b)索引,(a)就是冗余的,须要建立(a)索引时,直接建立(a)就行,而不是(id,a),id指主键,主键primary key已是UNIQUE KEY了,不用加惟一限制。
聚簇索引与覆盖索引
前面说到,mysql索引从结构上只有两类,BTREE与HASH,覆盖索引只是在查询时,要查询的列恰好与使用的索引列彻底一致,mysql直接扫描索引,而后就可返回数据,大大提升效率,由于不需再去原表查询、过滤,这种形式下的索引称做覆盖索引,好比key(a,b),查询时select a,b from tab where a = 1 and b > 2,本质缘由:BTREE索引存储了原表数据。
聚簇索引也不是单独的索引,前面简要写到,BTREE索引会把数据放在索引中,即索引的叶子页中,包括主键,主键是跟表数据紧挨着放在一块儿的,由于表数据只有一份,一列键值要跟每一行数据都紧挨在一块儿,因此一张表只有一个聚簇索引,对于mysql来讲,就是主键列,它是默认的。
聚簇索引将表数据组织到了一块儿(参考前面主键索引简略图),插入时严重依赖主键顺序,最好是连续自增,不然面临频繁页分裂问题,移动许多数据。
哈希索引
简要说下,相似于数据结构中简单实现的HASH表(散列表)同样,当咱们在mysql中用哈希索引时,也是对索引列计算一个散列值(相似md五、sha一、crc32),而后对这个散列值以顺序(默认升序)排列,同时记录该散列值对应数据表中某行的指针,固然这只是简略模拟图
好比对姓名列创建hash索引,生成hash值按顺序排列,可是顺序排列的hash值并不对应表中记录,从地址指针可反应出来,并且,hash索引可能创建在两列或者更多列上,取得是多列数据后的hash值,它不存储表中数据。它先计算列数据的hash值,与索引中的hash值比较,找到了而后比对列数据是否相等,可能涉及其余列条件,而后返回数据。hash固然会有冲突,即碰撞,除非有不少冲突,通常hash索引效率很高,不然hash维护成本较高,所以哈希索引一般用在选择性较高的列上面。哈希索引的结构决定了它的特色:
1. hash索引只是hash值顺序排列,跟表数据没有关系,没法应用于order by;
2. hash索引是对它的全部列计算哈希值,所以在查询时,必须带上全部列,好比有(a, b)哈希索引,查询时必须 where a = 1 and b = 2,少任何一个不行;
3. hash索引只能用于比较查询 = 或 IN,其余范围查询无效,本质仍是因不存储表数据;
4. 一旦出现碰撞,hash索引必须遍历全部的hash值,将地址所指向数据一一比较,直到找到全部符合条件的行。
填坑
前面提到经过explain的key_len字段,可大体估计出用了哪些列,索引列的长度跟索引列的数据类型直接相关,通常,咱们说int是4字节,bigint8字节,char是1字节,考虑到建表时要指定字符集,好比utf8,还跟选的字符集有关(==!),在utf8下边,一个char是3字节,可是知道这些仍不能说key_len就是将用到的索引列的数据类型表明字节数一加不就完啦?事实总有点区别,测试方法比较机械(如下基于mysql 5.5.2)
建表,加索引,int型
--测试表 create table keyLenTest1( id int primary key auto_increment, typeKey int default 0 , add_time timestamp not null default current_timestamp ) charset utf8 --添加索引 alter table keyLenTest1 add index idx_k(typeKey);
可知int型索引默认长度为5,在4字节基础上+1
char型
--改成char型,1个字符 alter table keyLenTest1 modify typeKey char(1);
--改成char型,2个字符 alter table keyLenTest1 modify typeKey char(2);
可知,char型初始是4字节(3+1 bytes),后续按照3字节递增
varchar型
--改成varchar型,1个字符 alter table keyLenTest1 modify typeKey varchar(1);
--改成varchar型,2个字符 alter table keyLenTest1 modify typeKey varchar(2);
可知,varchar型,1个字符时,key_len为6,之后以3字节递增
因此,若是一个语句用到了int、char、varchar,key_len如何计算以及用了哪些索引列应该很清楚了。
若是想了解的更详细点,explain各字段意义,索引的更多细节,除了explain,还有show profiles、慢查询日志等(没细看),推荐看高性能mysql,毕竟俺写的太肤浅。
end~