MySQL 中这么多索引该怎么选择
作者:NPy 发布时间:2024-01-17 12:58:54
前言
索引的本质是存储引擎用于快速查询记录的一种数据结构。特别是数据表中数据特别多的时候,索引对于数据库的性能就愈发重要。
在数据量比较大的时候,不恰当的索引对于数据库的性能的影响是非常大的。在实际的应用中常常会遇见使用错误的索引而导致一系列问题,所以,选择正确的索引对于 MySQL 数据库来说相当重要。
下面我们就来一起聊聊在 MySQL 数据库中该怎么选择正确的索引。
在了解怎么选择索引之前,我先给你举一个例子。如果我们在字典中用拼音查询某一个字,首先我们得根据拼音字母进而找到对应的页码。索引也是这个原理。
当我们查询一条数据的时候,我们首先在索引中查询到对应的值,然后根据匹配到的索引去找到对应数据。
例如:
mysql> select name from city where fid = 1;
+--------------+
| name |
+--------------+
| 浦东新区 |
+--------------+
1 row in set (0.00 sec)
如果我们在fid
字段上建立索引,那么 MySQL 数据库就会使用索引找到fid = 1
的行,然后返回包含fid = 1
的行中的所有数据。
对于 MySQL 数据库来说,索引是由存储引擎实现的,所以不同的存储引擎提供的索引也不一样。下面我们就来了解一下 MySQL 数据库中各种索引的优缺点。
MySQL 单字段索引问题
在 MySQL 数据库中,索引不能够使用表达式,具体如下:
mysql> explain select * from city where fid + 1 = 2;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | city | NULL | ALL | NULL | NULL | NULL | NULL | 5 | 100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.01 sec)
mysql> explain select * from city where fid = 1;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | city | NULL | ALL | index_1 | NULL | NULL | NULL | 5 | 100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
从结果上讲,select * from city where fid + 1 = 2;
和 select * from city where fid = 1;
是完全一致的。
但是,在explain
表达式中可以看出select * from city where fid + 1 = 2;
是无法命中索引的。这是因为 MySQL 数据库无法解析fid + 1 = 2
这个表达式,所以我们在使用索引时,索引的列不能够是一个表达式。
总之,通常情况下,对于单个字段的索引来说,必须直接使用,不能够使用一个表达式。
组合索引
我们经常会遇见这样一个场景,假设要求查询fid=1
或者name='青浦区'
,这个时候我们查询的SQL
语句如下:
select * from city where fid = 1 or name = '青浦区';
这个时候,我们如果要想提高查询速度,一般就会选择在fid
字段和name
字段上分别加上一个索引,但实际上这种做法是不恰当的。
具体如下:
mysql> explain select * from city where name = '青浦区' or fid = 1;
+----+-------------+-------+------------+------+-----------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+-----------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | city | NULL | ALL | index_1,index_2 | NULL | NULL | NULL | 5 | 100.00 | Using where |
+----+-------------+-------+------------+------+-----------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
我们可以看出,本次查询并没有使用到任何索引。
具体步骤如下:
首先,根据
name
字段全表扫描查询出name = '青浦区'
包含的所有结果;其次,再根据
fid
字段全表扫描查询出fid = 1
包含的所有结果;最后,通过
UNION ALL
将所有的结果组合到一起并返回。
在这一过程中,MySQL 数据库需要通过全表扫描两次才能查询出结果。如果有更多的条件,查询的次数会更多。所以,在大多数情况下,多个条件查询在多个字段上建立索引并不能够提高MySQL
的查询性能。
为了解决多个字段同时需要索引的这一问题,MySQL 5.0
之后的版本中提供了一个组合索引
。它主要是将所有的字段组合建立一个索引,这样就可以直接利用索引匹配,而不需要全表扫描了。
具体如下:
mysql> explain select * from city where name = '青浦区' or fid = 1;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+--------------------------+
| 1 | SIMPLE | city | NULL | index | index_3 | index_3 | 772 | NULL | 5 | 36.00 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)
我们可以看出,利用了组合索引之后的查询是使用到了索引,具体如下:
首先,根据索引匹配出
name = '青浦区'
的所有的内容;第二次查询仍然是根据
fid
字段全表扫描查询出fid = 1
包含的所有结果;最后,通过
UNION ALL
将所有的结果组合到一起并返回。
在这一过程中,MySQL 数据库需要通过索引匹配两次就能查询出结果。所以,在大多数情况下,当有多个条件查询时,组合索引可以有效地提高MySQL
的查询性能。
讲完单字段索引和组合索引之后,下面我们可以聊一下唯一索引和普通索引的区别以及使用场景。
唯一索引和普通索引
说起唯一索引和普通索引,有朋友可能就非常熟悉。普通索引的主要特征就是提高了查询的速度,唯一索引的主要特征除了提高查询的速度外就是所有字段的值唯一。
那么,我现在提一个问题,唯一索引和普通索引都应该在什么场景下使用呢?一定是需要唯一值的场景下才使用唯一索引吗?下面我们就来对比着聊一下普通索引和唯一索引。
为了加强了解,我们从读写性能
方面来聊一下普通索引和唯一索引。
假设现在我们有一个订单系统,订单号唯一,那么我们看一下订单号在使用唯一索引和普通索引的情况下读的性能。
具体如下:
mysql> select * from sp_order where order_id = 52355096;
+----+----------+---------+--------------+-------------+-----------+------------+-------------+-------------+
| id | order_id | user_id | order_number | order_price | order_pay | pay_status | create_time | update_time |
+----+----------+---------+--------------+-------------+-----------+------------+-------------+-------------+
| 1 | 52355096 | 410 | DD52355096 | 332.44 | 2 | 1 | 1509051984 | 1507411372 |
+----+----------+---------+--------------+-------------+-----------+------------+-------------+-------------+
1 row in set (0.00 sec)
在 order_id
字段上设置唯一索引时,具体步骤如下:
MySQL
首先会在B-Tree
的子树上查询order_id = 52355096
;再根据查询到的索引值,通过主键索引查询出对应的记录;
组装结果并返回。
在 order_id
字段上设置普通索引时,具体步骤如下:
MySQL
首先会在B-Tree
的子树上查询order_id = 52355096
;继续向下匹配,直至匹配到
order_id 不等于 52355096
时;再根据查询到的索引值,通过主键索引查询出对应的记录;
组装结果并返回。
唯一索引与普通索引之间对比之后,可以发现:普通索引比唯一索引多了一个步骤,就是唯一索引匹配成功之后直接返回,而普通索引还需要往下继续匹配直至条件不符合为止。
那么,在这个过程当中,普通索引与唯一索引之间的性能差多少呢?其实是微乎其微的。这是因为B-Tree
算法将相邻或相近的数据都放在相邻的子树之中,索引查询性能相差无几。
聊完普通索引与唯一索引读的性能之后,我们再来聊一下写的性能。
具体如下:
mysql> update sp_order set order_price = '888' where order_id = 52355096;
对于MySQL
来说,写的过程如下。
首先判断需要修改的数据是否在
Buffer Pool
之中。如果该数据在
Buffer Pool
之中,则直接修改逻辑记录到Buffer Pool
中的数据。如果该数据不在
Buffer Pool
之中,MySQL 会将这一修改的过程记录在Change Buffer
之中。之后如果该条数据被查询到,则会将该修改过程merge
到Buffer Pool
之中,确保数据一致性。之后,再统一写入磁盘。
那么对于普通索引来说,完全适用于这一过程;但是对于唯一索引来说,按着这种方式修改数据则会影响 MySQL 数据库的性能。这是因为唯一索引在修改数据之前,还需要判断该条数据是否唯一,这样的话就需要将所有的数据全部扫描一遍,进而达到数据唯一。那么这样就不需要使用Change Buffer
了,因为在修改之前,唯一索引会将所有的数据全部读取到Buffer Pool
之中,直接在内存修改即可。但是不可避免的是,唯一索引会将所有的数据全部独到内存之中,无异于一次全表扫描。
于是,我们可以得出:唯一索引和普通索引都适用于读的场景,而唯一索引不适用于写的场景。
来源:https://juejin.cn/post/7146011609121423367


