确认

  • [X] 我使用的版本是最新版, 并且使用插件确认过项目里无依赖版本冲突
  • [X] 我已经在 issue 中搜索过, 确认问题没有被提出过
  • [X] 我已经修改标题, 将标题中的 描述 替换为遇到的问题

当前程序版本

3.5.7

问题描述

环境:

MP版本 3.5.7,临时升级到3.5.9也一样 JDK17 DM数据库 驱动DmJdbcDriver18 springboot 3.1.4

场景一

数据库2000w,单字段in查询,数据库有idcard索引 异常: 耗时:1分钟30秒

LambdaQueryWrapper<CenterDiseaseDetail> wrapper = new LambdaQueryWrapper<>();
        wrapper.in(CenterDiseaseDetail::getIdcard, idcardList);
        wrapper.select(CenterDiseaseDetail::getIdcard, CenterDiseaseDetail::getCode);
        long l = System.currentTimeMillis();
        List<CenterDiseaseDetail> diseaseDetailList = serviceMap.getService(CenterDiseaseDetailService.class).findList(wrapper);
        LogPrintUtils.log("aa", l);

sql打印:

SELECT idcard,code FROM center_disease_detail WHERE (idcard IN ('DEMO00195403160202','DEMO00197301071002','DEMO00194805140201','DEMO00195001110803','DEMO00195008210801','DEMO00196809171001','DEMO00196409270401','DEMO00194107220501'))

正常: 使用xml方式正常,耗时:70 ms

List<CenterDiseaseDetail> diseaseDetailList = serviceMap.getService(CenterDiseaseDetailService.class).find(idcardList);

   <select id="find" resultType="com.kairui.aimb.model.center.disease.CenterDiseaseDetail">
        select idcard,code
        from center_disease_detail
        where  idcard IN
            <foreach collection="idcardList" item="idcard" separator="," open="(" close=")">
                #{idcard}
            </foreach>
    </select>

sql打印:

select idcard,code from center_disease_detail where idcard IN ( 'DEMO00195403160202' , 'DEMO00197301071002' , 'DEMO00194805140201' , 'DEMO00195001110803' , 'DEMO00195008210801' , 'DEMO00196809171001' , 'DEMO00196409270401' , 'DEMO00194107220501' )

场景二

数据库2000w,数据库有pid索引,查询条件拼接顺序影响索引生效 异常: 耗时:15秒

long l = System.currentTimeMillis();
        QueryWrapper<CenterFollowHbpDrug> wrapper = new QueryWrapper<>();
        wrapper.lambda()
        .eq(CenterFollowHbpDrug::getStatus, 0)
                .eq(CenterFollowHbpDrug::getPid, "2016070100899052_542101");
        List<CenterFollowHbpDrug> tempList = serviceMap.getService(CenterFollowHbpDrugService.class).findList(wrapper);
        LogPrintUtils.log("" + tempList.size(), l);

sql打印:

SELECT id,hr_id,name,spec,num,unit,frequency,way,dosage_per_use,dosage_unit_per_use,remarks,pid,status,utime,utimestamp,day FROM center_follow_hbp_drug WHERE (status = 0 AND pid = '2016070100899052_542101')

正常:耗时:99 ms

long l = System.currentTimeMillis();
        QueryWrapper<CenterFollowHbpDrug> wrapper = new QueryWrapper<>();
        wrapper.lambda()
                .eq(CenterFollowHbpDrug::getPid, "2016070100899052_542101")
        .eq(CenterFollowHbpDrug::getStatus, 0);
        List<CenterFollowHbpDrug> tempList = serviceMap.getService(CenterFollowHbpDrugService.class).findList(wrapper);
        LogPrintUtils.log("" + tempList.size(), l);

sql打印:

SELECT id,hr_id,name,spec,num,unit,frequency,way,dosage_per_use,dosage_unit_per_use,remarks,pid,status,utime,utimestamp,day FROM center_follow_hbp_drug WHERE (pid = '2016070100899052_542101' AND status = 0)

