MySQL索引及其优化
MySQL的基础架构
- Server层 :核心服务功能与跨引擎功能的实现(如所有的内置函数、存储过程、触发器等)
- 连接器:管理连接,权限验证
- 命中缓存:命中缓存则直接返回结果(8.0版本后删除)
- 分析器:词法分析,语法分析
- 优化器:执行计划生成,索引选择
- 执行器:操作引擎返回结果
- 存储引擎:数据的存储与提取,插件式的架构模式
- InnoDB *
- MyISMA
SQL语句的执行流程
一条SQL查询语句是怎么执行的
SELECT * FROM student WHERE id = 12004030124;
在整体架构层面分析此条语句的执行过程
连接器
连接器负责跟客户端建立连接、获取权限、维持和管理连接。并负责校验用户身份。
mysql [-h$ip -p$port] -u$user -p [password 不建议把密码写在此条语句]
命令行中的mysql是客户端工具,在执行完词条语句之后将会与服务器进行连接,即通过TCP三次握手建立连接之后会校验用户身份
连接器会验证用户输入的用户名和密码,若存在问题则会返回错误并且客户端程序结束执行
在通过身份登录验证之后,连接器会查询该用户的权限并保存,接下来所有的操作权限都基于此次查询,即使用户权限发生了更改,在此次连接没有重新的建立的情况下,用户的权限不会直接更改。
查询缓存
在建立好连接之后就可以进行sql语句的执行了。
执行逻辑将会来到第二部:查询缓存
在拿到查询请求的适合,会先去查询缓存,缓存中以key-value的形式存储查询操作和查询结果,如果缓存中记录了执行过此查询操作,则直接返回该查询操作所对用的结果,即不需要再向下执行复杂的查询工作,可以直接返回查询操作的结果大大的简化了执行时间,如果说没有命中缓存,则向下执行通过IO过程查询,在返回时将查询结果返回到查询缓存中存储。使用缓存是需要代价的,而且往往弊大于利,因为每次表更新都会将查询结果清空,所以对于更新压力大的业务数据库来说,缓存命中率极低,在很多情况下查询缓存甚至是一种性能负担,所以在非表更新极少的静态表之外,不建议使用查询缓存。
注:在mysql8.0之后查询缓存被彻底的删除
分析器
在没有命中缓存的情况下,就到了分析器
先进行 “词法分析”,mysql需要知道sql语句中的内容,识别关键字,例如识别到select关键字,并分析出student为表名,id为列名。在完成”词法分析”后,分析器将进行”语法分析”,分析sql语句是否存在语法问题,若存在则返回错误提示并终止执行。
优化器
在分析完执行内容之后,优化器会进行相应的优化
优化器会选择如何使用索引和如何进行表连接顺序,并选择执行效率最高的方案执行语句。
执行器
在优化器选择好执行方案之后,执行器就开始执行sql语句
首先执行器会判断当前用户是否拥有查询此表的权限啊,若无权限在返回错误并终止执行。若有权限打开表使用存储引擎的接口进行操作。例如在此条语句中,因为表中没有定义索引,执行过程大致如下:
- 调用存储引擎接口读取表的第一行,判断是否与条件匹配,即id是否等于0
- 调用存储引擎接口读取下一行,判断是否匹配,重复执行直到读取完最后一行
- 将查询到的结果集返回给客户端
如果表中存在索引,查询方式也大致相同,调用的是满足条件的第一行和下一行
索引
什么是索引
在关系数据库中,索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构,它是表中一列或多列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。
索引(Index)是帮助MySQL⾼效获取数据的数据结构。提取句⼦ 主⼲,就可以得到索引的本质:索引是数据结构
索引的作用
- 保证数据的准确性
- 提高检索速度
- 提高系统性能
索引的类型
system > const > eq_ref > ref > range > index > all
InnoDB索引的数据结构
⼀般来说,索引本身也很⼤,不可能全部存储在内存中,因此索引往往以索引⽂件的形式存储的 磁盘上。这样的话,索引查找过程中就要产⽣磁盘I/O消耗,相对于内存存取,I/O存取的消耗要⾼⼏个 数量级,所以评价⼀个数据结构作为索引的优劣最重要的指标就是在查找过程中磁盘I/O操作次数的渐进 复杂度。换句话说,索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数。
分析: 有哪些数据结构可以用来作为索引的存储容器
HashTable
- 优点:HashTable是字典(dict)的一种经典实现,通过对Key进行散列值计算,我们可以直接得到对应数据的存放位置,可以实现时间复杂度为O(1)的极快的查找速度。
-
缺点:
- 不支持模糊匹配,由于哈希计算没有局部特性,例如hash(枫阿雨)跟hash(枫阿)没有关系
- 不支持范围匹配,没有顺序性
- 不支持组合索引,因为hash值需要合在一起计算,所以不能支持最左匹配原则
- 哈希冲突问题
- 不支持模糊匹配,由于哈希计算没有局部特性,例如hash(枫阿雨)跟hash(枫阿)没有关系
跳表
- 优点:跳表基于链式结构,可以多点之间的直接连接,每个节点可以有多个指针指向不同的next节点,查询的时间复杂度为O(n),但如果节点指针设计的好,可以跳过某些不需要查询的节点,直接定位到数据,可以让n的值变小,从而带来比较大的性能提升
-
缺点:
- MySQL数据库的存储介质在磁盘当中,而链式结构的结构体存放在内存当中,而且MySLQL进行数据索引的时候是以块的形式,即每个块为16KB的内存页,然后在内存页中进行数据的定位,而跳表所使用的是我们所谓的链表中的Node节点,而且指针管理非常复杂,不适用于磁盘存储介质
- MySQL中涉及的查询较多且复杂,如果使用联合索引在跳表的数据结构下,假设有两个字段做联合索引,首先我们需要按首字段进行排序,基于此基础上再对第二个字段进行排序,如果在查询过程中使用跳表,除了要维护第一个的多个节点的跳跃指针,还要想办法维护第二个节点的跳跃指针,指针的管理将会非常的困难,而且还要分别进行不同列的标记。如果多个字段的联合索引则更复杂。
- 客观上,跳表这种数据结构的出现并应用的时间较晚,此时MySQL已有了自己的实现方式
红黑树
- 优点:红黑树是一种近似平衡(不完全平衡),结点非黑即红的树,它的树高最高不会超过 2logn,因此查找的时间复杂度为 O(logn),无论是增删改查,它的性能都十分稳定。
-
缺点:
- 因为二叉树的只有两个子节点,相同存储容量时,树的高度太高,每次节点的访问都对应着一次磁盘IO,红黑树属于一种二叉树,虽然拥有稳定平衡的功能,但是大量的磁盘IO在应用程序中是灾难性的,即若红黑树的高度为20,那么最坏情况下读取一个数据需要进行20次磁盘IO这显然是无法接受的
B-树
- 优点:B-树是一种专门为磁盘数据读取设计的一种度为n的多路平衡查找树。既然二叉树因为每个结点最多只有两个子结点,最终在存储大量数据时导致树高太高,因此不适合当做 MySQL 的索引,那么让树的每个结点尽可能多的拥有多个子结点,这样在大量储存数据时,树高就相对低很多了,磁盘IO的次数也就大大减少
- 缺点:
- 每个节点中既要存索引信息,又要存其对应的数据,如果数据很大,那么当树的体量很大时,每次读到内存中的树的信息就会不太够。
- B树遍历整个树的过程和二叉树本质上是一样的,仍需要中序遍历,B树相对二叉树虽然提高了磁盘IO性能,但并没有解决遍历元素效率低下的问题。
B+树
- 优点:B+树相比B树,本质上是一样的,区别就在与B+树的所有根节点都不带有任何数据信息,只有索引信息,所有数据信息全部存储在叶子节点里,这样,整个树的每个节点所占的内存空间就变小了,读到内存中的索引信息就会更多一些,相当于减少了磁盘IO次数,且所有叶子节点都会在同一层,B+树会以一个链表的形式将所有叶子节点的信息全部串联起来,这样,遍历所有数据信息只需要顺序遍历叶子节点就可以了。不仅如此,B+树还有一个相应的优质特性,就是B+树的查询效率是非常稳定的,因为所有信息都存储在了叶子节点里面,从根节点到所有叶子节点的路径是相同的。
-
缺点:B+树在非叶子节点上不存储数据,有些时候会相对B树有更多的磁盘IO
索引组织表
相比于正常存储方式。优化了遍历搜索的性能损失。
聚集索引
辅助索引(非聚集索引)
回表
通过辅助索引定位到聚集索引
索引的优化
使用层面
索引列选择
选择适当的索引,索引应有区分度。如果某个字段的取值范围很广,几乎没有重复,即属于高选择性 的字段适合作为索引
最左前缀原则
如果进行模糊查询,查找 name 的第一个字为”孙“开头的所有人的id,即SQL语句为
SELECT id FROM student WHERE name like '孙%';
由于在B+ 树结构的索引中,索引项时按照索引定义里面出现的字段顺序排序的,索引在查找的时候,可以快速定位到ID为 100 的 “孙a” ,然后直接向右遍历所有姓名为 “孙” 开头的人,直到条件不满足位置。也就是说,我们找到第一个满足条件的之后,直接向右遍历就可以了,由于索引是有序的,所有满足条件的人都会聚集到一起。
而这种定位到最左边,然后向右遍历寻找,就是我们所说的最左前缀原则。
联合索引
联合索引时指对表上的多个列进行索引
ALTER TABLE buy_log ADD KEY(user_id);
ALTER TABLE buy_log ADD KEY(user_id, buy_date);
ALTER TABLE buy_log ADD KEY(user_id, buy_date, price);
情况1:如果只对于userid进行查询
SELECT * FROM buy_log WHERE user_id = 2;
索引选择:优化器的最终选择是索引userid,因为该索引的叶子节点包含单个键值,所以理论上一个页能存放的记录应该更多。
情况2:对于userid查询并根据buy_date排序,或 对于userid和buy_date查询并根据price排序
SELECT * FROM buy_log WHERE user_id = 1 ORDER BY buy_date DESC LIMIT 3;
SELECT * FROM buy_log WHERE user_id = 1 AND buy_date = 2020 ORDER BY price DESC LIMIT 3;
索引选择:优化器最终选择的是联合索引(user_id, buy_date),因为在联合索引中buy_date已经排序好了。根据该联合索引去除数据,无须再对buy_date做一次额外的排序操作。
情况三:对于userid查询并根据price排序
SELECT * FROM buy_log WHERE user_id = 1 ORDER BY price DESC LIMIT 3;
此时联合索引不能直接得到结果,其还需要执行一次排序操作,因为索引(user_id,price)并未排序
覆盖索引
即从辅助索引中就可以得到查询的记录(此时不能够使用select * 操作,只能对特定的索引字段进行select),而不需要查询聚集索引的记录。使用覆盖索引的一个好处是辅助索引不包含整行记录的所有信息,故其大小要远小于聚集索引,因此可以减少大量的IO操作。
SELECT COUNT(*) FROM buy_log;
InnoDB存储引擎并不会选择通过查询聚集索引来进行统计。由于buy_log表上还有辅助索引,而辅助索引远小于聚集索引,而辅助索引远小于聚集索引,选择辅助索引可以减少IO操作。
用一句人话概括:直接从辅助索引拿数据
避免索引失效
- Like 开头以%开头 导致失效
SELECT id FROM student WHERE name = ‘%三’;
- OR 前后有任意一项不是索引字段则失效
-- age 为索引字段 SELECT id FROM student WHERE age > 10 OR age < 20;-- 索引正常使用 SELECT id FROM student WHERE age > 10 OR score > 80;-- 索引失效 SELECT id FROM student WHERE score > 80 OR age > 10;-- 索引失效
- 联合查询
-- 联合索引(age,score)id为主键 SELECT id FROM student WHERE age > 10 AND score > 80; -- 索引正常使用 SELECT id FROM student WHERE score > 80; -- 索引失效 -- 没有从联合索引的首元素开始进行索引,则索引失效
- 索引字段进行算数运算 导致索引失效
SELECT id FROM student WHERE age = 10; -- 索引正常使用 SELECT id FROM student WHERE age - 1 = 10; -- 索引失效
- NOT 取负面(取非)的结果集 导致索引失效
SELECT id FROM student WHERE age != 10; -- 索引失效 SELECT id FROM student WHERE age <> 10; -- 索引失效 SELECT id FROM student WHERE age IS NOT 10; -- 索引失效
- NULL 可能 导致索引失效
-- 并不会百分百造成索引失效, -- MySQL不会对NULL值创建索引,即NULL值在创建索引时会被抛弃 -- (所以逻辑上永远不会为空的字段应加上非空约束,如有逻辑上为空的情况,建议设置默认值约束) SELECT id FROM student WHERE age IS NULL; -- 索引失效 SELECT id FROM student WHERE age IS NOT NULL; -- 索引失效
- 方法函数 索引列使用内置函数时 可能 导致索引失效
-- SELECT birth_date FROM student WHERE DATE_ADD(birth_date, -1) = CURRENT_DATE(); -- 索引失效 SELECT birth_date FROM student WHERE birth_date = CURRENT_DATE() + 1; -- 正常使用
- 类型转换 导致索引失效
-- phone varchar(11) -- MySQL中的内置函数默认自动把字段值数字类型转换为字符,以匹配phone的类型 -- 因为字符串转整型会出现很多种情况,如"111" " 111" "111a"都会转换为整型的 111,故此时不使用索引 SELECT phone FROM student WHERE phone = 12345678901; -- 索引失效 SELECT phone FROM student WHERE phone = '12345678901'; -- 索引正常使用 -- 注:当隐式转换时 整型 转 字符串 则不受影响,因为整型转字符串的结果是唯一的
- 同一语句在某些版本 可能 导致索引失效
-- age 为索引字段 SELECT * FROM student age > 3;
是否使用索引?
首先,age是辅助索引,根据age查找到的是主键,仍需回表去查询聚簇索引来获得整行的数据。所以有两种可能。
- 因为age已经排好序,通过索引查找 age > 3 效率很高。所以有使用age索引的必要。
- 因为有回表的过程,排好序对应的主键id未必是排好序的,仍需多次额外的io查找,不如直接遍历聚簇索引
两种理由都有道理。MySQL在 5.6版本 前后有不同的选择。
-
5.6版本 之前,无论是否有age的辅助索引,都要走全表扫描,即遍历聚簇索引,不会使用辅助索引。
如果直接回表的话会有3次IO多次查询,如果有n条数据需要回表,即额外需要 n* 3 = 3n 次IO,代价较大(这其实就是离散读的概念),
如果直接全表扫描的话就是聚簇索引3次IO定位到叶子节点,然后根据叶子节点的链表遍历,可能也有多次IO,也并非轻松
所以MySQL设置了一个阈值,当需要查询的数据占到总数据的一定量的时候就会全表扫描,没有到达阈值的时候就根据辅助索引回表多次查询。所以可能失效也可能不失效。
-
5.6版本 之后,引入了
Multi-Range Read (MRR)
优化,专门为解决离散读的问题。执行上述查询语句的时候会进行3次IO使用辅助索引找到所有 age > 3 的主键id,然后将这些数据放在缓存中,并将这些id进行了排序(在InnoDB引擎层面进行这些操作)。然后根据这些排序的主键id进行查询,省略了多次回表的过程。在支持MRR优化后,针对离散读的场景,能够优化10倍以上的效率。
存储引擎层面
MRR优化(针对离散读)
离散读:
假设表:t_index。其中 id 为主键;c1 与 c2 组成了联合索引(c1,c2);此外 c1 还是一个 单独索引。
进行如下查找操作:
SELECT * FROM t_index WHERE c1 > 1 AND c1 < 100000;
在最后的索引使用中,优化器选择了 PRIMARY id 聚集索引,也就是表扫描(table scan),而非 c1 辅助索引扫描(index scan)。
因为如果强制使用c1索引,就会造成离散读。具体原因在于用户要选取的数据是整行信息,而c1作为辅助索引不能覆盖到我们要查询的信息,因此在对c1索引查询到指定数据后,还需要一次书签访问来查找整行数据的信息。虽然c1索引中数据是顺序存放的,但是再进行聚簇索引查找的数据是无序的,因此变味了磁盘上的离散读操作。如果要求访问的数据量很小,则优化器还会选择辅助索引,但是当访问的数据占整个表中数据的蛮大一部分时(一般是20%左右),优化器会选择通过聚簇索引来查找数据。
MRR:
在MySQL 5.6之后开始支持Multi-Range Read(MRR)优化。MRR 优化的目的就是为了减少磁盘的随机访问,并且将随机访问转化为较为顺序的数据访问,这对于IO-bound类型的SQL查询语句可带来性能极大的提升。MRR可适用于eq_ref、ref、range级别的索引
索引的级别:system > const > eq_ref > ref > range > index > all
MRR优化的执行过程:
- 将查询得到的辅助索引键值存放于一个缓存中,这时缓存中的数据时根据辅助索引键值排序的。
- 将缓存中的键值根据RowID进行排序
- 根据RowID的排序顺序来访问实际的数据文件
MRR 还可以将某些范围查询拆分为键值对,以此来进行批量的数据查询。这样的好处是可以在拆分过程中,直接过过滤一些不符合条件的数据
SELECT * FROM student WHERE id >= 1000 AND id < 2000 AND age = 1000;
表 student 有 (id, age)的联合索引,因此索引根据id、age的位置关系进行排序。若没有MRR,此时查询类型为Range,SQL优化器会先将id大于1000且小于2000的数据都去除,即使age不等于1000。待取出行数据后再根据age条件进行过滤。这会导致无用的数据被取出。如果有大量的数据且其age不等于1000,则启用MRR优化会使性能有巨大的提升。
若启用MRR优化,优化器会先将查询条件进行拆分,然后再进行数据查询。久上述查询语句而言。优化器会将查询条件拆分为 (1000, 1000), (1001, 1000), (1002, 1000) , …, (1999, 1000),最后再根据这些拆分出的条件进行数据的查询。
优化策略:在非必要的情况,拒绝使用 select * ,在必须 select * 的情况下,尽量使用MySQL 5.6 以后的版本并开启MRR
ICP优化
和MRR优化一样,Index Condition Pushdown (ICP) 同样是MySQL 5.6开始支持的一种根据索引进行查询的优化方式。在之前的版本中,当进行索引查询时,首先根据索引来查找记录,然后再根据WHERE条件来过滤记录。在支持ICP后。MySQL数据库会在取出索引的同时,判断是否可以进行WHERE条件的过滤,也就是将WHERE的部分过滤操作放在了存储引擎层。在某些查询下,可以大大减少上层SQL层对记录的索取(fetch),从而提高数据库的整体性能
FIC(快速索引创建)优化
在5.5版本之前,MySQL数据库对于索引的添加或者删除的DDL操作,MySQL数据库的操作过程为以下几步:
- 首先创建一张新的临时表,表结构为通过命令ALTER TABLE新定义的结构
- 然后把原表中的数据导入到临时表
- 接着删除原表
- 最后把临时表重命名为原来的表名
如果对于一张大表进行索引的添加和删除操作,那么会需要很长的时间。更关键的是若有大量事务需要访问正在被修改的表,这意味着数据库服务不可用。
从InnoDB 1.0.x 版本开始支持一宗称为 FIC (Fast Index Creation)的索引创建方式
对于辅助索引的创建,InnoDB存储引擎会对创建索引的表加上一个 S锁 。在创建的过程中,不需要重建表,因此速度较之前提高很多,并且数据库的可用性也得到了提高。删除辅助索引操作就更难了,InnoDB存储引擎只需要更新内部视图,并将辅助索引的空间标记为可用(不影响辅助索引的使用,因为可读),同时删除MySQL数据库内部视图上对该表的索引定义即可
但是,由于FIC在索引的创建过程中对表加上了S锁,因此在创建的过程中只能对该表进行读操作,若有大量的事务需要对目标表进行写操作,那么数据库的服务同样不可用。此外,FIC反射光hi只限定于辅助索引,对于主键的创建和删除通用需要重建一张表
Online DDL(在线数据定义)
在MySQL5.6版本开始支持Online DDL操作,其允许辅助索引创建的同时,还允许其他诸如INSERT、UPDATE、DELETE这类DML操作,极大地提高了MySQL数据库在生产环境中的可用性
不仅是辅助索引,以下几类DDL操作东可以通过“在线”的方式进行操作:
- 辅助索引的创建与删除
- 改变子增值
- 添加或删除外键约束
- 列的重命名
使用语法:
ALTER TABLE tbl_name
| ADD{INDEX | KEY} [index_name]
[index_type] (index_col_name,...) [index_option]...
ALGORITHM [=] {DEFAULT | INPLACE | COPY}
LOCK [=] {DEFAULT | NONE |SHARED | EXCLUSIVE}
ALGORITHM 制定了创建或删除索引的算法,COPY 表示按照MySQL 5.1版本之前的工作方式,即创建临时表的方式。INPLACE 表示索引创建或删除操作不需要创建临时表。 DEFAULT 表示根据参数 old_alter_table 来判断是通过 INPLACE 还是 COPY的算法,该参数的默认值为OFF,表示采用个INPLACE的方式
LOCK 部分索引创建或删除时对表添加锁的情况:
- NONE
执行索引创建或者删除操作时,对目标表不添加任何的锁,即事务仍然可以进行读写操作,不会受到阻塞。因此这种模式可以获得最大的并发度。
-
SHARE
和之前的FIC类似,执行索引创建或删除操作时,对目标表加上一个S锁,对于并发地读事务,依然可以执行,但是遇到写事务,就会发生等待操作。
-
EXCULSIVE
在EXCULSIVE模式下,执行索引创建或删除操作时,对目标表加上一个X锁。读写事务都不能进行,因此会阻塞所有的线程,这和COPY方式运行得到的状态类似,但是不需要像COPY方式那也创建一张临时表。
-
DEFAULT
DEFAULT模式会先判断当前操作是否可以使用NONE模式,若不能,则判断是否可以使用SHARE模式,最后判断是否可以使用EXCLUSIVE模式,也就是说DEFAULT会通过判断事务的最大并发性来判断执行DDL的模式。
InnoDB存储引擎实现Online DDL 的原理是在执行创建或者删除操作的同时,将INSERT、UPDATE、DELETE这类DML操作日志写入到一个缓存中,待完成索引创建后再将重做应用到表上,以此达到数据的一致性。
由于Online DDL 在创建索引完成后再通过重做日志达到数据库的最终一致性,这意味着再索引创建过程中,SQL优化器不会选择正在创建中的索引。
MySQL表设计与索引调优
表的设计阶段
单行数据量的要求,MySQL底层的内存页的大小为16KB,所以一条数据如果是16KB,则一个内存页只能存储一条数据,这是不可接受的,如果1条数据为1KB则能存储16条数据。之所以提到内存页,因为MySQL的每次IO就是读取一个内存页的数据,所以要保证单行数据量的值要尽量的小。为此,在初期需求分析的时候就要做到数据尽量的小,比如存放一个UUID,如果要存放32位的UUID,则直接定死为32位,不要浪费存储空间,否则当数据达到一定规模的时候会影响到B+树的整体结构,B+树拥有高扇出性,每一个节点对应着16KB的内存页,对于3层高的B+树,根节点存放着16KB的主键与指针,假设主键为bigint8个字节,指针固定为6个字节,所有对应着有16KB / 14B 约等于 1170 个指针,每个指针指向一个节点,第二层的每个节点的结构与根节点均相似,所以第二层则总共扇出1170 * 1170 个页子节点,若每条数据长1KB,则一个三层高的B+树能够存储 1170 * 1170 * 16 条数据,约等于两千万多条,如果一个数据大小为16KB则存储容量就大打折扣了。所以在设计阶段就要保证数据大小。在做设计的时候也应该或用枚举,比如性别男女分别用0和1来表示,一可以保证检索快速二可以控制数据量的大小,就能增加相同高度的B+树的数据容纳量。
数据操作时索引的设计
MySQL中的三种索引,聚簇索引、辅助索引、覆盖索引。
聚簇索引,每张表只有一个,即 PRIMARY KEY;辅助索引,是开发时额外增加的索引,每个辅助索引对应着一颗B+树;覆盖索引,本质是没有树的。在SQL的设计的时候首先要确保一点,不要使用SELECT *,它必须要走聚集索引,也就是要全表扫描,只有在聚集索引中才有全部的数据。如果要使用的话尽量使用5.6以后的版本,因为5.6以后有了针对离散读的MRR优化和ICP优化,这样 SELECT * 的查询速度会更快。着重提一下覆盖索引,在对于索引进行设计的时候,如果需要查询n个字段,如果保证这个n个字段都可以作为索引的话就尽量设置索引,因为在辅助索引中储存索引的值和主键的值,就可以避免回表查询。在书写SQL的时候,多使用执行计划查看索引是否失效,因为在特定的场景和函数下,MySQL的索引可能会失效