背景

订单系统在各行各业中广泛应用,为消费者、商家后台、促销系统等第三方提供用户、产品、订单等多维度的管理和查询服务。
为了挖掘出海量订单数据的潜能,丰富高效的查询必不可少。然而很多时候并不能给出完整准确的查询关键字,例如,只知道收货人姓氏,或是产品名称部分关键字,或是根据收货人手机尾号找到订单,我们将这类查询归为“模糊查询”。

需求分析

订单系统,作为电商系统的“核心”,管理着订单状态、商品信息、用户信息、收货地址和支付信息,联动库存更新,为下游的仓库物流系统提供依据。在订单表设计时,需要记录上述所有信息。本文仅以海量订单管理查询为背景,摘选部分重要字段,管窥多元索引(SearchIndex)在模糊查询时的一些典型使用,整理如下:

  • 手机号前缀匹配
    覆盖SQL like语义,满足前缀匹配、后缀匹配和通配符匹配查询。
  • 产品名称模糊查询
    用户输入产品名称中部分关键字即可查出包含相关产品的订单。
  • 商品型号的实时查询提示
    只输入少量查询关键字就可召回尽可能多的匹配结果,可用于“电影名称”、“产品型号”等字段的实时查询提示。

传统解法

MySql和PostgreSQL等关系型DB支持like语法进行模糊查询,其本质就是用查询关键词去扫描并匹配每条记录的字符串内容,只适用于小规模地检索,数据规模大了以后性能非常差。

TableStore解法

表格存储(TableStore)引入多元索引(SearchIndex)功能后,完全覆盖传统SQL所支持的前缀匹配和通配符匹配,并且提供针对不同场景的精细化模糊查询功能,性能优异。本节将以电商订单为背景逐一讲解。

手机号前缀匹配

定义手机号字段consumerCell为KEYWORD类型,创建多元索引,用PrefixQuery即可对原始字段进行前缀匹配查询。例如,查询consumerCell字段以'135'开头的行,等价于SQL中子句consumerCell like '135%'。

核心代码

//创建索引
indexSchema.addFieldSchema(new FieldSchema(CONSUMER_CELL, FieldType.KEYWORD)); //查询
PrefixQuery prefixQuery = new PrefixQuery();
prefixQuery.setFieldName(CONSUMER_CELL);
prefixQuery.setPrefix("13580");

通配符查询

如同前缀查询(PrefixQuery),通配符查询(WildcardQuery)可以对原始字符串进行更灵活一些的匹配。例如,查询consumerCell字段中以'135'开头并且中间包含'8066'的行,类比SQL中子句consumerCell like '135%8066%'。在表格存储中可以把consumerCell定义为KEYWORD类型,建立多元索引,然后用WildcardQuery进行通配符匹配。要匹配的值可以是一个带有通配符的字符串,用星号("*")代表0个或多个任意字符,用问号("?")代表任意单个字符。
需要注意的是,不能以“*”开头,且模式字符长度不能超过10字节。

如何实现后缀匹配

通配符查询不能以“*”开头,若要实现后缀匹配,可将字符串翻转,再用PrefixQuery/WildcardQuery查询。

产品名称模糊查询

在订单场景中,“收货人姓名”、“产品名”和“收货地址”这类字段可能包含汉字、英文、数字等,查询时需要以“单个汉字”或“英文单词”为粒度切分字符串并建立索引。查询同理,以“单个汉字”或“英文单词”为粒度对查询关键词字符串进行分词,在索引中进行查找匹配。

例如,订单表中,某行的产品名称字段值为"Xiaomi/小米redmi note 7 pro 红米索尼4800万智能手机",期望用"xiaomi", "小米", "note", "pro"都能匹配到该条记录。可以使用默认分词器SingleWord分词器。

TEXT类型索引列默认使用SingleWord分词器,按“单个汉字”切分中文,按“单个单词”切分英文,大小写字母不敏感,且单词不会被拆分为子词。例如,字段值"Xiaomi/小米redmi note 7 pro 红米索尼4800万智能手机"会被切分为词条:"xiaomi", "小", "米", "redmi", "note", "7", "pro", "红", "米", "索", "尼", "4800", "万", "智", "能", "手", "机",并建立倒排索引。

SingleWord支持配置参数CaseSensitive设置是否大小写敏感;参数DelimitWord表示是否将单词拆成子词。例如,当设置SingleWord的参数DelimitWord=true, CaseSensitive=true,TEXT字段值"TableStore"会被切分为"Table"和"Store"。

查询时若不在乎查询关键词分词后的顺序,用MatchQuery即可满足需求。对于特定场景,对查询关键词被被分词后顺序敏感,可以考虑使用MatchPhraseQuery。

核心代码

//创建索引
//等价于:indexSchema.addFieldSchema(new FieldSchema(PRODUCT_NAME, FieldType.TEXT));
indexSchema.addFieldSchema(new FieldSchema(PRODUCT_NAME, FieldType.TEXT).setAnalyzer(FieldSchema.Analyzer.SingleWord)); //查询
MatchQuery matchQuery = new MatchQuery();
matchQuery.setFieldName(PRODUCT_NAME);
matchQuery.setText("Xiaomi/小米redmi note 7 pro 红米索尼4800万智能手机");

