什么是索引?
索引是存储引擎用于快速找到记录的一种数据结构. 索引是在存储引擎层而不是服务器层实现的, 所以不同储存引擎的索引的工作方式是不一样的, 这里主要介绍应用最多的InnoDB存储引擎的B+Tree索引.
索引的基本语法
(1)创建索引
CREATE [UNIQUE] INDEX 索引名 ON 表名 (column_list)
ALTER 表名 ADD [UNIQUE] INDEX 索引名 ON (column_list)
(2)删除索引
DROP INDEX 索引名 ON 表名
(3)查看索引
SHOW INDEX FROM 表名
后面实例需要用到的表结构
CREATE TABLE `user_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL DEFAULT '',
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `name_index` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
CREATE TABLE `order_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) DEFAULT NULL,
`product_name` varchar(50) NOT NULL DEFAULT '',
`productor` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `user_product_detail_index` (`user_id`,`product_name`,`productor`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
B+Tree索引
在InnoDB存储引擎中, 数据管理的最小单位为页, 默认是16KB. B+Tree索引的一个节点大小等于一个页的大小. 其中非叶子节点存储键值和指针信息, 键值为索引值, 指针指向比键值大的节点. 叶子节点存储键值、数据信息以及一个指向下一个叶子节点的指针. B+Tree对索引列是按顺序储存的(默认按键值升序), 并且每一个叶子节点到根节点的距离相同, 下图是一个主键索引的结构:
B+Tree支持两种查找运算, 一种是从根节点开始进行二分查找. 另一种是区间查找, 例如, 如果要查询键值为从15到49的所有数据记录, 当找到15后, 只需顺着叶子节点的指针, 顺序遍历就可以一次性访问到所有数据节点, 极大提高了区间查询效率.
InnoDB存储引擎的B+Tree索引又分为聚集索引(Clustered Index)和辅助索引(Secondary Index)两种. 每张表只能有一个聚集索引, 其余全是辅助索引. 聚集索引的定义如下:
- 如果定义了主键, InnoDB会自动使用主键来创建聚集索引.
- 如果没有定义主键, InnoDB会选择一个唯一的非空索引(UNIQUE+字段不能为空), 将其作为聚集索引.
- 如果既没有定义主键, 又没有唯一的非空索引, InnnodDB会生成一个长度为6字节的隐藏主键.
上面的"主键索引"示例图即为聚集索引, 聚集索引按照每张表的主键构造一颗B+Tree, 叶子节点中存放的即为整张表的记录数据. 辅助索引与聚集索引的区别在于, 辅助索引的叶子节点并不存储行记录, 而是存储对应行记录的主键. 这意味着当使用辅助索引查询数据时, 需要进行两次索引查找, 第一次通过辅助索引查找到对应的主键值, 第二次根据主键值在聚集索引中查找到对应的行. 辅助索引结构如下图所示:
索引性能分析
MySQL 提供了一个 EXPLAIN 命令, 它可以对 SELECT 语句进行分析, 并输出 SELECT 执行的详细信息, 以供开发人员针对性优化. EXPLAIN 命令用法十分简单, 在 SELECT 语句前加上 EXPLAIN 就可以了, 例如:
EXPLAIN输出格式
(1) id
select标识符, 表示select查询的执行顺序, id值越大优先级越高, 越先被执行. id相同, 执行顺序由上至下.
(2)select_type
查询类型, 主要用于区分普通查询, 联合查询, 子查询等复杂查询. 常见类型如下:
select_type | Meaning |
---|---|
SIMPLE | 表示此查询不包含 UNION 查询或子查询 |
PRIMARY | 表示此查询是最外层的查询 |
SUBQUERY | 子查询中的第一个 SELECT |
DERIVED | 在FROM列表中包含的子查询被标记为DERIVED, MySQL会递归执行这些子查询, 把结果放在临时表里 |
UNION | 表示此查询是 UNION 后的查询 |
UNION RESULT | 从UNION表获取结果的SELECT |
(3)table
表示查询的表或衍生表.
(4)type
表示查询访问数据的方式(或者说MySQL在表中找到所需行的方式). 下面按照从最佳类型到最差类型的顺序给出常见的连接类型:
- system
查询的表是仅有一行记录的系统表, 这是const连接类型的一个特例. - const
针对主键或唯一索引的等值查询, 最多只返回一行数据. - eq_ref
多表连接中使用主键或者定义了唯一索引的列作为关联条件. - ref
多表连接中使用定义了非唯一性索引的列作为关联条件. - range
使用任意类型的索引来查询给定范围的行.如 like, in, >, < , !=, between等. - index
Full Index Scan, index与ALL区别为index类型只遍历索引树. 这通常比ALL快, 因为索引文件通常比数据文件小. - all
Full Table Scan, 将遍历全表以找到匹配的行.
(5)possible_keys
可能使用的索引.
(6)key
实际使用到的索引.
(7)key_len
实际使用到的索引的总长度. 通过key_len值可以确定MySQL实际使用了一个复合索引的几个字段.
(8)ref
哪些列或常量被用于查找索引列上的值.
(9)rows
显示在查询时必须检查的行数.
(10)Extra
MySQL解决查询的额外信息, 包括:
- Using filesort
无法利用索引完成排序操作, 必须额外进行一次排序. - Using temporary
表示需要创建临时表来存储结果集, 常见于连表排序和分组查询. - Using index
“覆盖索引扫描”,只需从辅助索引中就可以得到查询记录, 而不需要查询聚集索引. 覆盖索引即一个索引包含所有需要查询的字段. - Using index condition
需要查询聚集索引. - Using where
表示不能通过索引获取数据, 需要扫描表. - Using join buffer
在获取连接条件时没有使用索引, 并且需要连接缓冲区来存储中间结果. 如果出现了这个值, 那应该注意, 根据查询的具体情况可能需要添加索引来改进能. - Impossible where
where子句的值总是false, 不能用来获取任何元组. - Distinct
在distinct 查询中, 如果找到了第一个匹配的元素, 就停止查找同样值的操作.
索引分类
单列索引
单列索引即一个索引只包含单个列. MySQL有两种特殊的单列索引, 分别是唯一索引(UNIQUE字段修饰的列)和主键索引(被PRIMARY KEY修饰的列).
MySQL5.0后引入了一种叫"索引合并"的策略, 一定程度上可以使用表上的多个单列索引来定位指定的行.
单列索引不能是表达式的一部分, 也不能是函数的参数, 否则将失效. 如下面两种情况都将导致索引失效:
(1)作为表达式的一部分
(2)作为函数的参数
多列索引
多列索引即一个索引包含多个列.
索引可以按照升序或者降序进行扫描, 以满足精确符合列顺序的ORDER BY, GROUP BY 和 DISTINCT 等子句的查询需求. 多列索引要注意索引列的顺序, 因为B-Tree在排序时先按照最左列排序, 其次是第二列往后, 如果跳过了一列将会导致索引失效.
(1)正常使用索引
(2)索引失效
(3)注意一种情况, 当多列索引前面的列已经确定时可以跳过.
索引优化
(1)尽量使用全值匹配, 即多列索引各列都用到.
(2)尽量使用覆盖索引, 即一个索引包含所有需要查询的字段. 这样查询只需从辅助索引中就可以得到查询记录, 而不需要查询聚集索引.
(3)使用索引扫描来做排序.
(4)不要在索引上做任何操作(计算, 函数, 类型转换等), 否则会导致索引失效而转向全表扫描.
(5)满足最左前缀法则, 即对于多列索引, 要从左往右进行匹配, 且不能跳过一列.
(6)范围条件之后的索引全失效(like, in, >, < , !=, between等), LIKE的百分号写最右边.
(7)is null, is not null ,or容易造成索引失效.
(8)如果是字符串类型的数据, 不能丢失引号, 否则会进行自动类型转换, 从而导致索引失效.
索引的优缺点
(1)优点
- 提高数据的检索效率.
- 索引可以帮助服务器避免排序和临时表(ORDER BY, GROUP BY 和 DISTINCT).
(2)缺点
- 索引需要占据磁盘空间.
- 虽然索引大大提高了查询速度, 但同时也会降低更新表的速度(如INSERT,UPDATE, DELETE), 因为更新表时, MySQL不仅要更新数据, 还要更新索引信息.
哪些情况需要创建索引
- 频繁作为查询条件的字段应该创建索引.
- 查询中与其他表关联的字段, 外键关系创建索引.
- 查询中排序的字段应该创建索引.
- 查询中统计或分组的字段应该创建索引, 因为分组操作会默认先进行排序.
- 频繁更新的字段不适合创建索引.
- where条件里用不到的字段不创建索引.
- 表记录太少不需要创建索引, 如100万以下.
- 数据重复且分布平均的字段不适合创建索引.