诉求:

MySql数据库测试过,没有出现这两个异常现象,以上任意SQL直接DM数据库查询都是正常速度, 使用MP框架代码执行会变得异常缓慢,想问的是需要做哪些全局配置才能修复不走索引的问题?

详细堆栈日志

场景一:

异常日志

2024-11-28T15:39:58.201+08:00 DEBUG 1111 --- [nio-8180-exec-1] c.k.a.m.c.d.C.selectList                 : ==>  Preparing: SELECT idcard,code FROM center_disease_detail WHERE (idcard IN (?,?,?,?,?,?,?,?))
2024-11-28T15:39:58.220+08:00 DEBUG 1111 --- [nio-8180-exec-1] c.k.a.m.c.d.C.selectList                 : ==> Parameters: DEMO00195403160202(String), DEMO00197301071002(String), DEMO00194805140201(String), DEMO00195001110803(String), DEMO00195008210801(String), DEMO00196809171001(String), DEMO00196409270401(String), DEMO00194107220501(String)
2024-11-28T15:41:28.194+08:00 DEBUG 1111 --- [nio-8180-exec-1] c.k.a.m.c.d.C.selectList                 : <==      Total: 72
2024-11-28 15:41:28.211:aa 耗时:1分钟30秒

正常日志

2024-11-28T15:50:01.415+08:00 DEBUG 1868 --- [nio-8180-exec-1] c.k.a.m.c.d.C.find                       : ==>  Preparing: select idcard,code from center_disease_detail where idcard IN ( ? , ? , ? , ? , ? , ? , ? , ? )
2024-11-28T15:50:01.439+08:00 DEBUG 1868 --- [nio-8180-exec-1] c.k.a.m.c.d.C.find                       : ==> Parameters: DEMO00195403160202(String), DEMO00197301071002(String), DEMO00194805140201(String), DEMO00195001110803(String), DEMO00195008210801(String), DEMO00196809171001(String), DEMO00196409270401(String), DEMO00194107220501(String)
2024-11-28T15:50:01.467+08:00 DEBUG 1868 --- [nio-8180-exec-1] c.k.a.m.c.d.C.find                       : <==      Total: 72
2024-11-28 15:50:01.484:aa 耗时:70 ms

场景二:

异常日志

==>  Preparing: SELECT id,hr_id,name,spec,num,unit,frequency,way,dosage_per_use,dosage_unit_per_use,remarks,pid,status,utime,utimestamp,day FROM center_follow_hbp_drug WHERE (status = ? AND pid = ?)
==> Parameters: 0(Integer), 2016070100899052_542101(String)
<==    Columns: ID, HR_ID, NAME, SPEC, NUM, UNIT, FREQUENCY, WAY, DOSAGE_PER_USE, DOSAGE_UNIT_PER_USE, REMARKS, PID, STATUS, UTIME, UTIMESTAMP, DAY
<==        Row: a90e5c5e-399c-48f9-8252-8fd8d68d07f3, null, 门冬氨酸氨氯地平片, null, null, 8, null, po, qd, null, null, 2016070100899052_542101, 0, 2024-11-27 11:09:26.865000, 1732676966865, null
<==        Row: af76bf3b-b50a-4934-a97e-7e4f3325be1b, null, 奥美沙坦酯片, null, null, 8, null, po, qd, null, null, 2016070100899052_542101, 0, 2024-11-27 11:12:34.894000, 1732677154894, null
<==        Row: 701fba81-2d0e-49b4-890f-d24ee0cf77fa, null, 门冬氨酸氨氯地平片, null, null, 8, qd, po, 5, 8, null, 2016070100899052_542101, 0, 2024-11-27 18:25:45.687000, 1732703145687, null
<==        Row: a72fcf87-e96d-4d94-8d06-6faade1bd8ca, null, 奥美沙坦酯片, null, null, 8, qd, po, 20, 8, null, 2016070100899052_542101, 0, 2024-11-27 18:30:34.070000, 1732703434070, null
<==      Total: 4
2024-11-28 15:58:41.173:4 耗时:15秒