商品型号的实时查询提示

某些场景下,当使用者在搜索框中键入部分关键字,就召回尽可能多的结果。这样,可以避免用户输入错误的关键字,将其引导到“意中有语中无”的正确结果,提升用户体验。例如,订单表中包含某手机型号为"HUAWEI P30PRO",一种良好的查询体验是:仅当键入"P30","30"或者"PRO"时,就可以查出这行记录并反馈用户。此时,您可以考虑使用Fuzzy分词器。

Fuzzy分词器可以对字符串进行最细粒度的拆分,可以提升查询召回率,但也造成多元索引存储量膨胀,因此需要谨慎使用。其原理是设置一个“最小字符串长度窗口(minChars)”和“最大字符串长度窗口(maxChars)”,遍历[minChars, maxChars]窗口大小对TEXT类型字段值进行切分,并对所有切分后的字符串建立倒排。minChars默认为1,maxChars默认为3。

对“产品型号”建立TEXT索引,并使用Fuzzy分词器以后,字段值"HUAWEI P30PRO"会被切分为词条:"h", "hu", "hua", "u", "ua", "uaw", "a", "aw", "awe", "w", "we", "wei", "e", "ei", "i", "p", "p3", "p30", "3", "30", "30p", "0", "0p", "0pr", "pr", "pro", "r", "ro", "o",并建立倒排索引。因此,用"p30", "P30", "30", "PRO", "pro"查询时都可以匹配到。同时也可以看出,Fuzzy分词后索引数据量急剧膨胀,因此限制maxChars和minChars的差值不超过6,如果您需要自定义minChars和maxChars时需谨慎。

核心代码

//创建索引
indexSchema.addFieldSchema(new FieldSchema(PRODUCT_TYPE, FieldType.TEXT)
.setAnalyzer(FieldSchema.Analyzer.Fuzzy)
.setAnalyzerParameter(new FuzzyAnalyzerParameter(1, 4))); //查询
MatchQuery matchQuery = new MatchQuery();
matchQuery.setFieldName(PRODUCT_TYPE);
matchQuery.setText("P30");

代码示例

订单表结构

以订单系统的订单表(表名order)为例,可能包含以下字段:

列名 数据类型 索引类型 字段说明
order_id_md5 string - 主键列:md5(order_id)避免热点
order_id string KEYWORD 主键列:订单id
order_status long LONG 订单状态
order_time long LONG 下单时间
pay_time long LONG 支付时间
deliver_time long LONG 发货时间
receive_time long LONG 收货时间
product_id string KEYWORD 商品id
product_name string TEXT 产品名
product_type string TEXT 产品型号
consumer_id string KEYWORD 收货人id
consumer_name string TEXT 收货人姓名
consumer_cell string KEYWORD 收货人联系方式
consumer_address string TEXT 收货地址

项目代码

完整代码在这里找到:稍后开源,敬请期待

附录

除了上文提到的SingleWord和Fuzzy分词器以外,多元索引还支持以下分词器——

MaxWord分词器

MaxWord分词器将英文切分成单词,将中文切分成尽可能多的词语/词组。对中文分词时,会做多层语义分词,一般为两层,第一层按最短语义切分,第二层按最长语义切分,并将两次切分后的数组合并。例如字符串"菊花茶",第一层切分为["菊花"、"花茶"],第二层切分为["菊花茶"],合并后为["菊花"、"花茶"、"菊花茶"]。

MinWord分词器

MinWord分词器将英文切分成单词,将中文切分为尽可能少的词语/词组。对中文分词时,只会按最长语义切分,例如字符串"菊花茶",会被切分为["菊花茶"]。

Split分词器

Split分词器允许您按照自定义分隔符对字符串进行分词。例如,用字符串内容用逗号(","),将Split分词器的分隔符设置为",",可以按照","切分并构建索引。

总结

表格存储多元索引(SearchIndex)为海量订单数据提供了丰富的查询类型,而模糊查询使得用户在不给出完整准确的查询关键字就能实现近似匹配,大大提升了订单数据的查询易用性,发挥出存储的更多潜在价值。

本文作者:异雀

原文链接

本文为云栖社区原创内容,未经允许不得转载。

