Global lock batch acquire always fails on Dameng(DM): LockStoreDataBaseDAO.doAcquireLocks relies on executeBatch().length
# [Bug] Global lock batch acquire always fails on Dameng (DM) — `LockStoreDataBaseDAO.doAcquireLocks` relies on `executeBatch().length`
## Ⅰ. Describe the bug
When the Seata Server uses **DB store mode** with a **Dameng (DM)** database, any branch transaction that needs to acquire **two or more row locks** in a single `BranchRegister` **always fails** to acquire the global lock, even though the `lock_table` is empty and there is no real conflict.
The client side gets:
```
io.seata.rm.datasource.exec.LockConflictException: get global lock fail, xid:..., lockKeys:XXX:1;YYY:2
```
The server side logs:
```
LockStoreDataBaseDAO : Global lock batch acquire failed, xid ... branchId ... pks [1, 2]
AbstractExceptionHandler : this request cannot acquire global lock ...
```
A branch that locks only **one** row works fine. So in practice **any business transaction that modifies 2+ rows (master/detail, entity + i18n text, etc.) can never commit**, which makes AT mode effectively unusable on DM for real workloads.
## Ⅱ. Root cause
`org.apache.seata.server.storage.db.lock.LockStoreDataBaseDAO#doAcquireLocks`:
```java
protected boolean doAcquireLocks(Connection conn, List<LockDO> lockDOs) throws SQLException {
...
for (LockDO lockDO : lockDOs) {
... ps.addBatch();
}
return ps.executeBatch().length == lockDOs.size(); // <-- here
}
```
It judges batch success by `executeBatch().length == lockDOs.size()`.
The DM JDBC driver (`DmdbPreparedStatement`) implements batch execution via `executeBatchByRow()` and **aggregates** the per-statement results through `ExecuteRetInfo.union(...)`, so `executeBatch()` returns an `int[]` whose **length does not equal the number of batched statements**.
As a result:
- The lock rows **are actually inserted** into `lock_table`.
- But `executeBatch().length == lockDOs.size()` evaluates to **false**.
- `doAcquireLocks` returns `false`, the caller does `conn.rollback()` (the inserted lock rows are discarded), and the branch register is reported as a lock conflict.
Single-row branches take the `doAcquireLock` (non-batch) path, which does not have this check, so they succeed — which is why the failure only appears for multi-row branches.
> Per the JDBC spec, the return value of `Statement.executeBatch()` is an array of update counts where each element may be a count, `SUCCESS_NO_INFO (-2)`, or `EXECUTE_FAILED (-3)`. Assuming `length == number of statements` is not guaranteed across drivers; relying on it is the bug.
## Ⅲ. How to reproduce
1. Seata Server `store.mode=db`, store DB = Dameng (DM8).
2. Any AT-mode business transaction whose single branch updates/inserts **≥ 2 rows** (so the branch reports `lockKey` with 2+ pks).
3. Branch register fails with `Global lock batch acquire failed` against an empty `lock_table`.
## Ⅳ. Expected behavior
The batch lock acquire should succeed when the lock rows are inserted without conflict, regardless of the driver-specific length of the `executeBatch()` return array. Real conflicts (duplicate `row_key`) still surface as `SQLIntegrityConstraintViolationException` and are already handled.
## Ⅴ. Environment
- Seata version: **2.6.0** (the same code is present on the current `2.x` branch).
- DB store: Dameng (DM8), DM JDBC driver `DmJdbcDriver18`.
- The fix is proposed in the accompanying PR.
## Ⅵ. Suggested fix
Stop relying on `executeBatch().length`. Detect failure via `Statement.EXECUTE_FAILED` instead:
```java
int[] result = ps.executeBatch();
for (int updated : result) {
if (updated == java.sql.Statement.EXECUTE_FAILED) {
return false;
}
}
return true;
```
This is correct for drivers that return one element per statement and for drivers (like DM) that aggregate the result array, while preserving the existing conflict handling (the `catch (SQLIntegrityConstraintViolationException ...)` branch).
关闭于 2026-06-22 0 条评论