关于查询方式的总结与讨论

简介

查询的本质,是在一堆数据中,找出自己想要的数据,几乎是所有信息化系统都具备的功能。

本文介绍在程序设计中,或者在系统开发中,几种常见的查询方式及特点。

(1)顺序查询

顺序查询,即遍历所有元素,判断当前元素是否为自己想要的。

在程序设计语言中,通过循环语句加判断语句,如 Java

java 复制代码
List<Integer> data = List.of(1,2,3,4);
for (Integer datum : data) {
    if (datum == 2) {
        System.out.println("这是我想要的元素 = " + data);
    }
}

顺序查询的优点是能随机访问,就是能通过下标或者说序号访问元素。

这也是它底层实现的特点使其具备的优点,顺序存储可以使其在内存中占有一段连续的空间,如上面的"1,2,3,4",一个数占 4 个字节,那么想要访问第 3 个元素,只需访问起始位置的内存地址 + 3个元素的偏移量,就能直接访问到第 3 个元素。

在 Java 中,实现 RandomAccess 接口的容器具备了这个特点。

当然,随机访问需要容器占有一段连续的内存空间,这肯定会造成内存使用率不高,会有碎片的内存空间,相对的,就有了链表的数据结构,通过给单个元素增加引用节点,可以将多个碎片化的内存空间使用起来,可以提高内存使用率,但同时单个元素除了要存数据,还要存引用,单个元素就会占用更高的内存。

集合和链表,也是一种平衡和取舍。

(2)哈希查询

哈希查询,在存储元素时,使用元素的值给每一个元素生成一个唯一 ID,查询时,通过 ID,直接查询。

比如元素"张三的信息,李四的信息,王五的信息,赵六的信息",我们写一个"哈希算法",这个算法的作用是,**传入的值相同,会得到一个一模一样的返回值。**存入时,用"张三"为哈希算法入参,得到一个返回值,将张三的信息存入到这个返回值的地址上,查询时,同理,用"张三"为入参,得到返回值,获取这个返回值地址上的值。

这个查询的效率也是杠杠的,在 Java 中,HashMap 时哈希查询的一种实现,在开发中,我们为了避免循环查询数据库,降低接口耗时,常常将所需要的数据库数据一次性查出来,再使用 HashMap 组装起来,通过 .get(key) 的方式直接获取指定元素。

(循环查询)

java 复制代码
List<String> data = List.of("张三", "李四", "王五", "赵六");
for (String datum : data) {
    // 查询数据库
    UserInfo userInfo = dataMapper.selectByUsername(data);
    // 继续其他的业务
    .......
}

(哈希查询)

java 复制代码
List<String> data = List.of("张三", "李四", "王五", "赵六");
// 一次性查出所有
List<UserInfo> userInfoList = dataMapper.selectAll(data);
// 组装 HashMap
Map<String, UserInfo> userInfoMap = new HashMap<>();
for (UserInfo userInfo : userInfoList) {
    userInfoMap.put(userInfo.getUsername(), userInfo);
}
for (String datum : data) {
    // 取元素时,直接 get(datum) 即可
    UserInfo userInfo = userInfoMap.get(datum);
    // 继续其他的业务
    .......
}

哈希查询的缺点时,它无法做到范围查询,比如查询 id > 2 的元素,就只能循环查询,挨个判断。所以数据库就不能采用这种设计(当然还有众多因素的考虑),数据库需要支持多种数据类型的存储和查询,范围查询很常见。

(3)二叉树查询

二叉树查询,通过树状结构,将元素按照一定的算法逻辑存储,查询时按照树的存储逻辑查询。

以平衡二叉树为例(AVL),将一堆元素按照"左小右大"的特点存储,如下图,80 > 40,存在右边,8 < 40,存在左边,每个元素都是如此,此时再存入一个元素,如 4,

  • 4 < 40,存左边

  • 4 < 8,存左边

  • 4 > 3,存右边

  • 4 < 5,存左边,最后元素 4,就存在了

如下:

查询逻辑也是一样的,因为整个结构都是有序的,就是二分查询。

(4)分词查询

分词查询,最典型的技术是 Elasticsearch,存入时将数据拆分成多个小的片段,查询时,将查询的内容也进行拆分,用拆分后的片段进行匹配,找到所需要的记录。

如,这是一段关于查询的文章,被拆分成"这是","一段","关于","查询","的","文章"片段,当用关键词"查询的文章"检索时,"查询的文章",也被被拆分成,"查询","的","文章",最终查询的结果,也就是片段的匹配结果,某条记录片段匹配的数量越多,说明查询结果的匹配度越高,也就最符合查询结果。

这就是分词查询的实现思想,当然还有细节,如分词算法,某些名牌名称不应该被分词,如 "毛源昌眼镜","毛源昌"不应该被分为"毛","源","昌";还有分词后的匹配算法,也有相关的权重计算,最终查询结果是片段匹配 + 权重的结果。

(5)写入查询

写入查询,是查询思想的一个转变,当要查询某条记录是否存在时,不去直接查询这条记录,而是通过写入的方式来查询。

举个例子,某数据库表中,要查符合某些条件的记录是否存在,可以直接拼接条件查询,判断查询结果。也可以将这些条件设置成一个组合唯一约束,如果能成功写入,说明记录不存在,不能说明记录已存在。实际开发中,MySQL 有快照读和本地读的机制,高并发读写可能会出现读到的不是最新的记录,而通过写入查询就能避免这问题。(在避免 MQ 重复消费消息时会用得上,记录消费记录,每次消费前写入一条记录,能成功写入表示未被消费)

除此之外,分布式锁也是一种体现,能不能获取锁,通过写入来判断。这个写入操作可以是 Redis、MySQL、Zookeeper,甚至是文件操作------能不能生成某个文件,能生成就获取锁。

总结

顺序查询:最常用,最符合人脑思考。

哈希查询:非常高效,要善于使用。

二叉树查询:可以范围查询,设计得好,用起来就好,如 MySQL 底层实现。

分词查询:海量数据的模糊查询场景,如社交、电商平台。

写入查询:高并发读场景,考虑使用改读为写。

相关推荐
Victor3569 小时前
Hibernate(36)Hibernate如何处理多对多关系?
后端
Victor3569 小时前
Hibernate(35)什么是Hibernate的聚合函数?
后端
进阶的小名9 小时前
[超轻量级消息队列(MQ)] Redis 不只是缓存:我用 Redis Stream 实现了一个 MQ(自定义注解方式)
数据库·spring boot·redis·缓存·消息队列·个人开发
何中应9 小时前
@Autowrited和@Resource注解的区别及使用场景
java·开发语言·spring boot·后端·spring
源代码•宸9 小时前
Golang语法进阶(Context)
开发语言·后端·算法·golang·context·withvalue·withcancel
christine-rr9 小时前
linux常用命令(9)——查看系统与硬件信息
linux·运维·服务器·网络·后端
源代码•宸9 小时前
Golang语法进阶(Sync、Select)
开发语言·经验分享·后端·算法·golang·select·pool
IT_陈寒9 小时前
2024年JavaScript开发者必备的10个ES13新特性实战指南
前端·人工智能·后端
Li_7695329 小时前
Redis 进阶(七)—— 缓存
数据库·redis·缓存