近期在开发后台管理系统中,碰到了很多需要根据关键字来模糊查询记录的需求,在数据量很大的时候,会看到后端同学提示模糊查询会很慢,本篇文章将基于 MySQL 数据库来分析为什么会存在这种情况。

文章不涉及到代码,理论知识为主。讲解的 MySQL 中的存储引擎是 InnoDB

MySQL 存储数据格式预览

数据库即是我们一个业务系统中存储数据的地方,例如一个 txt 文件、一个 excel 文件都能做到存储数据,但在开发中,会选用"专业的"的工具来存储,如 MySQL 数据库,通过可视化工具可以预览一张数据表大概长这样

mysql 查询不到的数据计0 mysql数据量很少查询却很慢_前端

像一个 Excel 表一样,有表头信息:

  • id(商品ID)
  • goods_title(商品的标题)
  • goods_sub_title(商品的副标题)
  • goods_url(商品的主图)
  • goods_price(商品的价格)

还有一些数据(随机生成的500万条),表的名称为 my_goods

假设下列场景,需要找到所有商品价格为 1300.1 的商品(随机选择的一个值),执行

mysql 查询不到的数据计0 mysql数据量很少查询却很慢_前端_02

执行该查询花费了 1.22s ,查询过程很简单,从第一条开始,一直找到最后一条数据,期间如果遇到了商品价格是 1300.1 的商品,则记录下来,500万条数据都访问完成之后,返回那些符合条件的数据(这个操作称为全表扫描)

可以想象到,这种查询的效率是非常低的,当然也不符合这样的"专业工具"的能力,在 MySQL 中,有一个概念称为"索引",即是为了提升查询速度的存在,接下来看一下索引的相关知识

索引、BTREE

事实上,my_goods 表在建表时,已经存在了一个针对 ID 字段建立的索引,通过下面这个需求可以体验到索引带来的速度查询提升:

找到商品 ID 是 4890000(随机选择的一个值)的商品

mysql 查询不到的数据计0 mysql数据量很少查询却很慢_mysql 查询不到的数据计0_03

可以看到查询时间小于 0.000s,速度有了不小的提升,那么是怎么实现的呢

通常,在一组有序 的数据中想查询某个值,可以用一个简单的算法,即二分查找(折半查找),如下列一组有序数据

1, 3, 5, 7, 9, 12, 15, 20, 22, 25, 30

通过二分查找的方式找到数字 20 所在的位置有下列过程:

  1. 找到这一组中间数(按照位置) 15,比较目标值与 15 的大小,20 > 15,目标值大于这个中间数,即需要查找的值在 15 的右边
  2. 接着将查找区间放在 15 - 30 中,同样取 15 - 30 中的中间数(按照位置) 22,20 < 22,目标值小于这个中间数,即需要查找的值在 22 的左边、15 的右边
  3. 接着在区间 15 - 22 中查找,取 15 - 22 的中间值 20(按照位置),找到目标值

上述过程,找到数字 20 只需 3 步,而如果从头开始找,找到数字 20 需要 8 步,由此提升了查找效率

在代码中与此相似的有一种数据结构:二叉树

二叉树有这些特点:

  • 二叉树中一个节点能存储一个值,且允许有两个子节点
  • 比自己小的值放在自己左边子节点
  • 比自己大的值放在自己右边子节点

将上面的数据通过二叉树来表示(角标的小数字可以忽略,关注圆内的数值):

代码程序会将上述数据转为二叉树结构,具体实现这里不展开

mysql 查询不到的数据计0 mysql数据量很少查询却很慢_mysql 查询不到的数据计0_04

接下来找到数值为 20 的过程如下:

mysql 查询不到的数据计0 mysql数据量很少查询却很慢_数据_05

比起原始的从第一条开始找数据找到最后一条有了一些提升,不过由于二叉树中的节点只能存储两个子节点(左右子节点),如果数据量很大的时候,这个树结构会非常深,在找树底下的数据时依然会存在一些效率问题

因此 MySQL 中使用的是 B+Tree (BPlusTree)结构,它是 BTree 的升级,但此篇文章从简出发,仅讨论 BTree 结构,以一颗最大度数为 5 的 BTree 为例,一个度数为 5 的 BTree 表示一个节点中最多 可以储存 4 个值(二叉树中仅 1 个),最多 可以有 5 个子节点(在二叉树中仅 2 个),随机插入一些数据:

mysql 查询不到的数据计0 mysql数据量很少查询却很慢_mysql 查询不到的数据计0_06

每个节点中存储的格式为 "指针、数据、指针、数据、指针..."(注意图中的线条是从每个数值的左右两边出发的,线条这里称为指针),指针和数据也是有序的,从小到大排列,也就是说某一个指针指向的节点中的所有数据是和该指针左右侧数据有大小关系的,例如下图中,红色指针指向的红色框节点中的所有值都是大于该红色指针左侧的数值 90,而小于该红色指针右侧的数据 169 的

mysql 查询不到的数据计0 mysql数据量很少查询却很慢_前端_07

这样的数据结构相比于二叉树来说,大大的减少了树的层级,找到数值 780 的过程如下:

mysql 查询不到的数据计0 mysql数据量很少查询却很慢_数据库_08

综上,索引的功能就是将某一个字段在表中的所有值拿出来,构造成一个 BTree 结构来提升查询效率

在文章前部分根据商品的 ID 查询商品比根据商品 goods_price 来查询商品快得多的原因就是在建表时为 ID 字段添加了索引而 goods_price 字段没有,这样查询 ID 可以通过 B+Tree 结构快速找到而 goods_price 只能通过全表扫描

模糊查询导致索引失效

那为什么模糊索引会导致查询效率变低呢?一般理解的模糊查询即是输入关键字,找表中数据中任意地方包含该关键字的数据

而上文介绍的二叉树、BTree 树的应用场景都是找一个固定的值,这个固定的值去比较节点的中的数据从而知道往左找还是往右找,因此模糊查询这种场景无法通过 索引来提升查询效率而退步为全表扫描

针对模糊查询的优化

事实上,也不是所有的模糊查询场景都会导致索引失效,如果指定某一个关键字它就是一些数据的开头,如需求是找以 a 开头的数据,至于这些数据后面是什么无所谓,这样依然可以利用索引

假设有下列数据

axx, xax, xxa

如通过下列数据增加索引(为了从简,通过二叉树来说明)

字母也是有顺序的,简单理解:x 在 a 的后面,因此 a < x,当第一位字母相同时比较第二位字母...

mysql 查询不到的数据计0 mysql数据量很少查询却很慢_mysql_09

找以 a 开头的数据:

从第一个节点开始查询,节点是否以 a 开头,结果不是,因为它存储的数据是 xax,但可以知道需要的数据(以 a 开头)如果存在,一定是在 xax 的左子节点(a < x),进而可以利用该结构大大的过滤掉查找的范围来提升效率

优化 - 关键字作为开头

关键字作为开头可以利用索引来提升查询效率,如在阿里云中,有一些输入的地方提示通过前缀来匹配

mysql 查询不到的数据计0 mysql数据量很少查询却很慢_数据库_10

优化 - 其它

还有一些技术选型方面的优化,如配合使用搜索能力更强的 Elasticsearch 等,待探索...