首页 > 数据库 >从 SQL 查询优化技巧去看 h2 数据库查询原理 | 京东物流技术团队

从 SQL 查询优化技巧去看 h2 数据库查询原理 | 京东物流技术团队

时间:2023-11-09 11:02:48浏览次数:48  
标签:遍历 join h2 查询 cursor 索引 SQL row

本文目标是:了解查询的核心原理,对比 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 转载请注明来源

标签:遍历,join,h2,查询,cursor,索引,SQL,row
From: https://blog.51cto.com/u_15714439/8274012

相关文章

  • 通过日志恢复SQL Server的历史数据
    通过日志还原方案一:前提条件1.必须有一个完整的备份,且这个备份必须是在修改、删除数据之前做的。2.在更新、删除数据之后,做日志备份,该log备份将用于还原之前的数据建议使用备份数据库进行还原操作,确认无误再对原库进行操作,或同步数据开始还原操作:此处使用SQLmanagement界......
  • sql server2008禁用远程连接
    1.打开SQLServer配置管理器,双击左边SQLServer网络配置,点击TCP/IP协议,在协议一栏中,找到全部侦听,修改为否,然后点击IP地址,将IP地址为127.0.0.1(IPV4)或::1(IPV6)的已启用修改为是,其它的IP地址的已启用修改为否注意:如果要开启局域网远程连接,则找到内网ip地址,将已启用改为......
  • SqlServer开启远程连接配置
    第一步:打开配置管理器第二步:开启端口监听第三步:设置所有端口均使用1433(或者其他)......
  • MySQL 单表数据最大不要超过多少行?为什么?
    1背景作为在后端圈开车的多年老司机,是不是经常听到过,“mysql单表最好不要超过2000w”,“单表超过2000w就要考虑数据迁移了”,“你这个表数据都马上要到2000w了,难怪查询速度慢”这些名言民语就和“群里只讨论技术,不开车,开车速度不要超过120码,否则自动踢群”,只听过,没试过......
  • linux/docker 版 Sql Server新建的数据库插入中文乱码问题解决方案
    SqlServer插入遇到乱码原因:在英文系统中,SqlServer默认排序规则为英文字典顺序解决方案一:容器版SqlServer,在创建容器时,可以加上环境变量-eMSSQL_COLLATION=Chinese_PRC_CI_AS-eTZ=Asia/Shanghai 把排序规则设为中文字典顺序并忽略大小写区分重音,时区设置为上海,不然......
  • 高版本SqlServer数据库还原到低版本数据库
    需求:SqlServer2017还原到SqlServer2008数据库直接备份还原与拷贝文件还原均无法成功解决方案:1、修改兼容性,再备份数据库,还原(推荐)2、导出脚本、导入(即数据同步),大数据量不建议使用该方法......
  • orcale中把查询结果是逗号多个人员的数据转换为单行,过滤重复的
    1.原本图 2.解决方法selectregexp_substr(a.numbers,'[^,]+',1,level)asnumbersfrom(selectdistinctid,numbersfromprojectwhereisdelete=0andisactive=1andnvl(objstatus,'402881e50c717307010c719745ce0009')!='402881e50c717307010c......
  • mysql 优化之开启慢查询并分析原因
      第一步.开启mysql慢查询方式一:修改配置文件my.ini增加几行:slow_query_log=on#开启慢查询日志slow_query_log_file=filename#指定日志文件保存路径,不指定的话默认在数据库文件目录下,名为hostname-slow.loglong_query_time=2#指定达到多少秒才算慢查询long-que......
  • mysql常见问题及排查步骤
    问题一:数据库连接串测试报错:Couldnotcreateconnectiontodatabaseserver.504错误遇到数据库问题的通用排查步骤确定数据库没有挂确定远程可以连接(前两个可以通过在本地连接验证)以上都OK,大概率服务器ip被限制访问,如果不是人为限制,在可以访问的服务器连上对应数据库,执行......
  • mysql8.x通过备份文件及binlog日志恢复数据
    问题简述记一次mysql数据库被误删(是整个库被删了)后的还原前提条件数据库版本为mysql8.x以上具有库被删除前的完整备份数据库开启binlog还原步骤第一步:通过完整备份还原被删的库注意事项:还原后切勿让其他用户连接,操作数据库。待使用binlog日志恢复数据后再对库进行操作,否......