正常日志

==>  Preparing: SELECT id,hr_id,name,spec,num,unit,frequency,way,dosage_per_use,dosage_unit_per_use,remarks,pid,status,utime,utimestamp,day FROM center_follow_hbp_drug WHERE (pid = ? AND status = ?)
==> Parameters: 2016070100899052_542101(String), 0(Integer)
<==    Columns: ID, HR_ID, NAME, SPEC, NUM, UNIT, FREQUENCY, WAY, DOSAGE_PER_USE, DOSAGE_UNIT_PER_USE, REMARKS, PID, STATUS, UTIME, UTIMESTAMP, DAY
<==        Row: a90e5c5e-399c-48f9-8252-8fd8d68d07f3, null, 门冬氨酸氨氯地平片, null, null, 8, null, po, qd, null, null, 2016070100899052_542101, 0, 2024-11-27 11:09:26.865000, 1732676966865, null
<==        Row: af76bf3b-b50a-4934-a97e-7e4f3325be1b, null, 奥美沙坦酯片, null, null, 8, null, po, qd, null, null, 2016070100899052_542101, 0, 2024-11-27 11:12:34.894000, 1732677154894, null
<==        Row: 701fba81-2d0e-49b4-890f-d24ee0cf77fa, null, 门冬氨酸氨氯地平片, null, null, 8, qd, po, 5, 8, null, 2016070100899052_542101, 0, 2024-11-27 18:25:45.687000, 1732703145687, null
<==        Row: a72fcf87-e96d-4d94-8d06-6faade1bd8ca, null, 奥美沙坦酯片, null, null, 8, qd, po, 20, 8, null, 2016070100899052_542101, 0, 2024-11-27 18:30:34.070000, 1732703434070, null
<==      Total: 4
2024-11-28 16:10:17.712:4 耗时:99 ms

Comment From: miemieYaho

那你不应该去问数据库吗?

Comment From: sgps000

问题是使用xml方式和数据库直接sql执行都没问题,使用Wrapper才出现的现象

Comment From: miemieYaho

你说xml没问题,那你xml出来的sql和wrapper出来的sql都长得不一样有什么可比性

Comment From: sgps000

一、所有的sql打印都是MP log插件里拷贝的,堆栈日志里的sql也贴出来了; 二、场景一中xml方式与Wrapper对比,唯一的区别就是Wrapper把where后面的加了括号; 三、场景二中sql唯一的区别就是 查询条件位置有前后区分; 四、又出现特殊情况,在场景一和场景二的异常情况代码中,加上wrapper.last("--")后,全都正常走索引了。

LambdaQueryWrapper<CenterDiseaseDetail> wrapper = new LambdaQueryWrapper<>();
        wrapper.in(CenterDiseaseDetail::getIdcard, idcardList);
        wrapper.select(CenterDiseaseDetail::getIdcard, CenterDiseaseDetail::getCode);
        wrapper.last("--");
        long l = System.currentTimeMillis();
        List<CenterDiseaseDetail> diseaseDetailList = serviceMap.getService(CenterDiseaseDetailService.class).findList(wrapper);
        LogPrintUtils.log("aa1-" + diseaseDetailList.size(), l);
        LambdaQueryWrapper<CenterDiseaseDetail> wrapper2 = new LambdaQueryWrapper<>();
        wrapper2.in(CenterDiseaseDetail::getIdcard, idcardList);
        wrapper2.select(CenterDiseaseDetail::getIdcard, CenterDiseaseDetail::getCode);
        l = System.currentTimeMillis();
        diseaseDetailList = serviceMap.getService(CenterDiseaseDetailService.class).findList(wrapper2);
        LogPrintUtils.log("aa2-" + diseaseDetailList.size(), l);