猜你喜欢
- os模块下有两个函数:os.walk()os.listdir()# -*- coding: utf-8 -*- &
- 这里只列举了部分方法,其他方法或python库暂时还没使用到1.不用库,直接打印:代码样例:import time#demo1def pro
- 1.GO中包的定义与介绍go中包分为三种:1.系统内置包 2. 自定义包 3.第三方包2. 包管理工具 go mod2.1 自定义包 (可以
- 从PDF读取文本内容和从已经有的文档生成新的PDF。需要用到的模块是PyPDF2.mstamy2/PyPDF2: A utility to
- MySQL多字段相同数据去重复MySQL多字段去重复实际上是单字段去重复的衍生,原理就是把多字段数据通过子查询合并为单字段的数据表,再通过单
- MySQL-8.0.22-winx64的数据库安装教程,供大家参考,具体内容如下1.安装步骤直接将安装包解压在安装目录之下。2.添加系统变量
- 自定义比较排序/运算符Python3和Python2相比有挺多变化。在Python2中可以直接写一个cmp函数作为参数传入sort来自定义排
- 1. 安装vim:# apt-get install -y vim-gnome2. 安装ctags,ctags用于支持tagli
- 在一篇文章中看到关于PHP引用的图解,对于加深对PHP引用的理解很有帮助,在这里备份一下。如果你对PHP的引用一点也不了解,可以先看我之前的
- getattr函数(1)使用 getattr 函数,可以得到一个直到运行时才知道名称的函数的引用。>>> li = [&q
- 今天发现sympy依赖的库mpmath里也有很多数学函数,其中也有在复平面绘制二维图的函数cplot,具体例子如下from mpmath i
- 一. 连接池的原理首先, HTTP连接是基于TCP连接的, 与服务器之间进行HTTP通信, 本质就是与服务器之间建立了TCP连接后, 相互收
- 求3721,163,1,4832,1980,2008,68686688,9999,17173,5173,8848中最大的数明白后,试着求一下
- Cookie 对象是一种以文件(Cookie文件)的形式保存在客户端硬盘的Cookies文件夹中的数据信息(Cookie数据)。Cookie
- 原文地址:30 Days of Mootools 1.2 Tutorials - Day 4 - Functions函数和MooTools
- JAN-1(January) FEB-2(February) MAR-3(March)APR-4(April) MAY-5(Ma
- python方法实现字符串反转方法一:反转列表法Python中,列表可以进行反转,我们只要把字符串转换成列表,使用reverse()方法,进
- javascript上下滑动广告效果 参数说明:客服果果(
- docker安装mysqldocker search mysql 搜索docker pull mysql:5.6
- Yahoo和Google都有自己的建设高性能网站最佳实践, 我不做赘述, 需要了解的自行查阅资料:Yahoo的: Best Practice