seata 提交全域性事務失敗 Unable to commit against JDBC Connection
阿新 • • 發佈:2021-01-21
技術標籤: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 超長的情況。