本文目标是:了解查询的核心原理,对比 SQL 查询优化技巧在 h2database 中的落地实现。

前提:为了贴近实际应用,本文 Code Insight 基于 BTree 存储引擎。

数据查询核心原理

数据库实现查询的原理:遍历表/索引,判断是否满足where筛选条件,添加到结果集。简单通用。

对于选择表还是索引、如何遍历关联表、优先遍历哪个表、怎样提升遍历的效率,这个就是数据库查询复杂的地方。

/**
* 查询命令实现查询的主要过程
* @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

/**
* 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

/**
* 根据 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

// 用伪代码表示,可以更清晰理解上述 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.

作者:京东物流 杨攀

来源:京东云开发者社区 自猿其说Tech 转载请注明来源

从 SQL 查询优化技巧去看 h2 数据库查询原理的更多相关文章

  1. MySQL SQL查询优化技巧详解

    MySQL SQL查询优化技巧详解 本文总结了30个mysql千万级大数据SQL查询优化技巧,特别适合大数据里的MYSQL使用. 1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 ...

  2. 30个mysql千万级大数据SQL查询优化技巧详解

    1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索 ...

  3. springboot09 事务 H2数据库

    一.事务 1. 事务介绍 事务可以包含多个操作步骤 , 如果有一个步骤失败,那么这一组都以失败告终. 事务是指包含多个微小逻辑单元的一组操作, 只要其中有一个逻辑失败了,那么这一组操作就全部以失败告终 ...

  4. MySQL 性能调优——SQL 查询优化

    如何设计最优的数据库表结构,如何建立最好的索引,以及如何扩展数据库的查询,这些对于高性能来说都是必不可少的.但是只有这些还不够,要获得良好的数据库性能,我们还要设计合理的数据库查询,如果查询设计的很糟 ...

  5. 数据库查询服务DBCacheServer

    各个业务系统,都需要查询各类数据库; 一般查询数据库都是客户端自己连接,根据现在的情况,存在以下几点问题 1.客户端连接很多,连接大小,峰值不可控 2.客户端SQL程序员自己写,参差不齐 3.SQL书 ...

  6. [转载]Hibernate如何提升数据库查询的性能

    目录(?)[-] 数据库查询性能的提升也是涉及到开发中的各个阶段在开发中选用正确的查询方法无疑是最基础也最简单的 SQL语句的优化 使用正确的查询方法 使用正确的抓取策略 Hibernate的性能优化 ...

  7. Django 数据库查询

    #!/usr/bin/python #coding:utf-8 from django.shortcuts import render; from django.shortcuts import re ...

  8. MS SQL Server数据库查询优化技巧

    [摘 要]本文主要是对MS SQL Server数据库查询优化技巧进行了说明和分析,对索引使用.查询条件以及数据表的设计等进行了阐述.中国论文网 http://www.xzbu.com/2/view- ...

  9. 数据库查询优化-20条必备sql优化技巧

    0.序言 本文我们来谈谈项目中常用的 20 条 MySQL 优化方法,效率至少提高 3倍! 具体如下: 1.使⽤ EXPLAIN 分析 SQL 语句是否合理 使⽤ EXPLAIN 判断 SQL 语句是 ...

  10. 从SQL Server CloudDBA 看云数据库智能化

    最近阿里云数据库SQL Server在控制台推出了CloudDBA服务,重点解决数据库性能优化领域问题,帮助客户更好的使用好RDS数据库,这是继MySQL之后第二个关系型数据库提供类似的服务.   数 ...

随机推荐

  1. 进程相关API

    ID与句柄 如果我们成功创建一个进程,CreateProcess函数会给我们返回一个结构体,包括四个数 据:进程编号(ID).进程句柄.线程编号(ID).线程句柄. 进程ID其实我们早见过了,通常我们 ...

  2. Mybatis(日志工厂)

    日志工厂 如果一个数据库操作,出现了异常,我们需要排错.所以日志就是最好的助手 曾经:sout.debug 现在:日志工厂 SLF4J[工作中,springboot] LOG4J[掌握] LOG4J2 ...

  3. zabbix system.run

    Zabbix的system.run是一个主动模式的监控项,它允许用户在Zabbix服务器上执行自定义的命令或脚本,并获取执行结果作为监控数据. system.run监控项的配置包括以下几个关键参数: ...

  4. 2021-09-27 Core初步实战

    中间件Progarm的定义添加Logging public static IHostBuilder CreateHostBuilder(string[] args) => Host.Create ...

  5. Redis从入门到放弃(6):持久化

    1.引言 Redis作为一种高性能的内存数据存储系统,常被用作缓存.会话存储.消息队列等多种应用场景.然而,由于其数据存储在内存中,一旦发生意外或服务器重启,数据就会丢失.为了保障数据的持久性和安全性 ...

  6. Java日志系列:日志门面JCL、SLF4J

    目录 一.日志门面说明 二.JCL 使用 JCL 入门 JCL 原理 三.SLF4J 使用 配合自身简单日志实现(slf4j-simple) 配置logback日志实现 配置Log4J日志实现(需适配 ...

  7. Unity UGUI的Toggle(复选框)组件的介绍及使用

    Unity UGUI的Toggle(复选框)组件的介绍及使用 1. 什么是Toggle组件? Toggle(复选框)是Unity UGUI中的一个常用组件,用于实现复选框的功能.它可以被选中或取消选中 ...

  8. 使用flask开发web应用

    Flask环境搭建 要开发flash应用,我们需要做一些准备工作 我写了个初始化的脚本 Pip_init.sh来安装初始工作 可以到我的git上去下载该脚本进行初始化安装 要启动flask应用,我们需 ...

  9. Windows安装JDK 8/11/17教程

    JDK,全称Java Development Kit,即Java开发工具包,它是整个Java开发的核心,包含了Java运行环境(JVM+Java系统类库)和Java工具.目前JDK 8.11.17是长 ...

  10. 快速了解C#接口(Interface)

    Runoob 接口定义了所有类继承接口时应遵循的语法合同.接口定义了语法合同 "是什么" 部分,派生类定义了语法合同 "怎么做" 部分. 接口定义了属性.方法和 ...