堆栈日志:

2024-11-28T19:11:28.490+08:00 DEBUG 19267 --- [nio-8180-exec-1] c.k.a.m.c.d.C.selectList                 : ==>  Preparing: SELECT idcard,code FROM center_disease_detail WHERE (idcard IN (?,?,?,?,?,?,?,?)) --
2024-11-28T19:11:28.506+08:00 DEBUG 19267 --- [nio-8180-exec-1] c.k.a.m.c.d.C.selectList                 : ==> Parameters: DEMO00195403160202(String), DEMO00195008210801(String), DEMO00199100001501(String), DEMO00195001110803(String), DEMO00197301071002(String), DEMO00196809171001(String), DEMO00194305180301(String), DEMO00196409270401(String)
2024-11-28T19:11:28.524+08:00 DEBUG 19267 --- [nio-8180-exec-1] c.k.a.m.c.d.C.selectList                 : <==      Total: 78
2024-11-28 19:11:28.538:aa1-78 耗时:50 ms
2024-11-28T19:11:28.539+08:00 DEBUG 19267 --- [nio-8180-exec-1] c.k.a.m.c.d.C.selectList                 : ==>  Preparing: SELECT idcard,code FROM center_disease_detail WHERE (idcard IN (?,?,?,?,?,?,?,?))
2024-11-28T19:11:28.554+08:00 DEBUG 19267 --- [nio-8180-exec-1] c.k.a.m.c.d.C.selectList                 : ==> Parameters: DEMO00195403160202(String), DEMO00195008210801(String), DEMO00199100001501(String), DEMO00195001110803(String), DEMO00197301071002(String), DEMO00196809171001(String), DEMO00194305180301(String), DEMO00196409270401(String)
2024-11-28T19:12:43.914+08:00 DEBUG 19267 --- [nio-8180-exec-1] c.k.a.m.c.d.C.selectList                 : <==      Total: 78
2024-11-28 19:12:43.930:aa2-78 耗时:1分钟15秒

Comment From: sgps000

你说xml没问题,那你xml出来的sql和wrapper出来的sql都长得不一样有什么可比性

就是因为MP的Wrapper输出的sql和直接写xml的不一致导致查询速度不一致才来反馈问题的,不然我就认为是数据库的问题了。我想知道的是MP是否有什么特殊机制会造成这种现象,有没有全局配置可以修复?

Comment From: miemieYaho

你只用mybatis且在xml写SQL的格式和wrapper生成出来的一样的情况下速度有区别你才能说是mp有问题懂吗?

Comment From: sgps000

你只用mybatis且在xml写SQL的格式和wrapper生成出来的一样的情况下速度有区别你才能说是mp有问题懂吗?

wrapper的SQL where后面自动加括号是框架自带的吗?还是可配置的?

Comment From: miemieYaho

没配置就这样

Comment From: sgps000

代码:

        String pid = "2016070100899052_542101";
        long l = System.currentTimeMillis();
        List<CenterFollowHbpDrug> tempList = serviceMap.getService(CenterFollowHbpDrugService.class).findByXml(pid);
        LogPrintUtils.log("" + tempList.size(), l);

        l = System.currentTimeMillis();
        tempList = serviceMap.getService(CenterFollowHbpDrugService.class).findByWrapper(pid);
        LogPrintUtils.log("" + tempList.size(), l);

service:

  @Resource
    private CenterFollowHbpDrugMapper mapper;

    public List<CenterFollowHbpDrug> findByXml(String pid) {
        return mapper.find(0, pid);
    }

    public List<CenterFollowHbpDrug> findByWrapper(String pid) {
        LambdaQueryWrapper<CenterFollowHbpDrug> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CenterFollowHbpDrug::getStatus, 0)
                .eq(CenterFollowHbpDrug::getPid, pid);
        return mapper.selectList(wrapper);
    }

mapper:

