当前位置: 首页 > 科技观察

分页+模糊查询有坑!你知道吗?

时间:2023-03-18 01:13:44 科技观察

前言不知道大家有没有用过Mysql的like语句进行模糊查询?不知道大家有没有对查询结果进行分页?模糊查询,加上分页处理,会有意想不到的陷阱。不信我们继续往下看。之前我提供了一个品牌查询接口,用于前端品牌选择控件。当时出于性能考虑,怕前端控件一次加载太多品牌,导致页面卡顿。因此,品牌查询界面进行了分页。一开始brand表的数据比较小,没有问题。后期需要添加产品,用户可以在品牌下拉选择控件中输入自定义品牌。在用户添加品牌之前,需要先进行检查。如果该品牌存在,则将使用现有品牌。如果不存在,请添加品牌。(这里是精确匹配)这个需求很简单,很容易实现。后来增加了产品需求,需要通过名称来模糊查询品牌。该功能上线后,使用了很长时间,没有任何问题。突然,无意中的一天,这个函数出错了。到底发生了什么?1、一天下午在案发现场,运营在测试反馈中发现一个问题:明明苏珊这个品牌已经存在,但是当用户输入关键词:苏珊时,系统却不允许用户直接选择已有的牌子,而是多了一个牌子,叫做:苏三的定制牌子。我过去一看,果然有问题。过了一段时间,定位到原因,初步判断是分页问题。搜索关键词:苏三,出现好几页数据,吓了我一跳,品牌表怎么会有这么多数据。我检查了数据库。其实数据量不是特别大,但是有的品牌名比较特殊,有的品牌名是由多个品牌名拼接而成,比如:苏三、李四或者苏三、李四、王五.这是一个品牌。其实问题是品牌名称建设不规范造成的,但是运营修改品牌已经不可能了,目前的问题只能通过技术手段来解决。查询第一页数据sql:select*frombrandwherenamelike'%SuSan%'orderbyedit_datedesclimit5;执行结果:我们可以看到图中没有等于苏三的数据。注:为了演示,这里给出的每页大小为5,实际场景中并非如此。查询第二页数据sql:select*frombrandwherenamelike'%SuSan%'orderbyedit_datedesclimit5,5;执行结果:从图中可以看出,在第二行,有两个字正好等于苏三字数据。当用户搜索关键词:Susan时,前端页面正在调用品牌查询接口,pageNo默认为1。由于能匹配关键词的数据太多,无法返回第一页,需要多页才能全部返回。前端获取到第一页的数据后,与关键词:Susan进行比对,发现没有与Susan相等的品牌。这样下拉控件会自动添加一个品牌:Susan,右侧添加自定义标签。这导致了一个问题。明明有一个Susan品牌,但用户也可以定制一个Susan,而不是直接选择。2、想想关键字123Susan,可以通过模糊查询来查询,但是因为品牌界面是分页的,完全匹配的品牌:Susan出现在第二页,从而导致问题。如果要解决这个问题,让它出现在首页不就可以了吗?这时,有以下解决办法。(1)选项1是分页查询品牌界面,pageSize为5,为什么不把pageSize调大一点呢?比如改成:200、500等。这样通过苏三关键词进行模糊查询时,结果基本都在首页。这样可以很快解决问题。但是有一个缺点:如果这次增加了pageSize,但是查询关键词的品牌又出现在了第二页怎么办?pageSize不可能一直变吧?(2)方案二将分页查询接口的数据拆分为两部分:精确查询代码中处理模糊查询时,首先根据关键字进行精确查询,即在中使用name='苏三'SQL,该方法查询一次数据。如果没有查到数据,则直接使用like'%SuSan'进行模糊查询。如果找到一条数据,则将其放在返回结果集中的第一个位置。接下来在使用like'%Susan'进行模糊查询时,加入条件名<>'Susan'。把检测到的结果从第二个位置往后放。这样就可以拼出你想要的合集了。但是有个缺点,就是代码耦合度太大。(3)方案3之前,品牌Susan在第二页。最根本的原因是edit_time字段用于倒序。也就是说,修改时间越长,排名越高,而苏珊这个品牌的修改时间很小,所以排在了第二页。如果想让苏珊这个品牌排在第一页,修改排序规则不就行了吗?它可以更改为排序依据:id或name字段。按id字段排序是不合适的。虽然使用了雪花算法,但是和修改时间差不多,先插入的数据会比较小。select*frombrandwherenamelike'%SuSan%'orderbyiddesclimit5;用它排序的结果和按修改时间排序没有太大区别。好像只能用name字段来排序。3、如何排序?我们是直接在sql中对name字段进行升序还是降序排序呢?很明显不是。使用name字段降序排列:select*frombrandwherenamelike'%SuSan%'orderbynamedesclimit5;执行结果:图中没有看到我们要的数据。其实使用name字段的升序排列,第一页可能找不到我们要的数据。如何处理?假设,我们有这样一个排序:完整匹配显示在最前面,例如:苏三。左半边数据匹配,右半边按字母排序,比如:苏三1,苏三2,苏三说技术。从中间开始匹配,例如:1个苏三,2个苏三。步骤2和3也需要按照字符的长短排序,字符短的在前,例如:1苏三,1苏三1,苏三说技术。如果我们能够实现上面的排序方式,这个问题就可以完美解决。说起来容易,做起来难。是不是要先匹配all:name='苏三',再匹配:namelike'苏三%',再leftmatch:namelike'%苏三',再拼装三个查询的结果?显然这种做法有点low。实现我们上面想象的排序方式,在es中处理比较好,但是在mysql中怎么处理呢?4.解决方案其实我们可以换个思路,按照字符的长度排序。mysql为我们提供了很多非常有用的函数,比如:char_length。字符长度可以通过这个函数得到。sql调整如下:select*frombrandwherenamelike'%苏三%'orderbychar_length(name)asclimit5;name字段使用关键字模糊查询后,再使用char_length函数获取name字段的字符长度,然后按长度升序排列。只有这样的表演操作才能满足需求:期待的是:苏三终于名列第一。同时,由于SQL是分页的,即使在查询过程中name字段丢失了索引,执行效率也不会太低。业务需求得到满足。但是追求完美的我们很好奇,想看看第二页是怎么回事:select*frombrandwherenamelike'%SuSan%'orderbychar_length(name)asclimit5,5;执行结果:不是我们想象的那样脚本继续,我们之前假设的三种中,第二个和第三个都不满足。这个时候怎么办?答:可以使用mysql中的locate函数,通过它可以匹配关键字及其在字符串中的位置。使用locate函数后,sql如下:select*frombrandwherenamelike'%SuSan%'orderbychar_length(name)asc,locate('SuSan',name)asclimit5,5;执行结果:完美,终于我们想要的结果没有了。此外,您还可以使用:instr和position函数。它们的功能类似于定位功能。这里就不一一介绍了。有兴趣的朋友可以私聊我。5.总结其实模糊查询和分页,如果分开使用,一般是没有问题的。但如果要一起使用,一定要考虑顺序问题。如果只是简单的按时间或者id排序,有些特殊的业务场景无法满足,容易出现bug。当然,还有其他的方法可以解决上面的问题,比如:把pageSize调大一点,或者全匹配放在第一页。但是更好的方案是通过mysql函数来解决问题。我们可以通过mysql提供的char_length、locate、instr、position函数实现很多复杂的排序功能。