ITADN

Global lock batch acquire always fails on Dameng(DM): LockStoreDataBaseDAO.doAcquireLocks relies on executeBatch().length

#8144Closedlhozy 创建于 2026-06-17
L
lhozycommented
# [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 条评论