详解TableStore模糊查询——以订单场景为例的更多相关文章

  1. TCP/IP详解--发送ACK和RST的场景

    在有以下几种情景,TCP会把ack包发出去: 1.收到1个包,启动200ms定时器,等到200ms的定时器到点了(第二个包没来),于是对这个包的确认ack被发送.这叫做“延迟发送”: 2.收到1个包, ...

  2. 深入解析ThreadLocal 详解、实现原理、使用场景方法以及内存泄漏防范 多线程中篇(十七)

    简介 从名称看,ThreadLocal 也就是thread和local的组合,也就是一个thread有一个local的变量副本 ThreadLocal提供了线程的本地副本,也就是说每个线程将会拥有一个 ...

  3. MySQL简单查询详解-单表查询

    MySQL简单查询详解-单表查询 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.查询的执行路径 一条SQL查询语句的执行过程大致如下图所示: 1>.客户端和服务端通过my ...

  4. Lucene系列六:Lucene搜索详解(Lucene搜索流程详解、搜索核心API详解、基本查询详解、QueryParser详解)

    一.搜索流程详解 1. 先看一下Lucene的架构图 由图可知搜索的过程如下: 用户输入搜索的关键字.对关键字进行分词.根据分词结果去索引库里面找到对应的文章id.根据文章id找到对应的文章 2. L ...

  5. Yii 框架里数据库操作详解-[增加、查询、更新、删除的方法 'AR模式']

    public function getMinLimit () {        $sql = "...";        $result = yii::app()->db-& ...

  6. Hibernate第十篇【Hibernate查询详解、分页查询】

    前言 在Hibernate的第二篇中只是简单地说了Hibernate的几种查询方式-.到目前为止,我们都是使用一些简单的主键查询阿-使用HQL查询所有的数据-.本博文主要讲解Hibernate的查询操 ...

  7. 【Ray Tracing The Next Week 超详解】 光线追踪2-7 任意长方体 && 场景案例

    上一篇比较简单,很久才发是因为做了一些好玩的场景,后来发现这一章是专门写场景例子的,所以就安排到了这一篇 Preface 这一篇要介绍的内容有: 1. 自己做的光照例子 2. Cornell box画 ...

  8. 三、mysql登录详解及版本号查询

    1.用window+r,输入cmd,用mysql -uuser -ppassword登录时出现‘mysql’不是有效的内部命令? 答:这是因为没有配置MySQL的环境变量path所致. MySQL的环 ...

  9. 详解css媒体查询

    简介 媒体查询(Media Queries)早在在css2时代就存在,经过css3的洗礼后变得更加强大bootstrap的响应式特性就是从此而来的. 简单的来讲媒体查询是一种用于修饰css何时起作用的 ...

随机推荐

  1. fidder抓包使用(一)

    fidder是会占用 jupyter 端口的,在fidder里边最上边找到tools--->options-->connections里边的8888改成别的重启jupyter就好了

  2. bzoj 1026 [SCOI2009]windy数——数位dp水题

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=1026 迷恋上用dfs写数位dp了. #include<iostream> #in ...

  3. Linux平台的SVN服务器的配置及搭建

    https://jingyan.baidu.com/article/54b6b9c08b35382d593b477c.html 一.安装SVN   1 在Linux平台上,SVN的软件包名称是subv ...

  4. 洛谷 P1567 统计天数【最长上升子序列/断则归一】

    题目背景 统计天数 题目描述 炎热的夏日,KC非常的不爽.他宁可忍受北极的寒冷,也不愿忍受厦门的夏天.最近,他开始研究天气的变化.他希望用研究的结果预测未来的天气. 经历千辛万苦,他收集了连续N(1& ...

  5. 为Apple Watch而战-----(初级篇)

    重要 本文档是开发过程中使用的API或者技术的初步文档.苹果提供该文档以便于开发者使用苹果产品上使用技术和编程接口.后期该文档中信息会有所变动,所以依据本文档开发的软件应当使用最终的操作系统软件进行测 ...

  6. python正则表达式应用 重组分词

  7. golang中特殊的标识符

    你会发现在 Go 代码中的几乎所有东西都有一个名称或标识符.另外,Go 语言也是区分大小写的,这与 C 家族中的其它语言相同.有效的标识符必须以字符(可以使用任何 UTF-8 编码的字符或 _)开头, ...

  8. PHP是解释型语言:边解析边运行

    计算机语言的发展史: 第一代:机器语言,全部都是01010二进制代码,计算机能够直接的识别,运行效率是最高的,但是难编,难记,难区分,可移植性差! 第二代:汇编语言,其实就是符号化的机器语言,增加了编 ...

  9. PHP开发api接口安全验证的实例,值得一看

    php的api接口 在实际工作中,使用PHP写api接口是经常做的,PHP写好接口后,前台就可以通过链接获取接口提供的数据,而返回的数据一般分为两种情况,xml和json,在这个过程中,服务器并不知道 ...

  10. PLUTO平台是由美林数据技术股份有限公司下属西安交大美林数据挖掘研究中心自主研发的一款基于云计算技术架构的数据挖掘产品,产品设计严格遵循国际数据挖掘标准CRISP-DM(跨行业数据挖掘过程标准),具备完备的数据准备、模型构建、模型评估、模型管理、海量数据处理和高纬数据可视化分析能力。

    http://www.meritdata.com.cn/article/90 PLUTO平台是由美林数据技术股份有限公司下属西安交大美林数据挖掘研究中心自主研发的一款基于云计算技术架构的数据挖掘产品, ...