您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
从 SQL 查询优化技巧去看 h2 数据库查询原理
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
从 SQL 查询优化技巧去看 h2 数据库查询原理
完成设置321
2023-11-08
IP归属:北京
4560浏览
> 本文目标是:了解查询的核心原理,对比 SQL 查询优化技巧在 h2database 中的落地实现。 > > 前提:为了贴近实际应用,本文 Code Insight 基于 BTree 存储引擎。 ## 数据查询核心原理 > 数据库实现查询的原理:遍历表/索引,判断是否满足`where`筛选条件,添加到结果集。简单通用。 > > 对于选择表还是索引、如何遍历关联表、优先遍历哪个表、怎样提升遍历的效率,这个就是数据库查询复杂的地方。 ```java /** * 查询命令实现查询的主要过程 * @see org.h2.command.dml.Select#queryFlat */ private void queryFlat(int columnCount, ResultTarget result, long limitRows) { // 遍历单表 or 关联表。topTableFilter 可以简单理解为游标 cursor。 while (topTableFilter.next()) { // 判断是否符合 where 筛选条件 if (condition == null || Boolean.TRUE.equals(condition.getBooleanValue(session))) { Value[] row = new Value[columnCount]; // 填充select 需要的 columns ① for (int i = 0; i < columnCount; i++) { Expression expr = expressions.get(i); row[i] = expr.getValue(session); } // 保存符合条件的数据,这个对应 resultSet result.addRow(row); // 没有 sort 语句的情况下,达到 limitRows, 终止 table scan ② if ((sort == null || sortUsingIndex) && limitRows > 0 && result.getRowCount() >= limitRows) { break; } } } } ``` ## Join 查询核心原理 > 基于状态机模式,实现多表嵌套循环遍历。 > > 使用的 Join 算法是: Nested Loop Join。 > > 状态变迁:BEFORE_FIRST --> FOUND --> AFTER_LAST ```java /** * Check if there are more rows to read. * 遍历的数据 row 记录在当前 session 中,随时随地可以获取 * * @return true if there are * @see org.h2.table.TableFilter#next */ public boolean next() { // 遍历结束,没有符合的条件的 row if (state == AFTER_LAST) { return false; } else if (state == BEFORE_FIRST) { // cursor 遍历初始化, 如果基于索引的游标,则可以提前锁定数据范围。③ cursor.find(session, indexConditions); if (!cursor.isAlwaysFalse()) { // 如果包含 join 表,重置关联表的状态机。 if (join != null) { join.reset(); } } } else { // state == FOUND || NULL_ROW 的情况 // 嵌套遍历 join 关联表。这是个递归调用关联表的过程。 if (join != null && join.next()) { return true; } } // 表/索引数据扫描,匹配filterCondition,直到找到符合的 row while (true) { if (cursor.isAlwaysFalse()) { state = AFTER_LAST; } else { if (cursor.next()) { currentSearchRow = cursor.getSearchRow(); current = null; state = FOUND; } else { state = AFTER_LAST; } } // where 条件判断 if (!isOk(filterCondition)) { continue; } // 嵌套遍历 join 关联表。主表的每一行 row,需要遍历关联子表一次。④ if (join != null) { join.reset(); if (!join.next()) { continue; } } // check if it's ok if (state == NULL_ROW || joinConditionOk) { return true; } } state = AFTER_LAST; return false; } ``` ## 获取查询数据 > 从遍历的 row 中,获取 select 语句需要的 column 数据。 > > 对应的 Cursor 实现是:org.h2.index.PageBtreeCursor ```java /** * 根据 columnId 获取对应的值 * @see org.h2.table.TableFilter#getValue */ public Value getValue(Column column) { if (current == null) { // 优先从当前遍历的 row 获取数据。 // 如果是索引中的 row,不会包含所有的行,会有取不到的情况 Value v = currentSearchRow.getValue(columnId); if (v != null) { return v; } // 如果没有,再尝试从原始表 row 存储中获取数据。⑤ // 对应的实现: currentRow = index.getRow(session, currentSearchRow.getKey()); current = cursor.get(); if (current == null) { return ValueNull.INSTANCE; } } return current.getValue(columnId); } ``` ## 常用的 SQL 查询优化技巧 > 分别对应上述源代码注释的数字角标。 ### ①避免使用 SELECT *:只选择需要的列 如果使用 select *, 即使使用了索引查询。也需要取原数据行的所有数据(⑤)。会进行数据的二次读取,也就是回表查询。影响了性能。 ### ②避免使用 ORDER BY, 尽量使用LIMIT 使用 LIMIT:如果只需要部分结果,可以使用 LIMIT 子句限制返回的行数,避免检索整个结果集。 如上源代码,如果没有 Order By,有limit 限制情况下,可以中途结束表遍历。 如果有 Order By 的情况下,肯定要执行完成整个扫描遍历的过程,最终在 result 结果集中再一次进行排序计算。 ### ③使用索引:确保表中的列上有适当的索引,以加快查询速度。 如果使用索引,在初始化扫描阶段,会给 cursor 一定的范围,避免全表扫描。极大的缩小的查询范围。 ### ④减少连接的表的数量:如果可能,尽量减少查询中的表的数量。 无需多言,嵌套递归查询,理论上是所有表的笛卡尔积。 ### ⑤**使用覆盖索引**:一个查询的所有列都包含在索引中。 这样查询可以只扫描索引而不需要回表。例如,如果你的查询是 SELECT id, name FROM users WHERE age = 30,那么在 age, id, name 上创建一个复合索引可以避免回表。 ## 其他 ### Nested Loop Join ```java // 用伪代码表示,可以更清晰理解上述 join 遍历的过程 for (r in R) { for (s in S) { if (r satisfy condition s) { output <r, s>; } } } ``` ### MySQL 中的Nested Loop Join > MySQL官方文档中提到,MySQL只支持Nested Loop Join这一种join algorithm. > > MySQL resolves all joins using a nested-loop join method. > > This means that MySQL reads a row from the first table, and then finds a matching row in the second table, the third table, and so on.
上一篇:你真的了解@Async吗?
下一篇:WebSocket魔法师:打造实时应用的无限可能
完成设置321
文章数
7
阅读量
1155
作者其他文章
01
稳,从数据库连接池 testOnBorrow 看架构设计
本文从 Commons DBCP testOnBorrow 的作用机制着手,管中窥豹,从一点去分析数据库连接池获取的过程以及架构分层设计。以下内容会按照每层的作用,贯穿分析整个调用流程。1️⃣框架层 commons-poolThe indication of whether objects will be validated before being borrowed from the pool.
01
分布式数据库 Join 查询设计与实现浅析
相对于单例数据库的查询操作,分布式数据查询会有很多技术难题。本文记录 Mysql 分库分表 和 Elasticsearch Join 查询的实现思路,了解分布式场景数据处理的设计方案。文章从常用的关系型数据库 MySQL 的分库分表Join 分析,再到非关系型 ElasticSearch 来分析 Join 实现策略。逐步深入Join 的实现机制。①Mysql 分库分表 Join 查询场景分库分表场
01
这问题巧了,SpringMVC 不同参数处理机制引发的思考
这个问题非常有趣,不是SpringMVC 的问题,是实际开发中混合使用了两种请求方式暴露出来的。问题场景功能模块中,提供两个 Http 服务。一个是列表查询(application/json 请求),一个是列表导出(表单请求)。运行环境发现个问题:MVC model 新添加的属性,类似的 Http 请求,一个有值,一个没有代码如下:/** * application/json 请求。 这种情况 p
01
h2database BTree 设计实现与查询优化思考
h2database 是使用Java 编写的开源数据库,兼容ANSI-SQL89。即实现了常规基于 BTree 的存储引擎,又支持日志结构存储引擎。功能非常丰富(死锁检测机制、事务特性、MVCC、运维工具等),数据库学习非常好的案例。本文理论结合实践,通过BTree 索引的设计和实现,更好的理解数据库索引相关的知识点以及优化原理。BTree 实现类h2database 默认使用的 MVStore
完成设置321
文章数
7
阅读量
1155
作者其他文章
01
稳,从数据库连接池 testOnBorrow 看架构设计
01
分布式数据库 Join 查询设计与实现浅析
01
这问题巧了,SpringMVC 不同参数处理机制引发的思考
01
h2database BTree 设计实现与查询优化思考
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号