今天看到别人写的一些关于mysql索引的文章,有一些小收获,就以此开启个人随笔记录简单摘了一些重点html
转载文章:http://www.cnblogs.com/tgycoder/p/5410057.htmlmysql
mysql索引实现原理面试
1. MyISAM引擎使用B+Tree做为索引结构,叶结点的data域存放的是数据记录的地址,MyISAM的索引方式也叫作“非汇集”的,之因此这么称呼是为了与InnoDB的汇集索引区分。sql
2. InnoDB也使用B+Tree做为索引结构,第一个重大区别是InnoDB的数据文件自己就是索引文件,第一个重大区别是InnoDB的数据文件自己就是索引文件。从上文知道,MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而在InnoDB中,表数据文件自己就是按B+Tree组织的一个索引结构,这棵树的叶结点data域保存了完整的数据记录。这个索引的key是数据表的主键,所以InnoDB表数据文件自己就是主索引数据库
InnoDB主索引(同时也是数据文件)的示意图,能够看到叶结点包含了完整的数据记录。这种索引叫作汇集索引。由于InnoDB的数据文件自己要按主键汇集,因此InnoDB要求表必须有主键(MyISAM能够没有),若是没有显式指定,则MySQL系统会自动选择一个能够惟一标识数据记录的列做为主键,若是不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段做为主键,这个字段长度为6个字节,类型为长整形。函数
3. 最左前缀与相关优化mysql索引
以前我理解的最左前缀觉得索引的顺序是跟where条件查询的一致不一致就使用不到索引,这是错误的优化
Ps:最左前缀原则中where字句有or出现仍是会遍历全表spa
(1) 其实where条件的顺序不影响使用索引,好比三个字段添加联合索引t_user表联合索引(name, mobile, create_date)code
select * from t_user where mobile = '13256767876' and create_date= '2017-07-31' and name = 'corner';
理论上索引对顺序是敏感的,可是因为MySQL的查询优化器会自动调整where子句的条件顺序以使用适合的索引,因此这样也是能够用到索引的
(2)查询条件没有指定索引第一列
若是where条件中没有name条件,只有另外两个不管顺序是什么都是没法用到索引的,若是where条件只有name,status而没有mobile这时候只能用到一列索引,status这一列的索引是用不到的
(3)范围查询
范围列能够用到索引(必须是最左前缀),可是范围列后面的列没法用到索引。同时,索引最多用于一个范围列,所以若是查询条件中有两个范围列则没法全用到索引
表t_title联合索引(emp_no,title,from_date)
EXPLAIN SELECT * FROM employees.titles WHERE emp_no < '10010' AND title='Senior Engineer' AND from_date BETWEEN '1986-01-01' AND '1986-12-31';
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ | 1 | SIMPLE | titles | range | PRIMARY | PRIMARY | 4 | NULL | 16 | Using where | +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
只能用到第一列索引,这里特别要说明MySQL一个有意思的地方,那就是仅用explain可能没法区分范围索引和多值匹配,由于在type中这二者都显示为range。同时,用了“between”并不意味着就是范围查询,例以下面的查询:
所有索引都用到了
EXPLAIN SELECT * FROM employees.titles WHERE emp_no BETWEEN '10001' AND '10010' AND title='Senior Engineer' AND from_date BETWEEN '1986-01-01' AND '1986-12-31';
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ | 1 | SIMPLE | titles | range | PRIMARY | PRIMARY | 59 | NULL | 16 | Using where | +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
(4)查询条件中含有函数或表达式
若是查询条件中含有函数或表达式,则MySQL不会为这列使用索引
like若是通配符%不出如今开头,则能够用到索引,但根据具体状况不一样可能只会用其中一个前缀
EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND title LIKE 'Senior%'; +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ | 1 | SIMPLE | titles | range | PRIMARY | PRIMARY | 56 | NULL | 1 | Using where | +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
4.索引选择性与前缀索引
(1)什么状况下判断字段是否应该创建索引,今天刚看到这个"选择性"的概念,除了表数据不多的状况不用建索引由于索引文件自己要消耗存储空间会加剧数据库操做的负担,另一种状况就是索引的选择性比较低:
所谓索引的选择性(Selectivity),是指不重复的索引值(也叫基数,Cardinality)与表记录数(#T)的比值:Index Selectivity = Cardinality / #T
显然选择性的取值范围为(0, 1],选择性越高的索引价值越大,这是由B+Tree的性质决定的。
这个问题就像是面试时提问个人一个问题:性别列适不适合创建索引?(答案是否认的)
例如,上文用到的employees.titles表,若是title字段常常被单独查询,是否须要建索引,咱们看一下它的选择性:
SELECT count(DISTINCT(title))/count(*) AS Selectivity FROM employees.titles; +-------------+ | Selectivity | +-------------+ | 0.0000 | +-------------+
title的选择性不足0.0001(精确值为0.00001579),因此实在没有什么必要为其单独建索引。
(2)有一种与索引选择性有关的索引优化策略叫作前缀索引,就是用列的前缀代替整个列做为索引key,当前缀长度合适时,能够作到既使得前缀索引的选择性接近全列索引,同时由于索引key变短而减小了索引文件的大小和维护开销。下面以employees.employees表为例介绍前缀索引的选择和使用。
从图12能够看到employees表只有一个索引<emp_no>,那么若是咱们想按名字搜索一我的,就只能全表扫描了:
EXPLAIN SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido'; +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+ | 1 | SIMPLE | employees | ALL | NULL | NULL | NULL | NULL | 300024 | Using where | +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+
若是频繁按名字搜索员工,这样显然效率很低,所以咱们能够考虑建索引。有两种选择,建<first_name>或<first_name, last_name>,看下两个索引的选择性:
SELECT count(DISTINCT(first_name))/count(*) AS Selectivity FROM employees.employees; +-------------+ | Selectivity | +-------------+ | 0.0042 | +-------------+ SELECT count(DISTINCT(concat(first_name, last_name)))/count(*) AS Selectivity FROM employees.employees; +-------------+ | Selectivity | +-------------+ | 0.9313 | +-------------+
<first_name>显然选择性过低,<first_name, last_name>选择性很好,可是first_name和last_name加起来长度为30,有没有兼顾长度和选择性的办法?能够考虑用first_name和last_name的前几个字符创建索引,例如<first_name, left(last_name, 3)>,看看其选择性:
SELECT count(DISTINCT(concat(first_name, left(last_name, 3))))/count(*) AS Selectivity FROM employees.employees; +-------------+ | Selectivity | +-------------+ | 0.7879 | +-------------+
选择性还不错,但离0.9313仍是有点距离,那么把last_name前缀加到4:
SELECT count(DISTINCT(concat(first_name, left(last_name, 4))))/count(*) AS Selectivity FROM employees.employees; +-------------+ | Selectivity | +-------------+ | 0.9007 | +-------------+
这时选择性已经很理想了,而这个索引的长度只有18,比<first_name, last_name>短了接近一半,咱们把这个前缀索引 建上:
ALTER TABLE employees.employees ADD INDEX first_name_last_name4 (first_name, last_name(4));
此时再执行一遍按名字查询,比较分析一下与建索引前的结果:
SHOW PROFILES;
+----------+------------+---------------------------------------------------------------------------------+ | Query_ID | Duration | Query | +----------+------------+---------------------------------------------------------------------------------+ | 87 | 0.11941700 | SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido' | | 90 | 0.00092400 | SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido' | +----------+------------+---------------------------------------------------------------------------------+