Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
1 task done
isharpever opened this issue Jan 28, 2023 · 12 comments
Assignees
Labels
task: help-wanted Extra attention is needed type: bug Category issues or prs related to bug.

Comments

@isharpever
Copy link
Contributor

isharpever commented Jan 28, 2023

  • I have searched the issues of this repository and believe that this is not a duplicate.

Ⅰ. Issue Description

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

Ⅱ. Describe what happened

If there is an exception, please attach the exception trace:

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]

Ⅲ. Describe what you expected to happen

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

Ⅳ. How to reproduce it (as minimally and precisely as possible)

如下是测试代码

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

Ⅴ. Anything else we need to know?

  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")
)
  1. 从seata1.6.0开始有问题(使用seata1.5.2测试,没有上述问题)

Ⅵ. Environment:

  • JDK version : 1.8.0_301
  • Seata version: 1.6.0、1.6.1
  • OS : win10
  • Others: pgsql server版本=14.1、客户端驱动版本=42.5.1
@isharpever isharpever changed the title 1.6.1,pgsql自增主键、且insert语句不包含主键列时,报[ShouldNeverHappenException: pkIndex is not found] 1.6.1,pgsql自增主键、当insert语句不包含主键列时,报[ShouldNeverHappenException: pkIndex is not found] Jan 28, 2023
@funky-eyes
Copy link
Contributor

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

@isharpever
Copy link
Contributor Author

isharpever commented Jan 29, 2023

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的用户后面升级高版本带来困难;而且[单主键]比[多主键]普遍的多,为了较少的场景而放弃更加普适的功能,一般也不太可能做这样的选择。

@funky-eyes
Copy link
Contributor

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

@funky-eyes funky-eyes added task: help-wanted Extra attention is needed type: bug Category issues or prs related to bug. labels Jan 30, 2023
@funky-eyes
Copy link
Contributor

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

@isharpever
Copy link
Contributor Author

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

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

@funky-eyes
Copy link
Contributor

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

@isharpever
Copy link
Contributor Author

isharpever commented Jan 31, 2023

同意参考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

@funky-eyes
Copy link
Contributor

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

@isharpever
Copy link
Contributor Author

isharpever commented Jan 31, 2023

下面是基于个人理解,对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很相似,可能也需要验证验证。

@funky-eyes
Copy link
Contributor

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

@lk8808
Copy link

lk8808 commented Apr 27, 2023

这个问题有修复吗

@funky-eyes
Copy link
Contributor

这个问题有修复吗

等1.7发版

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
task: help-wanted Extra attention is needed type: bug Category issues or prs related to bug.
Projects
None yet
Development

No branches or pull requests

3 participants