一种能帮助mysql提高查询效率的数据结构:索引数据结构
“索引(在MySQL中也叫“键key”)是存储引擎快速找到记录的一种数据结构。”
——《高性能MySQL》
数据结构
。性能优化
最常用的工具之一优点
缺点
物理存储空间
结论:数据库表并不是索引加的越多越好,而是仅为那些常用的搜索字段建立索引效果才是最佳的!
主键索引:PRIMARY KEY
设定为逐渐后,数据库自动建立索引,innodb为聚簇索引
,主键索引列值不能有空(Null)
单值索引:又叫单列索引、普通索引
一个索引只包含单个列,一个表可以有多个单列索引
唯一索引:
索引列的值必须唯一,但允许有空值(Null),但只允许有一个空值(Null)
复合索引:又叫组合索引
一个索引可以包含多个列,多个列共同构成一个复合索引!
全文索引:Full Text (MySQL5.7之前,只有MYISAM存储引擎支持全文索引)
全文索引类型为FULLTEXT,在定义索引的列上支持值的全文查找,允许在这些索引列中插入重复值和空值。全文索引可以在Char 、Varchar 上创建。
SHOW INDEX FROM table_name;#查看索引详情:
它是一种特殊的唯一索引,不允许有空值。一般是在建表的时候同时创建主键索引,直接在字段后面加PRIMARY KEY
。注意:一个表只能有一个主键。
唯一索引列的值必须唯一,但允许有空值。
ALTER TABLE 表名 ADD UNIQUE 索引名 ON (列名) #修改表结构添加
CREATE UNIQUE INDEX 索引名 ON 表名(列名) #直接创建
如果是唯一组合索引,则列值的组合必须唯一。
ALTER TABLE 表名 ADD UNIQUE 索引名 (列名1,列名2) #修改表结构添加
CREATE UNIQUE INDEX 索引名 ON 表名(列名1,列名2) #直接创建
ALTER TABLE 表名 ADD INDEX ON (列名) #修改表结构添加
CREATE INDEX 索引名 ON 表名(列名) #直接创建
指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合
ALTER TABLE 表名 ADD INDEX 索引名 (列名1,列名2);
CREATE INDEX 索引名 ON 表名(列名1,列名2);
-- 复合索引查询的2个原则
-- 1.最左前缀原则
-- eg: 创建复合索引时,字段的顺序为 name,age,birthday
-- 在查询时能利用上索引的查询条件为:
SELECT * FROM t_user3 WHERE name = ?
SELECT * FROM t_user3 WHERE name = ? AND age = ?
SELECT * FROM t_user3 WHERE name = ? AND birthday = ?
SELECT * FROM t_user3 WHERE name = ? AND age = ? AND birthday = ?
-- 而其他顺序则不满足最左前缀原则:
... WHERE name = ? AND birthday = ? AND age = ? -- 不满足最左前缀原则
... WHERE name = ? AND birthday = ? -- 不满足最左前缀原则
... WHERE birthday = ? AND age = ? AND name = ? -- 不满足最左前缀原则
... WHERE age = ? AND birthday = ? -- 不满足最左前缀原则
-- 2.MySQL 引擎在执行查询时,为了更好地利用索引,在查询过程中会动态调整查询字段的顺序!
-- 这时候再来看上面不满足最左前缀原则的四种情况:
-- 不满足最左前缀原则,但经过动态调整顺序后,变为:name age birthday 可以利用复合索引!
... WHERE name = ? AND birthday = ? AND age = ?
-- 不满足最左前缀原则,也不能动态调整(因为缺少age字段),不可以利用复合索引!
... WHERE name = ? AND birthday = ?
-- 不满足最左前缀原则,但经过动态调整顺序后,变为:name age birthday 可以利用复合索引!
... WHERE birthday = ? AND age = ? AND name = ?
-- 不满足最左前缀原则,也不能动态调整(因为缺少name字段),不可以利用复合索引!
... WHERE age = ? AND birthday = ?
ALTER TABLE 表名 ADD FULLTEXT 索引名 (列名);
CREATE FULLTEXT INDEX 索引名 ON 表名(列名)
删除索引命令
DROP INDEX <索引名> ON <表名>
索引一经创建不能修改,如果要修改索引,只能删除重建。
FULLTEXT
即为全文索引,其可以在CREATE TABLE ,ALTER TABLE ,CREATE INDEX 使用,不过目前只有 CHAR、VARCHAR ,TEXT 列上可以创建全文索引。全文索引并不是和MyISAM一起诞生的,它的出现是为了解决WHERE name LIKE “%word%"这类针对文本的模糊查询效率较低的问题。
HASH
由于HASH的唯一(几乎100%的唯一)及类似键值对的形式,很适合作为索引。HASH索引可以一次定位,不需要像树形索引那样逐层查找,因此具有极高的效率。但是,这种高效是有条件的,即只在“=”和“in”条件下高效
,对于范围查询、排序及组合索引仍然效率不高。
BTREE
BTREE索引就是一种将索引值按一定的算法,存入一个树形的数据结构中(二叉树),每次查询都是从树的入口root开始,依次遍历node,获取leaf。这是MySQL里默认和最常用
的索引类型。
RTREE
RTREE在MySQL很少使用,仅支持geometry数据类型,支持该类型的存储引擎只有MyISAM、BDb、InnoDb、NDb、Archive几种。
BTREE索引
就是一种将索引值按一定的算法,存入一个树形的数据结构中
BTREE又分两种,一种是B-TREE,如下图
另外一种是B+TREE,结构如下图
B+Tree相对于B-Tree有几点不同:
B 树和 B+ 树在数据结构上其实有一些类似,它们都可以按照某些顺序对索引中的内容进行遍历,对于排序和范围查询等操作,B 树和 B+ 树相比于哈希会带来更好的性能,当然如果索引建立不够好或者 SQL 查询非常复杂,依然会导致全表扫描。
与 B 树和 B+ 树相比,哈希作为底层的数据结构的表能够以 O(1)
的速度处理单个数据行的增删改查,但是面对范围查询
或者排序时就会导致全表扫描的结果,而 B 树和 B+ 树虽然在单数据行的增删查改上需要 O(log n)
的时间,但是它会将索引列相近的数据按顺序存储,所以能够避免全表扫描
。
既然使用哈希无法应对我们常见的 SQL 中排序和范围查询等操作,而 B 树和 B 树和 B+ 树都可以相对高效地执行这些查询,那么为什么我们不选择 B 树呢?
这个原因其实非常简单 —— 计算机在读写文件时会以页为单位将数据加载到内存中。页的大小可能会根据操作系统的不同而发生变化,不过在大多数的操作系统中,页的大小都是 4KB
当我们需要在数据库中查询数据时,CPU 会发现当前数据位于磁盘而不是内存中,这时就会触发 I/O 操作将数据加载到内存中进行访问,数据的加载都是以页的维度
进行加载的,然而将数据从磁盘读取到内存中所需要的成本是非常大的,普通磁盘(非 SSD)加载数据需要经过队列、寻道、旋转以及传输的这些过程,大概要花费 10ms
左右的时间。
我们在估算 MySQL 的查询时就可以使用 10ms
这个数量级对随机 I/O 占用的时间进行估算,这里想要说的是随机 I/O 对于 MySQL 的查询性能影响会非常大,而顺序读取磁盘中的数据时速度可以达到 40MB/s,这两者的性能差距有几个数量级,由此我们也应该尽量减少随机 I/O 的次数
,这样才能提高性能。数据库系统的设计者巧妙利用了磁盘预读原理
,将一个节点的大小设为等于一个页,这样每个节点只需要一次I/O就可以完全载入
B 树与 B+ 树的最大区别就是,B 树可以在非叶结点中存储数据,但是 B+ 树的所有数据其实都存储在叶子节点中,当一个表底层的数据结构是 B 树时,假设我们需要访问所有『大于 4,并且小于 9 的数据』:
如果不考虑任何优化,在上面的简单 B 树中我们需要进行 4 次磁盘的随机 I/O
才能找到所有满足条件的数据行:
当然我们可以通过各种方式来对上述的过程进行优化,不过 B 树能做的优化 B+ 树基本都可以,所以我们不需要考虑优化 B 树而带来的收益,直接来看看什么样的优化 B+ 树可以做,而 B 树不行
。
由于所有的节点都可能包含目标数据,我们总是要从根节点向下遍历子树查找满足条件的数据行,这个特点带来了大量的随机 I/O,也是 B 树最大的性能问题。
B+ 树中就不存在这个问题了,因为所有的数据行都存储在叶节点中,而这些叶节点可以通过『指针』依次按顺序连接,当我们在如下所示的 B+ 树遍历数据时可以直接在多个子节点之间进行跳转
,这样能够节省大量的磁盘 I/O 时间,也不需要在不同层级的节点之间对数据进行拼接和排序;通过一个 B+ 树最左侧的叶子节点,我们可以像链表一样遍历整个树中的全部数据,我们也可以引入双向链表保证倒序遍历时的性能
但是使用 B+ 树这种数据结构会增加树的高度从而增加整体的耗时,然而高度为 3
的 B+ 树就能够存储千万级别的数据,实践中 B+ 树的高度最多也就 4 或者 5,所以这并不是影响性能的根本问题。
MySQL 默认的存储引擎选择 B+ 树而不是哈希或者 B 树的原因:
O(1)
的单数据行操作性能,但是对于范围查询和排序却无法很好地支持,最终导致全表扫描;指针相互连接
,能够减少顺序遍历时产生的额外随机 I/O;NULL
的列时索引性能不好,通常在建表时会将字段置为 NOT NULL
,并指定 DEFAULT
默认值如果表字段定义的类型为字符串char
,但是在搜索时指定的 where
条件传入的参数却是数字类型INT
,那么会存在 隐式类型转换
的问题,从而造成不走索引的慢查询
联合索引违反了最左前缀匹配原则,例如建立了联合索引 (a, b, c)
,以下使用都会有问题
违反最左前缀匹配原则
where b = 1 and c = 1
联合索引 b 的范围查询会使范围条件字段之后的索引失效,导致之后 c 字段没有使用到索引,也就是只用到了索引 a 和 b,联合索引部分失效
where a = 1 and b > 1 order by c
对索引列的运算会导致索引失效,应该避免
运算符 | 含义 |
---|---|
+ | 加 |
- | 减 |
* | 乘 |
/ | 除以 |
!=、<> | 不等于 |
% | 取余 |
is null| is not null | 都会导致索引失效
like 查询 ‘name%’ 的百分号加在右边才走索引,可以使用覆盖索引来避免 ‘%name%’ 查询索引失效
例如select * from template t where ROUND(t.logicdb_id) = 1,不会走索引列,这种情况下应该建立基于函数的索引,如 ROUND(t.logicdb_id)
in 查询肯定会走索引,但是当 in 的取值范围较大而表数据较少时会导致索引失效,走全表扫描,该情况与下文 1.9 节类似。如果使用了 not in,则不走索引
条件中有or,即使其中有部分字段带索引也不会使用,要想使用or又想让索引生效,只能将 or 条件中的每个列都加上索引
对于数据量比较小的表,如果 MySQL 查询优化时认为全表扫描更快时也不会走索引
作用:是为了保证数据的完整性而实现的摘自一套机制,即(约束是针对表中数据记录的)
MySQL中的约束:
MySQL索引和约束的区别
每个InnoDB表都需要一个聚簇索引。该聚簇索引可以帮助表优化增删改查操作。
如果你为表定义了一个主键,MySQL将使用主键作为聚簇索引。
如果你不为表指定一个主键,MySQL讲索第一个组成列都not null的唯一索引作为聚簇索引。
如果InnoBD表没有主键且没有适合的唯一索引(没有构成该唯一索引的所有列都NOT NULL),MySQL将自动创建一个隐藏的名字为“GEN_CLUST_INDEX ”的聚簇索引。
因此每个InnoDB表都有且仅有一个聚簇索引。
所有不是聚簇索引的索引都叫非聚簇索引或者辅助索引。
在InnDB存储引擎中,每个辅助索引的每条记录都包含主键,也包含非聚簇索引指定的列。
MySQL使用这个主键值来检索局促索引。因此应该尽可能将主键缩短,否则辅助索引占用空间会更大。
一般来说用自增的整数型列作为主键列。
举例聚簇索引和非聚簇索引的区别。
注意:这里的主键是非自增的。普通索引K表示普通的索引 非 唯一索引。
主键是采用B+Tree的数据结构(请看左图),根据上文可以知主键为聚簇索引,物理存储是根据ID的增加排序递增连续存储的。
普通索引K也是B+Tree的数据结构(请看右图),但是它不是聚簇索引,因此为非聚簇索引或者辅助索引(聚簇索引只可能是主键,或者所有组成唯一键的所有列都为NOT NULL的第一个唯一索引,或者隐式创建的聚簇索引这三种情况)。他的叶子节点存储的是索引列的值,它的数据域是聚簇索引即ID。
假如普通索引k为非唯一索引
,要查询k=3的数据。需要在k索引查找k=3得到id=30。然后在左侧的ID索引树查找ID=30对应的记录R3。
然后K索引树继续向右查找,发现下一个是k=5不满足(非唯一索引后面有可能有相等的值,因此向右查找到第一个不等于3的地方),停止。
整个过程从K索引树到主键索引树的过程叫做回表
。
非聚簇索引检索数据时,检索一次本树再去聚簇索引树中检索一次,这样二次检索树结构,那么为什么不直接在非聚簇索引树叶子节点中存放行数据物理地址,这样只需要检索一次树结构就拿到行数据呢?
当在做新增数据时,因为底层是需要基于主键索引进行排序的,那么就可能导致原来某些数据对应的物理地址发生了变化,而这时候由于我们的非聚簇索引树的叶子节点直接存储了数据的物理地址,所以为了保证能获取到数据,还需要同时对非聚簇索引树叶子节点的地址进行一遍更新修改!
也就是说:之所以不在非聚簇索引树的叶子节点直接存放行数据的物理地址,是因为,存储数据的物理地址会随着数据库表的CRUD操作而不断变更,为了保证能获取到数据,这时必须要对非聚簇索引树相关叶子节点的地址进行一遍修改!而存主键,主键不会随着CRUD操作发生变化,宁愿多查一次树,也不要再修改一次树的结构!
每次使用辅助索引检索都需要经过2次B+树查找,看上去聚簇索引的效率明显要低于非聚簇索引,那么聚簇索引的优势何在呢?
- 1.由于行数据和聚簇索引树的叶子节点存储在一起,同一页中会有多条行数据,首次访问数据页中某条行记录时,会把该数据页数据加载到Buffer(缓存器)中,当再次访问该数据页中其他记录时,不必访问磁盘而直接在内存中完成访问。
-- 注:主键id和行数据一起被载入内存,找到对应的叶子节点就可以将行数据返回了,如果按照主键id来组织数据,获取数据效率更快!
-- 2.辅助索引的叶子节点,存储主键的值,而不是行数据的存放地址。这样做的好处是,因为叶子节点存放的是主键值,其占据的存储空间小于存放行数据物理地址的储存空间
为什么主键通常建议使用自增id?
-- 聚簇索引树存放数据的物理地址(xx1,xx2,xx3,xxx5)与索引顺序(1,2,3,5)是一致的,即:
-- 1.只要索引是相邻的,那么在磁盘上索引对应的行数据存放地址也是相邻的。
-- 2.如果主键是自增,那么当插入新数据时,只需要按照顺序在磁盘上开辟新物理地址存储新增行数据即可。
-- 3.而如果不是主键自增,那么当新插入数据后,会对索引进行重新排序(重新调整B+树结构),磁盘上的物理存储地址也需要重新分配要存储的行数据!
参考博客
路飞:MySQL索引分析以及相关面试题
MySQL/Oracle数据库优化总结(非常全面)
MySQL数据库:SQL优化与索引优化
MySQL聚簇索引和非聚簇索引的理解
为什么 MySQL 使用 B+ 树