MySQL 如何使用索引
没有索引,mysql会从第一行开始扫全表,找出相关行。表越大,代价越大。有索引的话mysql会可以快速定位数据,比顺序读取每一行快。大部分索引会保存到B树里面
-
通过where子句过滤符合条件的行
-
多个索引可选会选区分度高的(扫描行数最小的)
-
多列索引根据最左原则优化
(col1)
,(col1, col2)
以及(col1, col2, col3)
都可以用到三列的索引(col1, col2, col3)
-
join 时 相同类型和大小(
VARCHAR(10)
andCHAR(10)
)使用索引会更有效,无法比较的值(不同字符集,不同类型)无法使用索引 -
查找特定索引列的
MIN()
或MAX()
值key_col
。这是由预处理器优化的,该预处理器检查您是否 在索引中之前出现的所有关键部分上使用。在这种情况下,MySQL 对每个or 表达式执行单个键查找,并将其替换为常量。如果所有表达式都替换为常量,则查询立即返回。https://stackoverflow.com/questions/1992312/meaning-of-select-tables-optimized-away-in-mysql-explain-plan
优缺点
索引种类
-
主键索引: 数据列不允许重复,不允许为NULL,一个表只能有一个主键。
-
唯一索引: 数据列不允许重复,允许为NULL值,一个表允许多个列创建唯一索引。
可以通过
ALTER TABLE table_name ADD UNIQUE (column);
创建唯一索引可以通过
ALTER TABLE table_name ADD UNIQUE (column1,column2);
创建唯一组合索引 -
普通索引: 基本的索引类型,没有唯一性的限制,允许为NULL值。
可以通过
ALTER TABLE table_name ADD INDEX index_name (column);
创建普通索引可以通过
ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3);
创建组合索引 -
全文索引: 是目前搜索引擎使用的一种关键技术。
可以通过ALTER TABLE table_name ADD FULLTEXT (column);创建全文索引
索引结构
索引有Hash索引,B+树索引等,而我们经常使用的InnoDB存储引擎的默认索引实现为:B+树索引。对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引。
B树
查询方式:
主键索引区:PI(关联保存的时数据的地址)按主键查询,
普通索引区:si(关联的id的地址,然后再到达上面的地址)。所以按主键查询,速度最快
B+tree性质:
- n棵子tree的节点包含n个关键字,不用来保存数据而是保存数据的索引。
- 所有的叶子结点中包含了全部关键字的信息,及指向含这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。
- 所有的非终端结点可以看成是索引部分,结点中仅含其子树中的最大(或最小)关键字。
- B+ 树中,数据对象的插入和删除仅在叶节点上进行。
- B+树有2个头指针,一个是树的根节点,一个是最小关键码的叶节点。
B+树
B树插入过程
关键字序列{1,2,6,7,11,4,8,13,10,5}为例,构建5阶B树,则一个结点最多关键字4个
1,2,6,7组成根节点
再插入10,以关键字10进行拆分
Hash
简要说下,类似于数据结构中简单实现的HASH表(散列表)一样,当我们在mysql中用哈希索引时,主要就是通过Hash算法(常见的Hash算法有直接定址法、平方取中法、折叠法、除数取余法、随机数法),将数据库字段数据转换成定长的Hash值,与这条数据的行指针一并存入Hash表的对应位置;如果发生Hash碰撞(两个不同关键字的Hash值相同),则在对应Hash键下以链表形式存储。当然这只是简略模拟图。
对比hash B树
-
hash索引进行等值查询更快(一般情况下),但是却无法进行范围查询。 因为在hash索引中经过hash函数建立索引之后,索引的顺序与原顺序无法保持一致,不能支持范围查询。而B+树的的所有节点皆遵循(左节点小于父节点,右节点大于父节点,多叉树也类似),天然支持范围。
- hash索引不支持使用索引进行排序,原理同上。
- hash索引不支持模糊查询以及多列索引的最左前缀匹配。原理也是因为hash函数的不可预测。AAAA和AAAAB的索引没有相关性。
- hash索引任何时候都避免不了回表查询数据,而B+树在符合某些条件(聚簇索引,覆盖索引等)的时候可以只通过索引完成查询。
- hash索引虽然在等值查询上较快,但是不稳定。性能不可预测,当某个键值存在大量重复的时候,发生hash碰撞,此时效率可能极差。而B+树的查询效率比较稳定,对于所有的查询都是从根节点到叶子节点,且树的高度较低。
B树B+树
B和B+树的区别在于,B+树的非叶子结点只包含导航信息,不包含实际的值,所有的叶子结点和相连的节点使用链表相连,便于区间查找和遍历。因此在内存页中能够存放更多的key。 数据存放的更加紧密,具有更好的空间局部性。因此访问叶子节点上关联的数据也具有更好的缓存命中率。
B树则需要进行每一层的递归遍历。相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。
但是B树也有优点,其优点在于,由于B树的每一个节点都包含key和value,因此经常访问的元素可能离根节点更近,因此访问也更迅速。
- B树只适合随机检索,而B+树同时支持随机检索和顺序检索;
- B+树空间利用率更高,可减少I/O次数,磁盘读写代价更低。一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗。B+树的内部结点并没有指向关键字具体信息的指针,只是作为索引使用,其内部结点比B树小,盘块能容纳的结点中关键字数量更多,一次性读入内存中可以查找的关键字也就越多,相对的,IO读写次数也就降低了。而IO读写次数是影响索引检索效率的最大因素;
- B+树的查询效率更加稳定。B树搜索有可能会在非叶子结点结束,越靠近根节点的记录查找时间越短,只要找到关键字即可确定记录的存在,其性能等价于在关键字全集内做一次二分查找。而在B+树中,顺序检索比较明显,随机检索时,任何关键字的查找都必须走一条从根节点到叶节点的路,所有关键字的查找路径长度相同,导致每一个关键字的查询效率相当。
- B+树全节点遍历更快,B-树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题。B+树的叶子节点使用指针顺序连接在一起,只要遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的操作。
- 增删文件(节点)时,效率更高。因为B+树的叶子节点包含所有关键字,并以有序的链表结构存储,这样可很好提高增删效率。
AVL 数和红黑树基本都是存储在内存中才会使用的数据结构。在大规模数据存储的时候,红黑树往往出现由于树的深度过大而造成磁盘IO读写过于频繁,磁盘查找存取的次数往往由树的高度所决定,所以,只要我们通过某种较好的树结构减少树的结构尽量减少树的高度。
红黑树是一种弱平衡二叉树(由于是弱平衡,可以推出,相同的节点情况下,AVL树的高度低于红黑树),相对于要求严格的AVL树来说,它的旋转次数变少,所以对于搜索、插入、删除操作多的情况下,我们就用红黑树
优化
索引
索引的数据类型
- 越小的数据类型通常更好:越小的数据类型通常在磁盘、内存和CPU缓存中都需要更少的空间,处理起来更快。
- 简单的数据类型更好:整型数据比起字符,处理开销更小,因为字符串的比较更复杂。在MySQL中,应该用内置的日期和时间数据类型,而不是用字符串来存储时间;以及用整型数据类型存储IP地址。
- 尽量避免NULL:应该指定列为NOT NULL,除非你想存储NULL。在MySQL中,含有空值的列很难进行查询优化,因为它们使得索引、索引的统计信息以及比较运算更加复杂。你应该用0、一个特殊的值或者一个空串代替空值。
建索引的几大原则
- 最左前缀匹配原则,非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
- =和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式
- 尽量选择区分度高的列作为索引,区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就是0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,这个值也很难确定,一般需要join的字段我们都要求是0.1以上,即平均1条扫描10条记录
- 索引列不能参与计算,保持列“干净”,比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本太大。所以语句应该写成create_time = unix_timestamp(’2014-05-29’);
- 尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可
索引失效
- 如果条件中有or,即使其中有条件带索引也不会使用。要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引。
- like查询是以%开头
- 类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引
- 如果mysql估计使用全表扫描要比使用索引快,则不使用索引
- 上面给出一个多列索引(username,password,last_login),当三列在where中出现的顺序如(username,password,last_login)、 (username,password)、(username)才能用到索引,如下面几个顺序(password,last_login)、(passwrod)、(last_login)—这三者不 从username开始,(username,last_login)—断层,少了password,都无法利用到索引。因为B+tree多列索引保存的顺序是按照索引创 建的顺序,检索索引时按照此顺序检索
理解MySQL——索引与优化 MYSQL索引结构原理、性能分析与优化
- 要取出所有等值谓词中的列,作为索引开头的最开始的列(任意顺序)
- 要将 ORDER BY 列加入索引中
-
要将查询语句剩余的列全部加入到索引中
- 不只是将等值谓词的列加入索引,它的作用是减少索引片的大小以减少需要扫描的数据行
- 用于避免排序,减少磁盘 IO 和内存的使用
- 用于避免每一个索引对应的数据行都需要进行一次随机 IO 从聚集索引中读取剩余的数据
索引覆盖
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`name` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '名称',
`age` int(3) unsigned NOT NULL DEFAULT '1' COMMENT '年龄',
PRIMARY KEY (`id`),
KEY `I_name` (`name`)
) ENGINE=InnoDB;
INSERT INTO student (name, age) VALUES("小赵", 10),("小王", 11),("小李", 12),("小陈", 13);
1 |
|
-
在name索引树上找到名称为小李的节点 id为03
-
从id索引树上找到id为03的节点 获取所有数据
-
从数据中获取字段命为age的值返回 12
回表:在流程中从非主键索引树搜索回到主键索引树搜索的过程
索引覆盖:从非主键索引中就能查到的记录,而不需要查询主键索引中的记录,避免了回表的产生减少了树的搜索次数,显著提升性能
1 |
|
- 在name,age联合索引树上找到名称为小李的节点
- 此时节点索引里包含信息age 直接返回 12
大表分页
一个6亿的表a,一个3亿的表b,通过外间tid关联,你如何最快的查询出满足条件的第50000到第50200中的这200条数据记录。
- 1、如果A表TID是自增长,并且是连续的,B表的ID为索引
1 |
|
- 2、如果A表的TID不是连续的,那么就需要使用覆盖索引.TID要么是主键,要么是辅助索引,B表ID也需要有索引。
1 |
|
mysql对的索引选择
MySQL 一张表是支持多个索引同时存在的,一条sql 同时命中多个索引,使用哪个索引? 在用户不主动选择索引的情况下,数据库的优化器会根据一系列的判断依据,选择最小的代价去执行语句。
扫描的行数越少,意味着访问磁盘数据的次数越少,消耗的 CPU 资源越少。当然,扫描行数并不是唯一的判断标准,优化器还会结合是否使用临时表、是否排序等因素进行综合判断。
why 数据库选错索引
MySQL 在真正开始执行语句之前,并不能精确地知道满足这个条件的记录有多少条,而只能根据统计信息来估算记录数。这个统计信息就是索引的区分度。
一个索引上不同的值越多,这个索引的区分度就越好。而一个索引上不同的值的个数,我们称之为基数(cardinality)。也就是说,这个基数越大,索引的区分度越好。我们可以使用 show index
方法,看到一个索引的基数。
MySQL的采样统计
采样统计的时候,InnoDB 默认会选择 N 个数据页,统计这些页面上的不同值,得到一个平均值,然后乘以这个索引的页面数,就得到了这个索引的基数。而数据表是会持续更新的,索引统计信息也不会固定不变。所以,当变更的数据行数超过 1/M 的时候,会自动触发重新做一次索引统计。
当采样统计得到的索引基数与实际差别很大,数据库为什么会选错索引。
- 删除低效索引,创建复合高效索引
- Force Index
- 由于索引统计信息不准确导致的问题,可以用
analyze table t
来解决,重新进行一次采样统计
example
1 |
|
1 |
|
mysql最终是否选择走索引或者一张表涉及多个索引, mysql最终如何选择索引,可以通过trace工具来一查究竟,开启trace工具会影响mysql性能,所以只能临时分析sql使用,用完之后需要立即关闭。
1 |
|
- 分析sql
- 预估全表搜索成本
- 根据sql条件查询可能会使用到的索引
- 分析各个索引使用成本
- 执行sql
1 |
|
order by
1 |
|
1 |
|
可以优化排序,但是用了select *
可能选择的列比(key_part1,key_part2)多很多,造成寻找不在索引的列比直接扫全表排序的代价大。
1 |
|
如果t1是InnoDB 表,则表主键是索引的隐式部分,并且可以使用索引来解析 ORDER BY此查询
1 |
|
order by 和 where 在索引范围里面比全局扫描快
不使用索引
1 |
|
Gitalking ...