从 SQL 查询优化技巧去看 h2 数据库查询原理
本文目标是:了解查询的核心原理,对比 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 数据库查询原理的更多相关文章
- MySQL SQL查询优化技巧详解
MySQL SQL查询优化技巧详解 本文总结了30个mysql千万级大数据SQL查询优化技巧,特别适合大数据里的MYSQL使用. 1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 ...
- 30个mysql千万级大数据SQL查询优化技巧详解
1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索 ...
- springboot09 事务 H2数据库
一.事务 1. 事务介绍 事务可以包含多个操作步骤 , 如果有一个步骤失败,那么这一组都以失败告终. 事务是指包含多个微小逻辑单元的一组操作, 只要其中有一个逻辑失败了,那么这一组操作就全部以失败告终 ...
- MySQL 性能调优——SQL 查询优化
如何设计最优的数据库表结构,如何建立最好的索引,以及如何扩展数据库的查询,这些对于高性能来说都是必不可少的.但是只有这些还不够,要获得良好的数据库性能,我们还要设计合理的数据库查询,如果查询设计的很糟 ...
- 数据库查询服务DBCacheServer
各个业务系统,都需要查询各类数据库; 一般查询数据库都是客户端自己连接,根据现在的情况,存在以下几点问题 1.客户端连接很多,连接大小,峰值不可控 2.客户端SQL程序员自己写,参差不齐 3.SQL书 ...
- [转载]Hibernate如何提升数据库查询的性能
目录(?)[-] 数据库查询性能的提升也是涉及到开发中的各个阶段在开发中选用正确的查询方法无疑是最基础也最简单的 SQL语句的优化 使用正确的查询方法 使用正确的抓取策略 Hibernate的性能优化 ...
- Django 数据库查询
#!/usr/bin/python #coding:utf-8 from django.shortcuts import render; from django.shortcuts import re ...
- MS SQL Server数据库查询优化技巧
[摘 要]本文主要是对MS SQL Server数据库查询优化技巧进行了说明和分析,对索引使用.查询条件以及数据表的设计等进行了阐述.中国论文网 http://www.xzbu.com/2/view- ...
- 数据库查询优化-20条必备sql优化技巧
0.序言 本文我们来谈谈项目中常用的 20 条 MySQL 优化方法,效率至少提高 3倍! 具体如下: 1.使⽤ EXPLAIN 分析 SQL 语句是否合理 使⽤ EXPLAIN 判断 SQL 语句是 ...
- 从SQL Server CloudDBA 看云数据库智能化
最近阿里云数据库SQL Server在控制台推出了CloudDBA服务,重点解决数据库性能优化领域问题,帮助客户更好的使用好RDS数据库,这是继MySQL之后第二个关系型数据库提供类似的服务. 数 ...
随机推荐
- FreeBSD 内核模块和硬盘相关
查看已加载的内核模块 使用命令:kldstat 加载驱动: kldload xx 查看识别到的硬盘 使用命令:dmesg | grep sector freeBSD 查看硬件信息 使用命令:dmesg ...
- Linux 脚本:shell
# 以脚本所在目录作为脚本执行时的当前路径. -P 选项寻找物理上的地址,忽略软连接. SCRIPT_DIR=$(cd $(dirname $0); pwd -P) # 在任意位置执行自己的可执行程序 ...
- 用 Rust 生成 Ant-Design Table Columns
经常开发表格,是不是已经被手写Ant-Design Table的Columns整烦了? 尤其是ToB项目,表格经常动不动就几十列.每次照着后端给的接口文档一个个配置,太头疼了,主要是有时还会粘错就尴尬 ...
- python教程 入门学习笔记 第1天 初识python python语言环境安装 python编写器
初识python 一.python语言简介: 1.起源:1989年由荷兰的前谷歌程序员吉多.范罗苏姆(龟叔)创造,python的命名来源于英国电视喜剧Monty Python's Flying Cir ...
- 【博客重构之路】webman-admin安装指南
原文地址[博客重构之路]webman-admin安装指南 视频地址[bilibili] webman是什么 webman是一款基于workerman开发的高性能HTTP服务框架.webman用于替代传 ...
- 熟悉又陌生的package.json
前言 随着前端的不断发展,package.json可谓是在前端项目中无处不在,它不仅在项目根目录会有,而且在 node_modules 中也存在.那么这个文件到底是干嘛的,又有什么作用?很多人对它的认 ...
- Redis从入门到放弃(9):集群模式
前面文章我们介绍了Redis的主从模式是一种在Redis中实现高可用性的方式,但也存在一些缺点. 1.主从模式缺点 写入单点故障:在主从模式中,写入操作只能在主节点进行,如果主节点宕机,写入将无法执行 ...
- [grpc]双向tls加密认证
前言 假设gRPC服务端的主机名为qw.er.com,需要为gRPC服务端和客户端之间的通信配置tls双向认证加密. 生成证书 生成ca根证书.生成过程会要求填写密码.CN.ON.OU等信息,记住密码 ...
- java file I/O流
一.File的简介:(java.io包) 生活中的文件: (1)文件的作用:持久化(瞬时状态的对立面状态) (1)文件的定义:一堆数据的集合 (2)文件存储的位置:磁盘,硬盘,软盘,U盘等等 计算机中 ...
- Inno SetUp安装包:如何在程序安装时卸载驱动程序
pnputil命令行方式卸载 如果您想通过命令行卸载.INF文件的驱动程序,您需要使用PnPUtil命令.以下是一个示例: pnputil /delete-driver oem0.inf /unins ...