MySQL全面瓦解23:MySQL索引实现和使用
MySQL索引实现
InnoDB引擎中的索引
非聚集索引类型 | 说明 |
---|---|
单列索引 | 一个索引只包含一个列 |
多列索引(复合索引) | 一个索引包含多个列 |
唯一索引 | 索引列的值必须唯一,允许有一个空值 |
MyISAM引擎中的索引
InnoDB数据检索过程
上面的表中有2个索引:id作为主键索引,name作为辅助索引。
如果需要查询id=14的数据,只需要在左边的主键索引中检索就可以了。
MyISAM数据检索过程
1、在索引中找到对应的关键字,获取关键字对应的记录的地址
2、通过记录的地址查找到对应的数据记录
索引管理和使用
数据准备
请参考第21篇(MySQL全面瓦解21(番外):一次深夜优化亿级数据分页的奇妙经历)中模拟的千万数据,我们以这个数据为测试数据。
创建索引
1 create [unique] index index_name on t_name(c_name[(length)]);
1 alter t_name add [unique] index index_name on (cname[(length)]);
这边需注意的是:
删除索引
1 drop index index_name on t_name;
查看索引
1 show index from t_name;
索引修改
示例
emp表中有500W数据 我们用emp来做测试
1 mysql> select count(*) from emp; 2 +----------+ 3 | count(*) | 4 +----------+ 5 | 5000000 | 6 +----------+ 7 1 row in set
查看和创建索引
记得我们之前在emp表上做过索引,所以先看一下这个表目前所有的索引
可以看到,目前主键字段id和depno字段上都有建立索引
1 mysql> desc emp; 2 +----------+-----------------------+------+-----+---------+----------------+ 3 | Field | Type | Null | Key | Default | Extra | 4 +----------+-----------------------+------+-----+---------+----------------+ 5 | id | int(10) unsigned | NO | PRI | NULL | auto_increment | 6 | empno | mediumint(8) unsigned | NO | | 0 | | 7 | empname | varchar(20) | NO | | | | 8 | job | varchar(9) | NO | | | | 9 | mgr | mediumint(8) unsigned | NO | | 0 | | 10 | hiredate | datetime | NO | | NULL | | 11 | sal | decimal(7,2) | NO | | NULL | | 12 | comn | decimal(7,2) | NO | | NULL | | 13 | depno | mediumint(8) unsigned | NO | MUL | 0 | | 14 +----------+-----------------------+------+-----+---------+----------------+ 15 9 rows in set 16 17 mysql> show index from emp; 18 +-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 19 | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | 20 +-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 21 | emp | 0 | PRIMARY | 1 | id | A | 4952492 | NULL | NULL | | BTREE | | | 22 | emp | 1 | idx_emp_id | 1 | id | A | 4952492 | NULL | NULL | | BTREE | | | 23 | emp | 1 | idx_emp_depno | 1 | depno | A | 18 | NULL | NULL | | BTREE | | | 24 +-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 25 3 rows in set
我们在没有做索引的字段上做一下查询看看,在500W数据中查询一个名叫LsHfFJA的员工,消耗 2.239S
再看看他的执行过程,扫描了4952492 条数据才找到该行数据:
1 mysql> explain select * from emp where empname='LsHfFJA'; 2 +----+-------------+-------+------+---------------+------+---------+------+---------+-------------+ 3 | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | 4 +----+-------------+-------+------+---------------+------+---------+------+---------+-------------+ 5 | 1 | SIMPLE | emp | ALL | NULL | NULL | NULL | NULL | 4952492 | Using where | 6 +----+-------------+-------+------+---------------+------+---------+------+---------+-------------+ 7 1 row in set
我们在empname这个字段上建立索引
1 mysql> create index idx_emp_empname on emp(empname); 2 Query OK, 0 rows affected 3 Records: 0 Duplicates: 0 Warnings: 0 4 5 mysql> show index from emp; 6 +-------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 7 | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | 8 +-------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 9 | emp | 0 | PRIMARY | 1 | id | A | 4952492 | NULL | NULL | | BTREE | | | 10 | emp | 1 | idx_emp_id | 1 | id | A | 4952492 | NULL | NULL | | BTREE | | | 11 | emp | 1 | idx_emp_depno | 1 | depno | A | 18 | NULL | NULL | | BTREE | | | 12 | emp | 1 | idx_emp_empname | 1 | empname | A | 1650830 | NULL | NULL | | BTREE | | | 13 +-------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 14 4 rows in set
再看一下这个执行效率,就会发现有质的飞跃:0.001S,就是这么神奇,学过之前那篇的B+ Tree就知道,它不用从头开始扫表核对,而是很小次数的io读取
再看看他的执行过程,一次定位到该条数据:
1 mysql> explain select * from emp where empname='LsHfFJA'; 2 +----+-------------+-------+------+-----------------+-----------------+---------+-------+------+-----------------------+ 3 | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | 4 +----+-------------+-------+------+-----------------+-----------------+---------+-------+------+-----------------------+ 5 | 1 | SIMPLE | emp | ref | idx_emp_empname | idx_emp_empname | 22 | const | 1 | Using index condition | 6 +----+-------------+-------+------+-----------------+-----------------+---------+-------+------+-----------------------+ 7 1 row in set
设置合适的索引长度
根据我们之前的了解,每个磁盘块(disk)存储的内容是有限的,如果一个页中可以存储的索引记录越多,那么查询效率就会提高,所以我们可以指定索引的字段长度。
但并不是越短越好,要保证字符类型字段查询有足够高的区分度,如果只设置了一个长度,反而导致查询的相似匹配度不高。
长度的原则是要恰到好处,太长索引文件就会变大,因此要在区分度和长度上做一个平衡。
如果在我们搜索的内容中,最后的内容是一致的或者高度一致的,那我们就可以省略,比如在用户的email字段上做索引,几乎前10个字符是不一样的,结尾限定在 @****,那么通过前面10个字符就可以定位一个email地址了。
我们在该字段创建索引的时候就可以指定长度为10,这样相对于整个email字段更短些,查询效果确却基本一样,这样一个页中也可以存储更多的索引记录。
像我们上面的那个 empname 字段,基本都是6位数的,只是小部分是超过6位数,而且后缀基本一致,所以6位数之后的区分度差不多。
有一个判断 高区分度以及合适长度索引 的通用算法,如下:
1 select count(distinct left(`c_name`,calcul_len))/count(*) from t_name;
下面是对 empname 做的分析,匹配度越高搜索效率越高:
1 mysql> select count(distinct left(`empname`,3))/count(*) from emp; 2 +--------------------------------------------+ 3 | count(distinct left(`empname`,3))/count(*) | 4 +--------------------------------------------+ 5 | 0.0012 | 6 +--------------------------------------------+ 7 1 row in set 8 9 mysql> select count(distinct left(`empname`,4))/count(*) from emp; 10 +--------------------------------------------+ 11 | count(distinct left(`empname`,4))/count(*) | 12 +--------------------------------------------+ 13 | 0.0076 | 14 +--------------------------------------------+ 15 1 row in set 16 17 mysql> select count(distinct left(`empname`,6))/count(*) from emp; 18 +--------------------------------------------+ 19 | count(distinct left(`empname`,6))/count(*) | 20 +--------------------------------------------+ 21 | 0.1713 | 22 +--------------------------------------------+ 23 1 row in set 24 25 mysql> select count(distinct left(`empname`,7))/count(*) from emp; 26 +--------------------------------------------+ 27 | count(distinct left(`empname`,7))/count(*) | 28 +--------------------------------------------+ 29 | 0.1713 | 30 +--------------------------------------------+ 31 1 row in set
删除索引
1 mysql> drop index idx_emp_empname on emp; 2 Query OK, 0 rows affected 3 Records: 0 Duplicates: 0 Warnings: 0 4 5 mysql> show index from emp; 6 +-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 7 | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | 8 +-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 9 | emp | 0 | PRIMARY | 1 | id | A | 4952492 | NULL | NULL | | BTREE | | | 10 | emp | 1 | idx_emp_id | 1 | id | A | 4952492 | NULL | NULL | | BTREE | | | 11 | emp | 1 | idx_emp_depno | 1 | depno | A | 18 | NULL | NULL | | BTREE | | | 12 +-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 13 3 rows in set
执行完删除命令再查看,发现索引已经没了
小结
本文只是理解索引的基本用法,后面会认真讲一讲索引的性能分析和优化策略。
总之,理想的索引应该符合以下特征:
1、相对低频的写操作,以及高频的查询的表和字段上建立索引
2、字段区分度高
3、长度小(合适的长度,不是越小越好)
4、尽量能够覆盖常用字段