1. 程式人生 > 其它 >seata 提交全域性事務失敗 Unable to commit against JDBC Connection

seata 提交全域性事務失敗 Unable to commit against JDBC Connection

技術標籤:javamysqlspring資料庫hibernate

  • 前言

將seata部署到生產環境中出現問題,業務程式碼正常執行結束後,應該提交全域性事務,但是日誌卻打出 rollback status: Rollbacked。簡略的異常資訊如下

15:48:13.947 [http-nio2-8821-exec-70] INFO  i.s.t.a.DefaultGlobalTransaction - [rollback,178] - [10.000.000.00:8091:93009917309542400] rollback status: Rollbacked
15:48:13.950 [http-nio2-8821-exec-70] ERROR c.x.c.j.r.GloablExceptionHandler - [handleException,19] - 伺服器異常
org.springframework.orm.jpa.JpaSystemException: Unable to commit against JDBC Connection; nested exception is org.hibernate.TransactionException: Unable to commit against JDBC Connection
        at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:351)
        at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:253)
        at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:98)
        at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:532)
        ... 85 common frames omitted
Caused by: io.seata.rm.datasource.exec.LockConflictException: get global lock fail, xid:10.000.00.00:8091:93009917309542400, lockKeys:scan_code_record:7df68bd5ea30425abb8022bde5f0e296;sold_sc_code:cadf510822f244169927f0c7fa0ecd30;sold_receipt:16c74a3a11de4f84b12c1a6cec13a1d6;scan_code_product:e0c05a6a891f41c7853cc77b6237003b;sold_note:4ae09914f1b34fde9b8ae02c1fb5aa5f
        at io.seata.rm.datasource.ConnectionProxy.recognizeLockKeyConflictException(ConnectionProxy.java:155)
        at io.seata.rm.datasource.ConnectionProxy.processGlobalTransactionCommit(ConnectionProxy.java:221)
        at io.seata.rm.datasource.ConnectionProxy.doCommit(ConnectionProxy.java:199)
        at io.seata.rm.datasource.ConnectionProxy.lambda$commit$0(ConnectionProxy.java:184)
        at io.seata.rm.datasource.ConnectionProxy$LockRetryPolicy.execute(ConnectionProxy.java:292)
        at io.seata.rm.datasource.ConnectionProxy.commit(ConnectionProxy.java:183)
        at org.hibernate.resource.jdbc.internal.AbstractLogicalConnectionImplementor.commit(AbstractLogicalConnectionImplementor.java:81)
        ... 88 common frames omitted


  • 解決過程:

看到日誌中的出現“jpa”相關的資訊,但是專案中沒有使用任何JPA的框架,所以排除專案自身的問題。

因為引入了seata框架,再加上最後的異常資訊也指向了seata,群友討論懷疑是競爭鎖導致的。所以修改seata服務的引數

client.rm.lock.retryInterval 20 校驗或佔用全域性鎖重試間隔 預設10,單位毫秒
client.rm.lock.retryTimes 60 校驗或佔用全域性鎖重試次數 預設30

然後重啟應用,依舊出現剛剛的異常。

想起檢視一下 seata的日誌,預設在 home/logs 下面


2021-01-14 16:38:28.243 ERROR --- [  ServerHandlerThread_1_19_500] i.s.s.s.db.lock.LockStoreDataBaseDAO     : Global lock batch acquire error: Data truncation: Data too long for column 'row_key' at row 1
==>
java.sql.BatchUpdateException: Data truncation: Data too long for column 'row_key' at row 1
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
        at com.mysql.cj.util.Util.handleNewInstance(Util.java:192)
        at com.mysql.cj.util.Util.getInstance(Util.java:167)
        at com.mysql.cj.util.Util.getInstance(Util.java:174)

發現出現了 row_key超長的報錯資訊。查看錶結構

-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
    `row_key`        VARCHAR(128) NOT NULL,
    `xid`            VARCHAR(96),
    `transaction_id` BIGINT,
    `branch_id`      BIGINT       NOT NULL,
    `resource_id`    VARCHAR(256),
    `table_name`     VARCHAR(32),
    `pk`             VARCHAR(36),
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

使用官方提供的sql建表,按說不應該出現問題。當時異常資訊如此明顯,於是嘗試將長度改為255。

然後再次執行方法,問題解決。

  • 深扒原理

扒seata原始碼,找到 row_key的生成策略如下

protected List<LockDO> convertToLockDO(List<RowLock> locks) {
        List<LockDO> lockDOs = new ArrayList<>();
        if (CollectionUtils.isEmpty(locks)) {
            return lockDOs;
        }
        for (RowLock rowLock : locks) {
            LockDO lockDO = new LockDO();
            lockDO.setBranchId(rowLock.getBranchId());
            lockDO.setPk(rowLock.getPk());
            lockDO.setResourceId(rowLock.getResourceId());
            lockDO.setRowKey(getRowKey(rowLock.getResourceId(), rowLock.getTableName(), rowLock.getPk()));
            lockDO.setXid(rowLock.getXid());
            lockDO.setTransactionId(rowLock.getTransactionId());
            lockDO.setTableName(rowLock.getTableName());
            lockDOs.add(lockDO);
        }
        return lockDOs;
    }

因為 row_key 的生成策略是 資源名 + 表名 + 主鍵,比如

jdbc:mysql://116.62.62.62/seata-storage

生成環境使用連線比較長,再拼接上 表名 + 主鍵 所以出現了 row_key 超長的情況。