LoveYou3000 opened a new issue, #40332:
URL: https://github.com/apache/shardingsphere/issues/40332

   ## Feature Request
   
   **For English only**, other languages will not be accepted.
   
   ### Is your feature request related to a problem?
   
   Yes, it is a performance problem in the SQL binding engine, most visible on 
wide tables where queries list many projection items.
   
   In `ProjectionsSegmentBinder#bind` 
(`infra/binder/core/src/main/java/org/apache/shardingsphere/infra/binder/engine/segment/dml/projection/ProjectionsSegmentBinder.java`
 on master, introduced by #34151 and available since 5.5.2), the loop eagerly 
builds `currentTableBinderContexts` on every iteration:
   
   ```java
   for (ProjectionSegment each : segment.getProjections()) {
       Multimap<CaseInsensitiveString, TableSegmentBinderContext> 
currentTableBinderContexts = createCurrentTableBinderContexts(binderContext, 
result.getProjections());
       result.getProjections().add(bind(binderContext, boundTableSegment, 
currentTableBinderContexts, tableBinderContexts, outerTableBinderContexts, 
each));
   }
   ```
   
   However, `currentTableBinderContexts` is only consumed by the retry path of 
the private `bind(...)` overload, which is taken only when the first binding 
attempt throws `ColumnNotFoundException` (the "current select projection 
reference" feature of #34151, e.g. `SELECT a + 1 AS b, b + 1 AS c FROM t`):
   
   ```java
   try {
       return bind(projectionSegment, binderContext, boundTableSegment, 
tableBinderContexts, outerTableBinderContexts);
   } catch (final ColumnNotFoundException ignored) {
       ...
       return bind(projectionSegment, binderContext, boundTableSegment, 
currentTableBinderContexts, outerTableBinderContexts);
   }
   ```
   
   In the common case where every projection binds successfully on the first 
attempt, each eagerly built multimap is discarded without ever being read.
   
   `createCurrentTableBinderContexts` is not cheap: it calls 
`SubqueryTableBindUtils.createSubqueryProjections(result.getProjections(), 
...)`, which iterates over all projections bound so far and allocates a new 
`ColumnProjectionSegment` / `ColumnSegment` / `ColumnSegmentBoundInfo` / 
`TableSegmentBoundInfo` for each one (recursively expanding the actual 
projections of shorthand segments).
   
   As a result, for a statement with `n` projection items the loop performs `1 
+ 2 + ... + (n-1) = n(n-1)/2` projection conversions and object allocations 
that are all thrown away, every time a statement context is created. For 
example, a `SELECT` listing all 200 columns of a wide table wastes about 19,900 
conversions per bind. ORM-generated queries, `INSERT ... SELECT` and explicit 
column lists on wide tables are typical victims.
   
   The same eager pattern was introduced once more for the `DISTINCT ON` items 
loop in #39944.
   
   ### Describe the feature you would like.
   
   Build `currentTableBinderContexts` lazily, i.e. only when the 
`ColumnNotFoundException` retry path is actually taken.
   
   `result.getProjections()` is only mutated after the inner `bind(...)` 
returns, so computing the contexts inside the `catch` block observes exactly 
the same list contents as the current eager computation. The change is 
therefore behavior-preserving:
   
   ```java
   for (ProjectionSegment each : segment.getProjections()) {
       result.getProjections().add(bind(binderContext, boundTableSegment, 
result.getProjections(), tableBinderContexts, outerTableBinderContexts, each));
   }
   
   private static ProjectionSegment bind(final SQLStatementBinderContext 
binderContext, final TableSegment boundTableSegment,
                                         final Collection<ProjectionSegment> 
boundProjections,
                                         final Multimap<CaseInsensitiveString, 
TableSegmentBinderContext> tableBinderContexts,
                                         final Multimap<CaseInsensitiveString, 
TableSegmentBinderContext> outerTableBinderContexts,
                                         final ProjectionSegment 
projectionSegment) {
       try {
           return bind(projectionSegment, binderContext, boundTableSegment, 
tableBinderContexts, outerTableBinderContexts);
       } catch (final ColumnNotFoundException ignored) {
           if (isWholeRowProjection(projectionSegment, binderContext, 
boundTableSegment, tableBinderContexts)) {
               return createWholeRowProjection((ColumnProjectionSegment) 
projectionSegment);
           }
           return bind(projectionSegment, binderContext, boundTableSegment,
                   createCurrentTableBinderContexts(binderContext, 
boundProjections), outerTableBinderContexts);
       }
   }
   ```
   
   With this change the happy path (no `ColumnNotFoundException`) performs zero 
extra work per projection, while statements that reference earlier projection 
aliases in the same `SELECT` keep working unchanged (covered by the existing 
tests of #34151). The same lazy treatment can be applied to the `DISTINCT ON` 
loop added by #39944.
   
   If the proposal looks good, I would like to submit a PR for it.
   
   Environment: observed in 5.5.2, and the code is still present on master as 
of today.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: 
[email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to