[seata]1.6.1,pgsql自增主键、当insert语句不包含主键列时,报[ShouldNeverHappenException: pkIndex is not found]

2024-04-28 839 views
6

seata1.6.1,AT模式,使用pgsql自增主键、且insert语句不包含主键列时,抛出异常[ShouldNeverHappenException: pkIndex is not found]

2023-01-28 11:05:50.316 ERROR 24928 --- [nio-8082-exec-4] i.s.r.d.exec.AbstractDMLBaseExecutor     : execute executeAutoCommitTrue error:pkIndex is not found

io.seata.common.exception.ShouldNeverHappenException: pkIndex is not found
    at io.seata.rm.datasource.exec.BaseInsertExecutor.parsePkValuesFromStatement(BaseInsertExecutor.java:142) ~[seata-all-1.6.1.jar:1.6.1]
    at io.seata.rm.datasource.exec.postgresql.PostgresqlInsertExecutor.getPkValuesByColumn(PostgresqlInsertExecutor.java:70) ~[seata-all-1.6.1.jar:1.6.1]
    at io.seata.rm.datasource.exec.postgresql.PostgresqlInsertExecutor.getPkValues(PostgresqlInsertExecutor.java:65) ~[seata-all-1.6.1.jar:1.6.1]
    at io.seata.rm.datasource.exec.BaseInsertExecutor.afterImage(BaseInsertExecutor.java:78) ~[seata-all-1.6.1.jar:1.6.1]
    at io.seata.rm.datasource.exec.AbstractDMLBaseExecutor.executeAutoCommitFalse(AbstractDMLBaseExecutor.java:98) [seata-all-1.6.1.jar:1.6.1]
    at io.seata.rm.datasource.exec.AbstractDMLBaseExecutor.lambda$executeAutoCommitTrue$2(AbstractDMLBaseExecutor.java:137) [seata-all-1.6.1.jar:1.6.1]
    at io.seata.rm.datasource.ConnectionProxy$LockRetryPolicy.doRetryOnLockConflict(ConnectionProxy.java:356) ~[seata-all-1.6.1.jar:1.6.1]
    at io.seata.rm.datasource.exec.AbstractDMLBaseExecutor$LockRetryPolicy.execute(AbstractDMLBaseExecutor.java:180) ~[seata-all-1.6.1.jar:1.6.1]
    at io.seata.rm.datasource.exec.AbstractDMLBaseExecutor.executeAutoCommitTrue(AbstractDMLBaseExecutor.java:136) [seata-all-1.6.1.jar:1.6.1]
    at io.seata.rm.datasource.exec.AbstractDMLBaseExecutor.doExecute(AbstractDMLBaseExecutor.java:82) [seata-all-1.6.1.jar:1.6.1]
    at io.seata.rm.datasource.exec.BaseTransactionalExecutor.execute(BaseTransactionalExecutor.java:125) [seata-all-1.6.1.jar:1.6.1]
    at io.seata.rm.datasource.exec.ExecuteTemplate.execute(ExecuteTemplate.java:137) [seata-all-1.6.1.jar:1.6.1]
    at io.seata.rm.datasource.exec.ExecuteTemplate.execute(ExecuteTemplate.java:56) [seata-all-1.6.1.jar:1.6.1]
    at io.seata.rm.datasource.PreparedStatementProxy.executeUpdate(PreparedStatementProxy.java:65) [seata-all-1.6.1.jar:1.6.1]
    at org.springframework.jdbc.core.JdbcTemplate.lambda$update$0(JdbcTemplate.java:867) [spring-jdbc-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:617) [spring-jdbc-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:862) [spring-jdbc-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:917) [spring-jdbc-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:927) [spring-jdbc-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at com.example.Demo.doWithGlobalTraction(Demo.java:17) ~[classes/:na]

seata1.6.1,AT模式,使用pgsql自增主键、且insert语句不包含主键列时,不发生异常,insert执行成功。

如下是测试代码

import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

@Component
public class Demo {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @GlobalTransactional
    public void doWithGlobalTraction(String name) {
        // 向pgsql的自增主键表hello插入一条数据
        // insert语句不包含主键列: id
        jdbcTemplate.update("insert into hello(name) values(?)", name);
    }
}

demo传送门:https://github.com/isharpever/seata-demo.git

  1. 上面例子里面,pgsql自增主键表hello的建表语句如下:
    CREATE TABLE "hello" (
    "id" serial4 NOT NULL,
    "name" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
    CONSTRAINT "hello_pkey" PRIMARY KEY ("id")
    )
  2. 从seata1.6.0开始有问题(使用seata1.5.2测试,没有上述问题)
  • JDK version : 1.8.0_301
  • Seata version: 1.6.0、1.6.1
  • OS : win10
  • Others: pgsql server版本=14.1、客户端驱动版本=42.5.1

回答

0

at对pg的支持上本身不支持自增主键的获取,只支持通过insert中的主键列来获取,应该是因为1.6的多主键支持后开始做了检测

0

at对pg的支持上本身不支持自增主键的获取

这句话不能理解,因为我使用1.5.2测试,是支持pgsql自增主键的(能insert成功、也能回滚成功)。

基于1.5.2支持pgsql自增主键这个事实前提,能否怀疑这两种可能性:

  1. 1.6.0在支持pgsql多主键时,基于设计主动放弃了对自增主键的支持
  2. 1.6.0在支持pgsql多主键时,因疏忽把对自增主键的支持弄丢了

第1点可能性不大,因为这会给1.5.x的用户后面升级高版本带来困难;而且[单主键]比[多主键]普遍的多,为了较少的场景而放弃更加普适的功能,一般也不太可能做这样的选择。

3

抱歉是我看错了,确实是多主键支持后出现的bug,我们看下如何解决

0

由于逻辑修改,走到了 image 由于插入列中没有包括pk,导致认为pk不存在抛出了异常

4

麻烦确认下io.seata.rm.datasource.sql.struct.TableMeta#containsPK image 在多主键情况下,List.containsAll与注释//at least contain one pk的意思不符?

另外,io.seata.rm.datasource.sql.struct.TableMetaTest#testContainsPK无对多主键情况的测试。

9

注释没改不用管,后面修这个bug的时候顺便改下,pg获取pk应该改成类似mysql目前多主键的支持 image 你上面sql的情况应该直接通过getGeneratedKeys获取主键值

2

同意参考mysql类似实现的看法。

有一点我觉得比较重要,所以啰嗦一下:mysql只支持一列设为自增,而pgsql可支持多列自增。 基于此,对多主键场景时insert语句的影响是:

  1. mysql:insert将至少包含一个主键列
  2. pgsql:insert可不包含任何主键列

例: 假设表multi有三列:id1、id2、name,其中(id1,id2)构成联合主键,则: 对于insert into multi(name) values('xx'),在pgsql是允许的(把id1、id2都设置为自增时)。

那么,如果与mysql的这段代码完全一样,多主键场景运行的else分支,会抛出异常ShouldNeverHappenException: pkIndex is not found,原因与我上面遇到的单主键报错相同。 MySQLInsertExecutor

7

插入列不包含pk时走getGeneratedKeys应该是可以拿到2个自增主键的值的,你有兴趣来修复这个bug吗?

4

下面是基于个人理解,对io.seata.rm.datasource.exec.postgresql.PostgresqlInsertExecutor#getPkValues的一个修改尝试,供参考探讨。

    /**
     * 1. insert values不存在主键列的值时,所有主键列的值都来自自增。条件是:
     * <ul>
     *     <li>insert columns非空、且未包含任一主键列</li>
     * </ul>
     *
     * 2. insert values存在主键列的值, 可能的情况有:
     * <ul>
     *     <li>insert columns为空: 可从insert values获取到全部主键的值</li>
     *     <li>insert columns包含了至少一个主键列: 先从insert values获取存在的主键值, 其他主键列的值来自自增</li>
     * </ul>
     *
     * @return {@link Map}<{@link String}, {@link List}<{@link Object}>>
     * @throws SQLException sqlexception异常
     */
    @Override
    public Map<String, List<Object>> getPkValues() throws SQLException {
        List<String> pkColumnNameList = getTableMeta().getPrimaryKeyOnlyName();
        Map<String, List<Object>> pkValuesMap = new HashMap<>(pkColumnNameList.size());

        // 先从insert values获取存在的主键值(若有)
        if (!containsColumns() || containsAnyPk()) {
            pkValuesMap = getPkValuesByColumn();
        }
        // 其他主键列的值来自自增
        for (String columnName : pkColumnNameList) {
            if (!pkValuesMap.containsKey(columnName)) {
                pkValuesMap.put(columnName, getGeneratedKeys(columnName));
            }
        }
        return pkValuesMap;
    }

    private boolean containsAnyPk() {
        SQLInsertRecognizer recognizer = (SQLInsertRecognizer)sqlRecognizer;
        List<String> insertColumns = recognizer.getInsertColumns();
        if (CollectionUtils.isEmpty(insertColumns)) {
            return false;
        }
        List<String> pkColumnNameList = getTableMeta().getPrimaryKeyOnlyName();
        return pkColumnNameList.stream().anyMatch(pkColumn -> insertColumns.contains(pkColumn)
            || CollectionUtils.toUpperList(insertColumns).contains(pkColumn.toUpperCase()));
    }

另外,目前oracle的实现与pgsql很相似,可能也需要验证验证。

0

或许oracle那块也存在这个问题,你可以在本地验证通过后,提交一个pr给社区

6

这个问题有修复吗

8

这个问题有修复吗

等1.7发版