li3zhi4 opened a new issue, #11741:
URL: https://github.com/apache/seatunnel/issues/11741

   ### Search before asking
   
   - [X] I had searched in the 
[issues](https://github.com/apache/seatunnel/issues?q=is%3Aissue) and found no 
issue/PR covering composite-key **multi-column chunk splitting** for the JDBC 
source (`connector-jdbc` has its own splitter and is a different module from 
the CDC connectors):
     - #9949 "[Bug] [mysql-cdc] ... composite index ... retrieves fields other 
than the primary key ... as the partitioning column" (closed) — split-column 
**selection**, fixed by #10236/#10319; not multi-column splitting.
     - #7794 "[Bug] [MYSQL-CDC] ... 不使用主键(id)进行分割,而是使用唯一键" (closed) — 
split-column selection, single-column splitting.
     - #10596 / #10604 "[Feature][CDC/JDBC] Add option to disable 
sampling-based sharding" (closed/merged) — adds the 
`split.allow-sampling=false` toggle that this issue's scenario relies on 
(single-column split degrades when sampling is off and the first key column 
repeats heavily); related prerequisite, but that work is about the sampling 
switch itself, not multi-column composite-key splitting.
     - #11396 "[Fix][Connector-V2][Jdbc] Fix DATE partition column splitting" 
(open) — single-column DATE type handling.
     - #8301 "[Bug] [JDBC source] Automatic Split Chunk does not support string 
type primary key" (closed, stale) — single-column **String** PK splitting is 
slow; addressed by the single-column string split strategy, not multi-column 
splitting.
     - #10861 "[Improve][Connector-V2][JDBC] Add string split strategy" 
(merged) — single-column String partition strategy 
(`none`/`hash`/`range`/`auto`); not composite-key splitting.
   
   ### What happened
   
   The JDBC source (`connector-jdbc`) splits a table into chunks using **only 
the first column of the primary key**. `ChunkSplitter.findSplitKey()` (verified 
on `dev` 2026-08-10):
   
   ```java
   PrimaryKey pk = schema.getPrimaryKey();
   if (pk != null) {
       for (String pkField : pk.getColumnNames()) {
           Column column = columnMap.get(pkField);
           if (isSupportSplitColumn(column)) {
               return Optional.of(new SeaTunnelRowType(
                       new String[] {pkField},                    // ← single 
column only
                       new SeaTunnelDataType[] {column.getDataType()}));
           }
       }
   }
   ```
   
   For tables with a **composite primary key** (`PRIMARY KEY (col1, col2, 
...)`), the whole split is driven by the first key column alone:
   
   1. **First column has (almost) one distinct value** — the worst case. The 
chunk loop hits `min.equals(max)` and returns a **single full-table chunk**, so 
the whole table is read by one reader: parallelism is lost entirely and a large 
table's snapshot takes as long as a single-threaded scan.
   2. **First column has low cardinality / many duplicates** — 
`splitUnevenlySizedChunks`' `nextChunkEnd()` must keep calling `queryMin` to 
skip past duplicated boundary values; chunk count collapses and remaining 
chunks are severely skewed, so a single chunk can dominate the job duration.
   3. `DynamicChunkSplitter` on `dev` has **no** multi-column (`Composite`) 
methods at all — grep for `Composite`/`queryNextChunkMaxComposite` returns zero 
hits (verified 2026-08-10).
   
   ### SeaTunnel Version
   
   Current upstream `dev` (verified 2026-08-10 by code inspection); the same 
single-column split behavior exists in 2.3.13.
   
   ### SeaTunnel Config
   
   ```hocon
   source {
     Jdbc {
       url = "jdbc:mysql://<host>:3306/<db>"
       driver = "com.mysql.cj.jdbc.Driver"
       user = "user"
       password = "pass"
       table_path = "<db>.<composite_key_table>"
       parallelism = 4
     }
   }
   ```
   
   A table like:
   
   ```sql
   CREATE TABLE t (
     order_id  BIGINT,
     line_no   INT,
     payload   TEXT,
     PRIMARY KEY (order_id, line_no)
   );
   ```
   
   ### Root cause (confirmed by code inspection)
   
   - `ChunkSplitter.findSplitKey()` returns a single-column `SeaTunnelRowType` 
(first PK column supported by `isSupportSplitColumn`).
   - `DynamicChunkSplitter` on `dev` exposes only single-column `queryMinMax` / 
`queryMin` / `queryNextChunkMax` (taking `String splitColumnName`); there is no 
multi-column path.
   - The bounding-box predicate issue also exists for the data-scan SQL once 
multi-column keys are introduced (per-column AND comparison is not 
lexicographic).
   
   ### Scope of application (which tables are affected)
   
   The multi-column split path would apply only to composite-PK tables on the 
JDBC source:
   
   | Table shape | Split key selection (dev) | Path taken |
   |---|---|---|
   | **Composite PK** (≥2 columns) | `findSplitKey()` picks **only the first 
supported PK column** → single-column split on one key | Degraded (this issue): 
single-chunk or severely skewed chunks when the first column repeats heavily |
   | Single PK (1 column) | First supported PK column | Unchanged single-column 
path |
   | User-configured `partition_column` | Always wins, single column | 
Unchanged |
   | Query-based table (`query` set, no `partition_column`) | No split key → 
single split | Unchanged |
   | No PK | First unique-key column, else single split | Unchanged |
   
   ### Design rationale (why this is the right approach)
   
   1. **Tuple-ordered splitting is the correct semantics for a composite key** 
— chunk boundaries must follow the key's lexicographic order, otherwise ranges 
overlap or leave gaps. With a single-column split key the lexicographic order 
is trivial, but for a composite key `(col1, col2, ...)` the correct boundary 
condition is a tuple comparison (`(col1, col2) > (v1, v2)`), not per-column AND 
predicates. Splitting on the full key tuple is therefore the direct, 
correctness-first consequence of having a composite PK.
   2. **Expanded OR/AND form is the index-friendly way to express tuple 
comparisons** — a row-constructor comparison `(a, b) >= (?, ?)` often makes 
MySQL's optimizer fall back to a full scan, while the equivalent expanded form 
`a > ? OR (a = ? AND b >= ?)` lets the composite index be used. This is a 
standard SQL technique for multi-column range scans, so the fix is not a 
workaround but the canonical SQL shape.
   3. **Directly complements #10604** — with the sampling switch 
(`split.allow-sampling=false`) now available, tables that are unevenly 
distributed go straight to `splitUnevenlySizedChunks`; for composite-PK tables 
whose first column repeats heavily, only tuple-ordered multi-column splitting 
keeps chunks balanced. This issue is the natural next step after that switch.
   
   ### Proposed fix
   
   1. `ChunkSplitter.findSplitKey()`: when the primary key has multiple 
columns, return all supported PK columns as the split key (multi-column 
`SeaTunnelRowType`), with a user override (`partition_column`) still winning.
   2. `DynamicChunkSplitter`: add multi-column `queryMinMaxComposite` / 
`queryNextChunkMaxComposite` / `queryMinComposite` and `createCompositeSplit` — 
tuple comparisons **expanded into OR/AND form** (`col1 > ? OR (col1 = ? AND 
col2 >= ?)`) so the composite index can be used, and `ORDER BY ... LIMIT 
chunkSize` for boundary walking.
   3. Keep single-column behavior unchanged when the table has a single-column 
key (backward compatible).
   
   ### Verification
   
   Unit tests (composite `findSplitKey`, multi-column 
`queryMinMax`/`queryNextChunkMax`, expanded OR/AND SQL shape, half-open 
boundary semantics) plus an official JDBC E2E on `dev` with a composite-PK 
table asserting PK-level source/target consistency — to be included in the PR.
   
   ### Zeta or Flink or Spark Version
   
   zeta
   
   ### Java or Scala Version
   
   Java 8 / 11 (both OK)
   
   ### Screenshots
   
   _No response_
   
   ### Are you willing to submit PR?
   
   - [X] Yes I am willing to submit a PR! (port to `dev` in progress)
   
   ### Code of Conduct
   
   - [X] I agree to follow this project's [Code of 
Conduct](https://www.apache.org/foundation/policies/conduct)
   


-- 
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