1、MySQL索引简介
MySQL在存储数据之外,还维护着满足特定查找算法的数据结构,这些数据结构以某种引用(指向)表中的数据,这样就可以通过数据结构上实现的高级查找算法来快速找到想要的数据。这种数据结构就是索引。
MySQL 索引使用 B+ 树结构,在 B+ 树基础上使用的是双向指针连接叶结点。
MySQL 索引为什么使用 B+ 树结构?
(1)Hash表:使用Hash表存储表数据Key可以存储索引列,Value可以存储行记录或者行磁盘地址。但是不支持范围快速查找,范围查找时只能通过扫描全表方式,哈希表并不适合作索引。
(2)二叉搜索树:二叉树特点是每个节点最多有2个分叉,左小右大。极端情况下二叉搜索树是可能会退化成一颗单分支的树(一直向右下插,或者左下插入),这时候时间复杂度就变成O(N)。
(3)平衡二叉树(红黑树):采用的是二分法的思维,最主要的特征是树的左右两个子树的层级最多相差1。当插入的数据过多时,树会很高,这样每层节点的数量就会很多。每个节点读取,都对应这一次磁盘的IO操作,这样,查询性能就会很差。磁盘IO 是制约数据库性能的主要因素。
(4)B树(多叉平衡查找树):每个结点存储M/2到M个关键字,非叶子结点存储指向关键字范围的子结点;所有关键字在整颗树中出现,且只出现一次,非叶子结点可以命中。
B树的特点:
1.关键字集合分布在整颗树中;
2.任何一个关键字出现且只出现在一个结点中;
3.搜索有可能在非叶子结点结束;
4.其搜索性能等价于在关键字全集内做一次二分查找;
(5)B+树:B+树和B树最主要的区别在于非叶子节点是否存储数据的问题。B+树为叶子结点增加链表指针,所有关键字都在叶子结点中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中;
B+树的特点:
- 所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字是有序的;
- 不可能在非叶子结点命中;
- 非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层;
- 更适合文件索引系统;
B+树的单个节点能存储更多信息,减少了磁盘 IO 的次数,从而提升了查找速度,而且叶节点形成有序链表,非常适合进行范围查询。
2、索引的优缺点
优点:
提高数据的检索效率,降低数据库的IO成本
缺点:
索引会占据磁盘的空间
索引虽然会提高查询效率,但是会降低更新表的效率。比如每次对表进行增删改操作,MySQL不仅要保存数据,还有保存或者更新对应的索引文件。
3、索引分类
MySQL中的索引可以大致分为以下几类:主键索引、唯一索引、普通索引、全文索引、组合索引、空间索引。
普通索引是由KEY或INDEX定义的索引,是MySQL的基本索引类型,其值是否唯一和非空由字段本身的约束条件所决定。
CREATE INDEX idx_username ON users (username);
唯一索引是指由UNIQUE定义的索引,该索引所在字段的值必须是唯一的。
全文索引是由FULL TEXT定义的索引,只能创建在CHAR、VARCHAR或TEXT类型的字段上。
CREATE FULLTEXT INDEX ft_idx_content ON articles (content);
主键索引 PRIMARY KEY,它是一种特殊的唯一索引,不允许有空值。定义主键后,MySQL会自动为这个列创建一个主键索引。建议使用int/bitint类型自增id作为主键,避免使用uuid等无序数据作为主键。有序主键能保证顺序io提升性能,无序主键是随机io,会导致聚簇索引的插入变成完成随机和频繁页分裂。
组合索引是在表中多个字段上创建索引,只有在查询中使用了这些字段中的第一个字段时,该索引才会被使用。
空间索引是由SPATIAL定义的索引,它只能创建在空间数据类型的字段上。MySQL中空间数据类型有四种:GEOMETRY、POINT、LINESTRING和POLYGON。创建空间索引的字段必须将其声明为NOT NULL,并且只能在存储引擎为MyISAM的表中创建。
CREATE SPATIAL INDEX sp_idx_location ON parks (location);
前缀索引是指对于一个列的值,只取其前几个字符建立索引。查询时也需要使用该前缀才能使用索引优化。
CREATE INDEX username_prefix_idx ON user (username(10));
SELECT * FROM user WHERE username LIKE 'sun%';
以下 SQL 查询语句无法使用该前缀索引进行优化:
SELECT * FROM user WHERE username LIKE '%sun%';
- 覆盖索引:如果一个索引包含了查询所需的所有数据,那么这个索引被称为覆盖索引。查询可以直接通过索引来获取数据,无需回表查询。
对于以下查询语句
SELECT order_no, create_time, total_amount
FROM orders WHERE order_no = '123456';
建立索引,包含order_no, create_time, total_amount三个字段
CREATE INDEX orders_idx ON orders (order_no, create_time, total_amount);
无论是返回的数据还是过滤个条件都是索引字段,查询可以直接使用索引来获取数据,不需要访问表的其他部分。
4、索引的设计原则
对数据量大并且查询比较频繁的表建立索引
对常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引
尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率就越高
如果是字符串类型的字段,字段的长度较长,可以建立前缀索引
尽量使用复合索引,减少使用单列索引,查询时,复合索引可以索引覆盖,减少回表查询的频次,提高查询效率
要控制索引的数量,索引并非多多益善,索引越多,维护索引结构的空间就越大,维护代价就越大,会影响到增删改的效率
如果索引列不能存储 NULL 值,那么在建表前就要使用 not null 来进行约束,当MySQL的优化器知道每列是否包含 NULL 值时,它可以更好地确定哪个索引最有效地用于查询。
5、正确使用索引
(1)最左前缀匹配原则
最左前缀法则指的是从索引的最左列开始,并且不跳过索引的列,如果跳过了某一列,索引将会部分失效(后面的字段索引失效)。只要复合索引最左的列在查询条件中,就会走这个复合索引,无论查询条件列的先后顺序,只要出现最左匹配的列就可以走这个索引,MySQL会一直向右匹配,直到遇到范围查询(< 、>、between、like等)就停止匹配。
-- 只有a走联合索引
select * from table where a>1 and b=2 and c=3
-- 不会走联合索引
select * from table where b=2 and c=3
(2)禁止在索引字段上做数学运算或函数运算
数学运算或函数运算会对字段进行计算,使得MySQL无法通过直接比较索引来确定查询结果
select * from table where age < 23;
select * from table where age + 1 > 50;
select * from table where month(updateTime) = 7;
常见的隐式类型转换:
select * from table where oplogid=123456
oplogid这个字段上有索引,但是explain的结果却显示这条语句会全表扫描。原因在于oplogid的字符类型是varchar(32),比较值却是整型,故需要做类型转换。在MySQL中字符串和数字进行比较的话是将字符串转换成数字,相当于下面这条语句(对索引字段做了函数运算,所以会出现索引失效):
select * from table where cast(oplogid as signed int)=123456
常见的隐式字符编码转换:表Tradelog字符集是utf8,表Tradedetail的字符集是utf8mb4,由于utf8mb4是utf8的超集,当两个表的字符串在做比较时,MySQL会先把utf8字符集的字符串转换成utf8mb4再做比较。所以,它也属于对索引字段做函数操作,索引会失效。
建议数据库和表的字符集统一使用utf8mb4,兼容性更好,统一字符集可以避免由于字符集转换产生的乱码。
(3)like使用
一般情况下不鼓励使用like,如果要使用的话避免以通配符%和_开头,即like '%xxx%',它不会走索引,而like 'xxx%'能走索引。
(4)避免负向查询
负向查询指的是在查询中使用不等于(<>)或不包含(NOT IN、NOT EXISTS等)的条件,即查询不满足某些条件的记录。负向查询通常会导致数据库执行全表扫描,影响查询性能。
(5)避免使用select *
select * 无法利用覆盖索引优化,还会为服务器带来额外的IO、内存和cpu的消耗。
(6)组合索引
对于组合索引,当前where查询中过滤性更好的字段在索引字段顺序中位置越靠前越好,
尽量选择能够包含在当前查询中where子句中更多字段的索引。
(7)关联查询优化
内连接时,mysql.会自动把小结果集的选为驱动表,所以大表的字段最好加上索引。左外连接时,左表会全表扫描,所以右边大表字段最好加上索引,右外连接同理。我们最好保证被驱动表上的字段建立了索引。
book表是被驱动表:
6、索引使用分析
查看SQL语句有没有走索引的语法格式为:explain 查询语句
select_type 表示查询方式,type 表示访问类型,key 表示索引,possible_keys 表示可能走的索引
由上图可知:查询为简单查询,访问类型为ALL(全表扫描),key 索引为 NULL,说明没有走索引。
type:
从左到右,性能越来越好
ALL:扫描全表
index : 扫描全部索引树
range: 扫描部分索引,在索引范围扫描,对索引的扫描开始于某一点,返回匹配值域的行,常见于 between、<、>等查询
ref : 使用非唯一索引或非唯一索引前缀进行的查找,不是主键或者不是唯一索引
eq_ref:唯一性索引扫描,遂于每个索引键,表中只要一条记录与之匹配,常见于主键或唯一索引扫描
const,system : 单表中最多有一个匹配行,查询起来非常迅速,例如根据主键或唯一索引查询。system 是const类型的特例,当查询的表只有一行的情况下,使用system
NULL:不用访问表或者索引,直接就能得到结果
Extra:
Using index: 表示使用索引,如果只有 Using index ,说明他没有查询到数据表,只用索引表就完成了这次查询,这个也叫索引覆盖
Using where: 表示条件查询,如果不读取表中的所有数据,或者不是仅仅通过索引树就能获得所许需要的数据的时候,则会出现 Using where