@Mapper
public interface CenterFollowHbpDrugMapper extends BaseMapper<CenterFollowHbpDrug> {

    List<CenterFollowHbpDrug> find(@Param("status") Integer status,@Param("pid") String pid);

}

XML:

<select id="find" resultType="c.k.a.m.c.f.CenterFollowHbpDrug">
        SELECT id,
               hr_id,
               name,
               spec,
               num,
               unit,
               frequency,
               way,
               dosage_per_use,
               dosage_unit_per_use,
               remarks,
               pid,
               status,
               utime,
               utimestamp, day
        FROM center_follow_hbp_drug
        WHERE (status = #{status} AND pid = #{pid})
    </select>

堆栈日志:

2024-11-29T10:02:15.734+08:00 DEBUG 31544 --- [    Test worker] c.k.a.m.c.f.C.find                       : ==>  Preparing: SELECT id, hr_id, name, spec, num, unit, frequency, way, dosage_per_use, dosage_unit_per_use, remarks, pid, status, utime, utimestamp, day FROM center_follow_hbp_drug WHERE (status = ? AND pid = ?)
2024-11-29T10:02:15.752+08:00 DEBUG 31544 --- [    Test worker] c.k.a.m.c.f.C.find                       : ==> Parameters: 0(Integer), 2016070100899052_542101(String)
2024-11-29T10:02:15.774+08:00 DEBUG 31544 --- [    Test worker] c.k.a.m.c.f.C.find                       : <==      Total: 4
2024-11-29 10:02:15.790:4 耗时:74 ms
2024-11-29T10:02:15.794+08:00 DEBUG 31544 --- [    Test worker] c.k.a.m.c.f.C.selectList                 : ==>  Preparing: SELECT id,hr_id,name,spec,num,unit,frequency,way,dosage_per_use,dosage_unit_per_use,remarks,pid,status,utime,utimestamp,day FROM center_follow_hbp_drug WHERE (status = ? AND pid = ?)
2024-11-29T10:02:15.811+08:00 DEBUG 31544 --- [    Test worker] c.k.a.m.c.f.C.selectList                 : ==> Parameters: 0(Integer), 2016070100899052_542101(String)
2024-11-29T10:02:31.456+08:00 DEBUG 31544 --- [    Test worker] c.k.a.m.c.f.C.selectList                 : <==      Total: 4
2024-11-29 10:02:31.474:4 耗时:15秒

SQL log插件打印:

-- 311 -- 2024-11-29T10:02:15.734+08:00 DEBUG 31544 --- [    Test worker] c.k.a.m.c.f.C.find                       : ==>  
SELECT id, hr_id, name, spec, num, unit, frequency, way, dosage_per_use, dosage_unit_per_use, remarks, pid, status, utime, utimestamp, day FROM center_follow_hbp_drug WHERE (status = 0 AND pid = '2016070100899052_542101')
-- 312 -- 2024-11-29T10:02:15.794+08:00 DEBUG 31544 --- [    Test worker] c.k.a.m.c.f.C.selectList                 : ==>  
SELECT id,hr_id,name,spec,num,unit,frequency,way,dosage_per_use,dosage_unit_per_use,remarks,pid,status,utime,utimestamp,day FROM center_follow_hbp_drug WHERE (status = 0 AND pid = '2016070100899052_542101')

我把xml也加上了括号,此时sql应该是一样了吧,速度有差别,请问还有哪些变量没有做到一致?

Comment From: miemieYaho

提供一下你的db环境以及git项目吧,能稳定复现的

Comment From: miemieYaho

而且你试过

public List<CenterFollowHbpDrug> findByWrapper(String pid) {
        QueryWrapper<CenterFollowHbpDrug> wrapper = new QueryWrapper<>();
        wrapper.eq("status", 0).eq("pid", pid);
        return mapper.selectList(wrapper);
    }

了吗?

Comment From: sgps000

提供一下你的db环境以及git项目吧,能稳定复现的

我是拿生产环境的DB调的,生产环境的DM数据库是信创工程师部署的,我的测试服的DM数据库是对方提供的试用版,我自己测试服没出现这个问题,就生产环境出现了。DM数据库是有语法兼容模式选择的,但对方工程师未提供,全靠自己摸索。所以我想是来问问MP底层是否有特殊的机制会迫使数据库不走索引。

而且你试过

java public List<CenterFollowHbpDrug> findByWrapper(String pid) { QueryWrapper<CenterFollowHbpDrug> wrapper = new QueryWrapper<>(); wrapper.eq("status", 0).eq("pid", pid); return mapper.selectList(wrapper); }

了吗?

试过了,跟Lambda的效果一致。

之后又测试了以下两种情况 test1:

public List<CenterFollowHbpDrug> findByWrapper(String pid) {
        QueryWrapper<CenterFollowHbpDrug> wrapper = new QueryWrapper<>();
        wrapper.eq("status", 0)
                .eq("pid", pid)
                .select("id","hr_id");
        return mapper.selectList(wrapper);
    }

test2:

QueryWrapper<CenterFollowHbpDrug> wrapper = new QueryWrapper<>();
        wrapper.eq("status", 0)
                .eq("pid", pid)
                .select("id",
                        "hr_id",
                        "name",
                        "spec",
                        "num",
                        "unit",
                        "frequency",
                        "way",
                        "dosage_per_use",
                        "dosage_unit_per_use",
                        "remarks",
                        "pid",
                        "status",
                        "utime",
                        "utimestamp",
                        "day"
                );
        return mapper.selectList(wrapper);

发现速度又正常了,test2打印的sql日志与你发的案例一模一样的,区别在于手动select了所有字段。 但是这是适用场景二,场景一尝试了一下这种test2的方案还是没解决。

然后又按照你的思路,严格保证sql一致,因为之前的sql里还是有个别有空格,再加上有sql末尾任意拼接不影响的sql的字符串(场景一种的last(”--“))都能变快的案例,就在场景一把字段加上个空格试了一下,结果是变正常了

QueryWrapper<CenterDiseaseDetail> wrapper = new QueryWrapper<>();
        wrapper.in("idcard", idcardList);
        wrapper.select("idcard ", "code ");
        List<CenterDiseaseDetail> diseaseDetailList = serviceMap.getService(CenterDiseaseDetailService.class).findList(wrapper);

所以目前的解决方案是 直接加了个全局sql拦截器,把所有的逗号改成逗号加空格。

@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
})
public class DmInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        Object target = invocation.getTarget();
        if (target instanceof StatementHandler statementHandler) {
            // 获取sql语句
            String originalSql = statementHandler.getBoundSql().getSql();

//            System.out.println("SQL: " + originalSql);

            // sql替换
            String newSql = originalSql.replaceAll(",", ", ");

            if (!Objects.equals(originalSql, newSql)) {
                // 更新 SQL 语句
                Field field = statementHandler.getBoundSql().getClass().getDeclaredField("sql");
                field.setAccessible(true);
                field.set(statementHandler.getBoundSql(), newSql);
//                System.out.println("转换后" + statementHandler.getBoundSql().getSql());
            }
        }

        // 继续调用拦截链
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Interceptor.super.plugin(target);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}

应该不会和MP的其他逻辑冲突吧? 感觉直接改sql不是长久之计,但目前工作繁多,只能先应付使用了。如果此方案不会对MP框架造成影响,可以直接关闭这个issue

Comment From: miemieYaho

所以那还是dm有问题啊

Comment From: sgps000

所以那还是dm有问题啊

我已经尽量排除数据库的影响了,我想指出的是,MP在DM特殊的一个版本下,可能存在兼容问题。主要还是目前比较小众,但信创改版项目越来越多,我不确定会不会成为主流数据库,如果MP框架没有强制DM改变索引策略的机制话,可以不管了。当给后来者一个临时解决方案吧。