Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ public IdGenerator<IdType> resetId(int initialId) {
return this;
}

/**
* The id value that {@link #getNextId()} would hand out next. Used to seed a fresh
* per-execution id generator from an existing one so ids never collide.
*/
public int getCurrentId() {
return nextId;
}

public abstract IdType getNextId();

}
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,53 @@ private StatementContext(ConnectContext connectContext, OriginStatement originSt
}
}

/**
* Create a fresh StatementContext for the next EXECUTE of a prepared statement.
*
* <p>A prepared statement keeps its StatementContext inside {@code PreparedStatementContext}
* for the whole lifetime of the connection. Reusing the same object across executions makes
* its per-statement state (bound tables, CTE maps, statistics, snapshots, connector scope,
* ...) accumulate and it is only released when the connection closes, which can OOM
* long-lived connections. Instead of clearing in place, allocate a brand-new context per
* EXECUTE and copy over only the state that must survive between executions, so the previous
* context becomes unreachable and is promptly GC'd.
*
* <p>Carried over:
* <ul>
* <li>id generator positions, so ids generated during this execution never collide with
* ids already present in the cached analyzed plan from PREPARE;</li>
* <li>the placeholder real expressions bound by this EXECUTE (the protocol layer fills
* them on the previous context before this method runs) and the placeholder list;</li>
* <li>the placeholder to comparison-slot registry used by the short-circuit fast path;</li>
* <li>the short-circuit and nondeterministic flags that gate the short-circuit fast path
* before this execution re-plans.</li>
* </ul>
* Everything else (tables, CTEs, statistics, snapshots, planner resources, connector
* scope, ...) starts empty/fresh on the new context.
*/
public StatementContext createNextExecuteContext() {
// Continue the id generators from the previous context. The cached analyzed plan from
// PREPARE (and every prior execution) already consumed ids from them, so a fresh
// generator starting at 0 would collide with those ids during this execution's planning.
StatementContext next = new StatementContext(connectContext, originStatement,
exprIdGenerator.getCurrentId());
next.objectIdGenerator.resetId(objectIdGenerator.getCurrentId());
next.relationIdGenerator.resetId(relationIdGenerator.getCurrentId());
next.cteIdGenerator.resetId(cteIdGenerator.getCurrentId());
next.talbeIdGenerator.resetId(talbeIdGenerator.getCurrentId());
next.placeHolderIdGenerator.resetId(placeHolderIdGenerator.getCurrentId());
// Placeholder bindings of this EXECUTE, and the comparison-slot registry used to replace
// conjuncts on the cached short-circuit plan without re-planning.
next.idToPlaceholderRealExpr.putAll(idToPlaceholderRealExpr);
next.idToComparisonSlot.putAll(idToComparisonSlot);
next.placeholders = new ArrayList<>(placeholders);
// Short-circuit gating flags are computed by the previous execution's planning and gate
// the fast path of this execution before any re-planning happens.
next.isShortCircuitQuery = isShortCircuitQuery;
next.hasNondeterministic = hasNondeterministic;
return next;
}

