【发布时间】:2015-04-09 22:02:55
【问题描述】:
环境:
- Java 8;具体来说,Oracle JDK 1.8u25;
- h2 作为 SQL 后端;
- jooq 用于查询。
我从中查询的表/数据库:
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 表是有趣的。
nodes 表中的一行代表一个匹配事件;这里感兴趣的是start_index、end_index 和level 列。保证start_index小于等于end_index;对于level列,是匹配树中的深度,深度从0开始;也就是说,对于匹配器路径/a/b/c 中的某个匹配器c,c 的level 将为2。
现在,我想要得到的结果如下:
给定一个行范围(10、25 或 50),返回一个映射,其中键是行号,值是该行的解析树的最大深度;只应被视为当前为此行活动的匹配器
一条线由间隔[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 到 n + 24 行),这个请求在 2200 万表条目的最繁忙部分大约需要 15 秒,但是有什么方法可以改进它吗?
请注意,更改 SQL 引擎不是一种选择;这是一个 GUI 应用程序,其数据库是“容易忘记的”;而且我不想要求安装“成熟”的 RDBMS 引擎!
【问题讨论】: