有没有办法改进这个请求?
Is there a way to improve this request?
环境:
- Java 8;具体来说,Oracle JDK 1.8u25;
- h2 作为 SQL 后端;
- 用于查询的jooq
我正在查询的 table/database 来自:
private static final String H2_URI_PREFIX = "jdbc:h2:";
private static final String H2_URI_POSTFIX
= ";LOG=0;LOCK_MODE=0;UNDO_LOG=0;CACHE_SIZE=131072";
private static final String H2_USERNAME = "sa";
private static final String H2_PASSWORD = "";
private static final List<String> H2_DDL = Arrays.asList(
"create table matchers ("
+ " id integer not null,"
+ " class_name varchar(255) not null,"
+ " matcher_type varchar(30) not null,"
+ " name varchar(1024) not null"
+ ");",
"create table nodes ("
+ " id integer not null,"
+ " parent_id integer not null,"
+ " level integer not null,"
+ " success integer not null,"
+ " matcher_id integer not null,"
+ " start_index integer not null,"
+ " end_index integer not null,"
+ " time long not null"
+ ");",
"alter table matchers add primary key(id);",
"alter table nodes add primary key(id);",
"alter table nodes add foreign key (matcher_id)"
+ " references matchers(id)"
);
// ...
private void doDdl(final DSLContext jooq)
{
H2_DDL.forEach(jooq::execute);
jooq.createIndex("nodes_parent_id").on(NODES, NODES.PARENT_ID)
.execute();
jooq.createIndex("nodes_start_index").on(NODES, NODES.START_INDEX)
.execute();
jooq.createIndex("nodes_end_index").on(NODES, NODES.END_INDEX)
.execute();
}
尽管我在这里展示了完整的 DDL 代码(注意 NODES
和 MATCHERS
是由 jooq 的代码生成包生成的),但只有 nodes
/NODES
table 很有趣。
nodes
中的一行table表示匹配事件;这里感兴趣的是 start_index
、end_index
和 level
列。保证start_index
小于等于end_index
; level
列是matcher树中的深度,深度从0开始;也就是说,对于匹配器路径 /a/b/c
中的某些匹配器 c
,c
的 level
将为 2.
现在,我要得到的结果如下:
给定一个行范围(10、25 或 50),return 一个映射,其中键是行号,值是该行的解析树的最大深度;只应被视为当前为此行激活的匹配器
一条线由间隔 [start, end)
(start
包含,end
不包含)具体化。如果以下两个语句都为真,则匹配器被认为对给定行有效:
- 它的开始索引严格小于该行的结束索引;和
- 它的结束索引大于或等于该行的开始索引。
现在,我如何解决这个问题:
- 我创建了一个由一系列 SQL
case
语句组成的虚拟列,每行一个,检查给定行的匹配器是否处于活动状态;此列名为 line
;
- 我做
select line, max(level)
并按 line
分组,附加条件是结束索引应大于或等于第一行的起始索引,并且起始索引应严格小于比最后一行的结束索引。
代码:
@Override
public Map<Integer, Integer> getDepthMap(final int startLine,
final int wantedLines)
throws GrappaDebuggerException
{
loadInputBuffer();
final List<IndexRange> ranges
= IntStream.range(startLine, startLine + wantedLines)
.mapToObj(inputBuffer::getLineRange)
.collect(Collectors.toList());
final int startIndex = ranges.get(0).start;
final int endIndex = ranges.get(ranges.size() - 1).end;
final Condition indexCondition = NODES.START_INDEX.lt(endIndex)
.and(NODES.END_INDEX.ge(startIndex));
final Field<Integer> lineField = getLineField(startLine, ranges);
final Map<Integer, Integer> ret = new HashMap<>();
jooq.select(lineField, DSL.max(NODES.LEVEL))
.from(NODES)
.where(indexCondition)
.groupBy(lineField)
.forEach(r -> ret.put(r.value1(), r.value2() + 1));
IntStream.range(startLine, startLine + wantedLines)
.forEach(line -> ret.putIfAbsent(line, 0));
return ret;
}
private Field<Integer> getLineField(final int startLine,
final List<IndexRange> ranges)
{
CaseConditionStep<Integer> step = DSL.decode()
.when(activeThisRange(ranges.get(0)), startLine);
final int size = ranges.size();
for (int i = 1; i < size; i++)
step = step.when(activeThisRange(ranges.get(i)), startLine + i);
return step.as("line");
}
private static Condition activeThisRange(final IndexRange range)
{
return NODES.START_INDEX.lt(range.end)
.and(NODES.END_INDEX.ge(range.start));
}
如果我查询 25 行(即第 n 到 n + 24 行,对于某些 n),这个请求在 2200 万个 table 条目的最繁忙部分大约需要 15 秒,但是有没有改进方法?
请注意,更改 SQL 引擎不是一个选项;这是一个 GUI 应用程序,其数据库为 "forgettable";而且我不想要求安装 "full fledged" RDBMS 引擎!
我不太了解 H2,但是由于您的谓词总是会同时命中 START_INDEX
和 END_INDEX
,因此最好创建两列的索引:
jooq.createIndex("better_index")
.on(NODES, NODES.START_INDEX, NODES.END_INDEX)
.execute();
原因是 SQL 引擎只需要访问磁盘并扫描索引一次,因为谓词的所有相关信息都已包含在索引中。这样会大大降低你的IO。
同样,不确定 H2 是否包含此内容(双关语意),但如果您还向索引添加 NODES.LEVEL
,您将有一个所谓的 covering index,即索引包含此特定查询所需的所有数据,无需再次访问磁盘(对于 MAX()
函数):
jooq.createIndex("covering_index")
.on(NODES,
NODES.START_INDEX,
NODES.END_INDEX
NODES.LEVEL)
.execute();
Here's also a very interesting question about range queries on PostgreSQL.
环境:
- Java 8;具体来说,Oracle JDK 1.8u25;
- h2 作为 SQL 后端;
- 用于查询的jooq
我正在查询的 table/database 来自:
private static final String H2_URI_PREFIX = "jdbc:h2:";
private static final String H2_URI_POSTFIX
= ";LOG=0;LOCK_MODE=0;UNDO_LOG=0;CACHE_SIZE=131072";
private static final String H2_USERNAME = "sa";
private static final String H2_PASSWORD = "";
private static final List<String> H2_DDL = Arrays.asList(
"create table matchers ("
+ " id integer not null,"
+ " class_name varchar(255) not null,"
+ " matcher_type varchar(30) not null,"
+ " name varchar(1024) not null"
+ ");",
"create table nodes ("
+ " id integer not null,"
+ " parent_id integer not null,"
+ " level integer not null,"
+ " success integer not null,"
+ " matcher_id integer not null,"
+ " start_index integer not null,"
+ " end_index integer not null,"
+ " time long not null"
+ ");",
"alter table matchers add primary key(id);",
"alter table nodes add primary key(id);",
"alter table nodes add foreign key (matcher_id)"
+ " references matchers(id)"
);
// ...
private void doDdl(final DSLContext jooq)
{
H2_DDL.forEach(jooq::execute);
jooq.createIndex("nodes_parent_id").on(NODES, NODES.PARENT_ID)
.execute();
jooq.createIndex("nodes_start_index").on(NODES, NODES.START_INDEX)
.execute();
jooq.createIndex("nodes_end_index").on(NODES, NODES.END_INDEX)
.execute();
}
尽管我在这里展示了完整的 DDL 代码(注意 NODES
和 MATCHERS
是由 jooq 的代码生成包生成的),但只有 nodes
/NODES
table 很有趣。
nodes
中的一行table表示匹配事件;这里感兴趣的是 start_index
、end_index
和 level
列。保证start_index
小于等于end_index
; level
列是matcher树中的深度,深度从0开始;也就是说,对于匹配器路径 /a/b/c
中的某些匹配器 c
,c
的 level
将为 2.
现在,我要得到的结果如下:
给定一个行范围(10、25 或 50),return 一个映射,其中键是行号,值是该行的解析树的最大深度;只应被视为当前为此行激活的匹配器
一条线由间隔 [start, end)
(start
包含,end
不包含)具体化。如果以下两个语句都为真,则匹配器被认为对给定行有效:
- 它的开始索引严格小于该行的结束索引;和
- 它的结束索引大于或等于该行的开始索引。
现在,我如何解决这个问题:
- 我创建了一个由一系列 SQL
case
语句组成的虚拟列,每行一个,检查给定行的匹配器是否处于活动状态;此列名为line
; - 我做
select line, max(level)
并按line
分组,附加条件是结束索引应大于或等于第一行的起始索引,并且起始索引应严格小于比最后一行的结束索引。
代码:
@Override
public Map<Integer, Integer> getDepthMap(final int startLine,
final int wantedLines)
throws GrappaDebuggerException
{
loadInputBuffer();
final List<IndexRange> ranges
= IntStream.range(startLine, startLine + wantedLines)
.mapToObj(inputBuffer::getLineRange)
.collect(Collectors.toList());
final int startIndex = ranges.get(0).start;
final int endIndex = ranges.get(ranges.size() - 1).end;
final Condition indexCondition = NODES.START_INDEX.lt(endIndex)
.and(NODES.END_INDEX.ge(startIndex));
final Field<Integer> lineField = getLineField(startLine, ranges);
final Map<Integer, Integer> ret = new HashMap<>();
jooq.select(lineField, DSL.max(NODES.LEVEL))
.from(NODES)
.where(indexCondition)
.groupBy(lineField)
.forEach(r -> ret.put(r.value1(), r.value2() + 1));
IntStream.range(startLine, startLine + wantedLines)
.forEach(line -> ret.putIfAbsent(line, 0));
return ret;
}
private Field<Integer> getLineField(final int startLine,
final List<IndexRange> ranges)
{
CaseConditionStep<Integer> step = DSL.decode()
.when(activeThisRange(ranges.get(0)), startLine);
final int size = ranges.size();
for (int i = 1; i < size; i++)
step = step.when(activeThisRange(ranges.get(i)), startLine + i);
return step.as("line");
}
private static Condition activeThisRange(final IndexRange range)
{
return NODES.START_INDEX.lt(range.end)
.and(NODES.END_INDEX.ge(range.start));
}
如果我查询 25 行(即第 n 到 n + 24 行,对于某些 n),这个请求在 2200 万个 table 条目的最繁忙部分大约需要 15 秒,但是有没有改进方法?
请注意,更改 SQL 引擎不是一个选项;这是一个 GUI 应用程序,其数据库为 "forgettable";而且我不想要求安装 "full fledged" RDBMS 引擎!
我不太了解 H2,但是由于您的谓词总是会同时命中 START_INDEX
和 END_INDEX
,因此最好创建两列的索引:
jooq.createIndex("better_index")
.on(NODES, NODES.START_INDEX, NODES.END_INDEX)
.execute();
原因是 SQL 引擎只需要访问磁盘并扫描索引一次,因为谓词的所有相关信息都已包含在索引中。这样会大大降低你的IO。
同样,不确定 H2 是否包含此内容(双关语意),但如果您还向索引添加 NODES.LEVEL
,您将有一个所谓的 covering index,即索引包含此特定查询所需的所有数据,无需再次访问磁盘(对于 MAX()
函数):
jooq.createIndex("covering_index")
.on(NODES,
NODES.START_INDEX,
NODES.END_INDEX
NODES.LEVEL)
.execute();
Here's also a very interesting question about range queries on PostgreSQL.