public void setNeedLockTables(boolean needLockTables) {
this.needLockTables = needLockTables;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,20 @@ public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {

@Override
public void run(ConnectContext ctx, StmtExecutor executor) throws Exception {
StatementContext statementContext = ctx.getStatementContext();
statementContext.setPrepareStage(false);
statementContext.setIsInsert(false);
statementContext.resetMvccSnapshots();
PreparedStatementContext preparedStmtCtx = ctx.getPreparedStementContext(stmtName);
if (null == preparedStmtCtx) {
throw new AnalysisException(
"prepare statement " + stmtName + " not found, maybe expired");
}
PrepareCommand prepareCommand = preparedStmtCtx.command;
// Allocate a fresh StatementContext per EXECUTE so the per-statement state accumulated by
// prior executions (bound tables, CTE maps, statistics, snapshots, ...) is released
// promptly instead of living as long as the connection, which can OOM long-lived
// connections. The necessary cross-execution state (placeholder bindings, comparison
// slots, id generator positions, short-circuit flags) is carried over to the new context.
StatementContext statementContext = preparedStmtCtx.nextStatementContext();
statementContext.setPrepareStage(false);
statementContext.setIsInsert(false);
LogicalPlan logicalPlan = prepareCommand.getLogicalPlan();
List<LogicalPlan> relationRoots = new ArrayList<>();
if (logicalPlan instanceof InsertIntoTableCommand) {
Expand Down Expand Up @@ -127,17 +131,27 @@ public void run(ConnectContext ctx, StmtExecutor executor) throws Exception {
if (logicalPlan instanceof InsertIntoTableCommand
|| logicalPlan instanceof InsertOverwriteTableCommand
|| logicalPlan instanceof UpdateCommand) {
ctx.getStatementContext().setIsInsert(true);
statementContext.setIsInsert(true);
}
LogicalPlanAdapter planAdapter = new LogicalPlanAdapter(
logicalPlan, executor.getContext().getStatementContext());
LogicalPlanAdapter planAdapter = new LogicalPlanAdapter(logicalPlan, statementContext);
// Point the executor (and its ConnectContext) at the fresh per-execution context so the
// following execution/result-sending uses it instead of the previous execution's context.
executor.setStatementContext(statementContext);
executor.setParsedStmt(planAdapter);
// If it's not a short circuit query, schema version or file cache query limit changed, or
// the statement has nondeterministic functions, then reanalyze and plan.
if (executor.getContext().getStatementContext().isShortCircuitQuery()
&& preparedStmtCtx.shortCircuitQueryContext.isPresent()
&& preparedStmtCtx.shortCircuitQueryContext.get().isReusable(ctx)
&& !executor.getContext().getStatementContext().hasNondeterministic()) {
boolean hasShortCircuitContext = preparedStmtCtx.shortCircuitQueryContext.isPresent();
boolean shortCircuitContextReusable = hasShortCircuitContext
&& preparedStmtCtx.shortCircuitQueryContext.get().isReusable(ctx);
// Reuse the cached short-circuit plan only when table metadata is unchanged and the statement
// has no nondeterministic functions. Otherwise fall back to the normal execution path below.
if (statementContext.isShortCircuitQuery()
&& hasShortCircuitContext
&& shortCircuitContextReusable
&& !statementContext.hasNondeterministic()) {
// The fresh per-execution context carries the short-circuit flag but not the cached plan.
// Install the just-validated cache before the direct path: result sending reads it via
// statementContext.getShortCircuitQueryContext(), and the fallback (building one from a
// null planner, since this path skips planning) would NPE.
statementContext.setShortCircuitQueryContext(preparedStmtCtx.shortCircuitQueryContext.get());
PointQueryExecutor.directExecuteShortCircuitQuery(executor, preparedStmtCtx, statementContext);
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,31 @@ public long getStartTime() {
return startTime;
}

public StatementContext getStatementContext() {
return statementContext;
}

public void setStatementContext(StatementContext statementContext) {
this.statementContext = statementContext;
}

/**
* Allocate a fresh StatementContext for this EXECUTE and replace the previous one, so the
* old context (with the per-statement state accumulated by prior executions: bound tables,
* CTE maps, statistics, snapshots, ...) becomes unreachable and is promptly GC'd.
*
* <p>A prepared statement lives as long as its connection. Reusing one StatementContext
* across all executions would keep growing those maps and could OOM long-lived connections,
* so we create a new object per execution and carry over only the state that must survive
* (placeholder bindings, comparison slots, id generator positions, short-circuit flags).
*
* @return the fresh StatementContext to use for the current execution
*/
public StatementContext nextStatementContext() {
statementContext = statementContext.createNextExecuteContext();
return statementContext;
}

public void setStartTime() {
startTime = System.currentTimeMillis();
}
Expand Down
12 changes: 11 additions & 1 deletion fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ public class StmtExecutor {

private static final Pattern beIpPattern = Pattern.compile("\\[(\\d+):");
private ConnectContext context;
private final StatementContext statementContext;
private StatementContext statementContext;
private MysqlSerializer serializer;
private OriginStatement originStmt;
private StatementBase parsedStmt;
Expand Down Expand Up @@ -2089,6 +2089,16 @@ public StatementBase setParsedStmt(StatementBase parsedStmt) {
return parsedStmt;
}

/**
* Replace the executor statement context and synchronize it to the owning ConnectContext.
*/
public void setStatementContext(StatementContext statementContext) {
this.statementContext = statementContext;
this.statementContext.setConnectContext(context);
this.statementContext.setOriginStatement(originStmt);
this.context.setStatementContext(statementContext);
}

public List<Slot> planPrepareStatementSlots() throws Exception {
parseByNereids();
Preconditions.checkState(parsedStmt instanceof LogicalPlanAdapter,
Expand Down
Loading
Loading