# Sql语句过长报错查询慢优化方案探索

Posted 爱码代码的喵

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了# Sql语句过长报错查询慢优化方案探索相关的知识,希望对你有一定的参考价值。

Sql 过长查询报错、in过多、查询慢优化方案探索

目录

文章目录

背景描述

  • 当不能直接进行sql联表查询的时候,一些中间数据需要写到表中进行查询从而解决一些无法解决的问题

  • Sql中通过sql in or in 的方式拼接sql会导致sql长度过长,Sql执行报错。

  • 使用 in 查询效率较慢

select * from test where id in('1001','1002')  or id in('1001')
  • in的内容过长后,sql超过数据库对sql长度的设定后会报错

解决方案

方案1(内存中过滤)

  • in中的内容超过一定的阈值后,超过阈值数据的字段在内存中进行过滤,先查出其它条件符合的数据(sql不做分页查询 ),加载到内存中,最后进行内存分页
/**
  * 内存分页
  *
  * @param list     数据
  * @param pageNum  当前页
  * @param pageSize 分页条数
  * @return List
  */
public <T> List<T> pageOperateInMemory(List<T> list, Integer pageNum, Integer pageSize) 
    if (CollectionUtils.isEmpty(list)) 
        return Collections.emptyList();
    
    // 根据当前页和分页条数 从总记录数中取数据
    return list.stream().skip((long) pageSize * (pageNum - 1)).limit(pageSize)
        .collect(Collectors.toList());

存在问题

  • 主表数据过大的时候,查询全部数据过慢

方案2 In 查询的内容写入固化表

  • 创建一张固化表保存In里面的数据,每次查询的时候先写表,再用这张表做关联查询
# 方式 1 子查询
select * from master_table x where col_one in(select code from table_temp y where x.col_one=y.code)
select * from test x where  exists (select id from test y where y.id=x.id  )
# 方式 2 inner join
select x.* from master_table x inner join  table_temp y on x.col_one = y.code
  • 一次查询完成后用id删除数据

固化表格式如下

  • table_temp
标识名称含义
idid一次查询生成的 uuid
codein中的值
type类型类型
request_id查询id查询id

写多张固化表思路

  • 每次查询生成一个查询id
  • 创建多张固化表,每次查询随机hash到随机表进行写表
  • 使用多线程写表(存在问题见存在的问题描述)

存在问题

  • 本地测试15万数据写入空表5秒左右,当表中数据越来越多的时候,写入速度变慢
  • 使用多项成写表的时候,数据量比较大的时候,写表速度不是特别理想,在并发情况下数据库处理大量sql不是很理想、并且在写完表后还有后续查询逻辑,sql查询不是特别理想
  • 此种方法不推荐

方案2 改进使用 left join

  • 写固化表在使用 join方式比 in 子查询的方式快
  • 使用 in子查询在遇到分页的问题的时候会比较慢,推荐优先使用 join方式

方案3(精确查询条件)

  • sql拼接更加精确的条件,减少 in的查询范围

方案4 使用临时表

方案5 固化写表使用事务特性

  • 手动控制事务,写表之后事务不提交,等所有查询语句执行完成之后,回滚事务
  • 这种方案比较好,整体速度快

查询执行过程性能分析

  • table_temp 共有 15

写表性能分析

写入固化表

单条插入
  • mybatis insert方式
private void insertOneByOne(List<TempDO> tempDOList) 
    StopWatch stopWatch = new StopWatch("task1");
    stopWatch.start();
    int i = 0;
    for (TempDO tempDO : tempDOList) 
        testMapper.insertSelective(tempDO);
        i++;
        if (i == 10000) 
            stopWatch.stop();
            logger.info(String.valueOf(stopWatch.getTotalTimeMillis()));
            i = 0;
        
    

// 10000 条 需要 4分29秒
  • jdbcTemplate方式
private void insertOneByOneJdbcExcute(List<TempDO> tempDOList) 
    String sql = "INSERT INTO table_temp_one (ID,VER,CODE,ORGCODE,NAME,SHORTNAME,VALIDTIME,INVALIDTIME,PARENTCODE,ORDINAL,CREATEUSER,CREATETIME,PARENTS) VALUES\\n" +
        "\\t (?,?,?,?,?,?,?,?,?,?,?,?,?);\\n";
    List<Object[]> objectList = new ArrayList<>();
    for (TempDO tempDO : tempDOList) 
        Object[] object = 
            tempDO.getId(), tempDO.getVer(), tempDO.getCode(), tempDO.getOrgcode(), tempDO.getName(), tempDO.getShortname(), tempDO.getValidtime(), tempDO.getInvalidtime(),
            tempDO.getParentcode(), tempDO.getOrdinal(), tempDO.getCreateuser(), tempDO.getCreatetime(), tempDO.getParents()
        ;
        objectList.add(object);
    
    StopWatch stopWatch = new StopWatch("task1");
    stopWatch.start();
    int i = 0;
    for (Object[] objects : objectList) 
        jdbcTemplate.update(sql, objects);
        i++;
        if (i == 10000) 
            stopWatch.stop();
            logger.info(String.valueOf(stopWatch.getTotalTimeMillis()));
            i = 0;
        
    

// 10000 条 需要 4分 36秒
批处理
sql 多values
@Insert("<script>INSERT INTO table_temp\\n" +
        "        (id,request_id,code,type)\\n" +
        "        values\\n" +
        "        <foreach collection=\\"list\\" item=\\"item\\" separator=\\",\\">\\n" +
        "            (#item.id, #item.requestId, #item.code, #item.type)\\n" +
        "        </foreach></script>")
int insertBatch(TempDO tempDO);

写入 10000 条执行耗时:937
写入 10000 条执行耗时:734
写入 10000 条执行耗时:925
写入 10000 条执行耗时:1241
写入 10000 条执行耗时:959
写入 10000 条执行耗时:999
写入 10000 条执行耗时:701
写入 10000 条执行耗时:352
写入 10000 条执行耗时:582
写入 10000 条执行耗时:390
写入 10000 条执行耗时:527
写入 10000 条执行耗时:388
写入 10000 条执行耗时:357
写入 10000 条执行耗时:491
写入 10000 条执行耗时:300
写入 10000 条执行耗时:23
写入 150000 条执行耗时:9915
------------------------------------
写入 150000 条执行耗时:6206 
------------------------------------
写入 150000 条执行耗时:5658
写入 150000 条执行耗时:6674
写入 150000 条执行耗时:7744
手动提交事务
SqlSessionFactory sqlSessionFactory = ApplicationContextRegister.getBean(SqlSessionFactory.class);
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
InsertTestMapper mapper = sqlSession.getMapper(InsertTestMapper.class);

List<List<TempDO>> lists = splitList(list, 10000);
StopWatch stopWatch = new StopWatch("totalTask");
stopWatch.start();
for (List<TempDO> tempDOList : lists) 
    StopWatch stopWatch1 = new StopWatch("task1");
    stopWatch1.start();
    mapper.insertBatch(tempDOList);
    stopWatch1.stop();
    System.out.println("写入 10000 条执行耗时:" + stopWatch1.getTotalTimeMillis());

sqlSession.commit();
stopWatch.stop();
System.out.println("写入 150000 条执行耗时:" + stopWatch.getTotalTimeMillis());

---------------------------------------------------------------
写入 10000 条执行耗时:159
写入 10000 条执行耗时:106
写入 10000 条执行耗时:106
写入 10000 条执行耗时:106
写入 10000 条执行耗时:106
写入 10000 条执行耗时:139
写入 10000 条执行耗时:109
写入 10000 条执行耗时:106
写入 10000 条执行耗时:133
写入 10000 条执行耗时:107
写入 10000 条执行耗时:107
写入 10000 条执行耗时:107
写入 10000 条执行耗时:106
写入 10000 条执行耗时:107
写入 10000 条执行耗时:121
写入 10000 条执行耗时:0
写入 150000 条执行耗时:4734
---------------------------------------------------------------------
写入 10000 条执行耗时:130
写入 10000 条执行耗时:105
写入 10000 条执行耗时:105
写入 10000 条执行耗时:127
写入 10000 条执行耗时:135
写入 10000 条执行耗时:106
写入 10000 条执行耗时:105
写入 10000 条执行耗时:133
写入 10000 条执行耗时:106
写入 10000 条执行耗时:107
写入 10000 条执行耗时:111
写入 10000 条执行耗时:106
写入 10000 条执行耗时:159
写入 10000 条执行耗时:142
写入 10000 条执行耗时:138
写入 10000 条执行耗时:0
写入 150000 条执行耗时:6495
PreparedStatement executeBatch 方式
Connection connection = SpringContextUtils.getBean(JdbcTemplate.class).getDataSource().getConnection();
String sql = "insert into table_temp(id, request_id,code,type) VALUES (?,?,?,?)";
PreparedStatement ps = null;
try 
    ps = connection.prepareStatement(sql);
    // 取消自动提交
    connection.setAutoCommit(false);
    StopWatch stopWatch = new StopWatch("totalTask");
    stopWatch.start();
    for (int i = 1; i <= list.size(); i++) 
        TempDO tempDO = list.get(i - 1);
        ps.setObject(1, tempDO.getId());
        ps.setObject(2, tempDO.getRequestId());
        ps.setObject(3, tempDO.getCode());
        ps.setObject(4, tempDO.getType());
        ps.addBatch();
        if (i % 10000 == 0) 
            StopWatch stopWatch1 = new StopWatch("task1");
            stopWatch1.start();
            ps.executeBatch();
            ps.clearBatch();
            stopWatch1.stop();
            System.out.println("写入 10000 条执行耗时:" + stopWatch1.getTotalTimeMillis());
        
    
    ps.executeBatch();
    ps.clearBatch();
    connection.commit();//所有语句都执行完毕后才手动提交sql语句
    stopWatch.stop();
    System.out.println("写入 150000 条执行耗时:" + stopWatch.getTotalTimeMillis());
 catch (SQLException e) 
    e.printStackTrace();
 finally 
    connection.close();


写入 10000 条执行耗时:1696
写入 10000 条执行耗时:1423
写入 10000 条执行耗时:1192
写入 10000 条执行耗时:1273
写入 10000 条执行耗时:1203
写入 10000 条执行耗时:1445
写入 10000 条执行耗时:1226
写入 10000 条执行耗时:1298
写入 10000 条执行耗时:1666
写入 10000 条执行耗时:1251
写入 10000 条执行耗时:1190
写入 10000 条执行耗时:1364
写入 10000 条执行耗时:1263
写入 10000 条执行耗时:1191
写入 10000 条执行耗时:1682
写入 150000 条执行耗时:20753
--------------------------------------------------------------------
写入 10000 条执行耗时:1579
写入 10000 条执行耗时:1859
写入 10000 条执行耗时:1483
写入 10000 条执行耗时:1470
写入 10000 条执行耗时:1488
写入 10000 条执行耗时:1059
写入 10000 条执行耗时:1112
写入 10000 条执行耗时:1164
写入 10000 条执行耗时:1426
写入 10000 条执行耗时:1202
写入 10000 条执行耗时:1315
写入 10000 条执行耗时:1654
写入 10000 条执行耗时:1301
写入 10000 条执行耗时:1134
写入 10000 条执行耗时:1529
写入 150000 条执行耗时:21168

写临时表插入

  • 先创建临时表,在执行插入,使用完之后删除
多values方式
写入 10000 条执行耗时:1267
写入 10000 条执行耗时:440
写入 10000 条执行耗时:352
写入 10000 条执行耗时:524
写入 10000 条执行耗时:380
写入 10000 条执行耗时:306
写入 10000 条执行耗时:457
写入 10000 条执行耗时:303
写入 10000 条执行耗时:301
写入 10000 条执行耗时:804
写入 10000 条执行耗时:333
写入 10000 条执行耗时:297
写入 10000 条执行耗时:323
写入 10000 条执行耗时:325
写入 10000 条执行耗时:324
写入 150000 条执行耗时:6746
---------------------------------------------------------------
写入 10000 条执行耗

以上是关于# Sql语句过长报错查询慢优化方案探索的主要内容,如果未能解决你的问题,请参考以下文章

MySQL慢查询:慢SQL定位日志分析与优化方案

数据库优化技巧 - SQL语句优化

优化mysql的内存

对SQL慢查询的优化(MySQL)

Mysql优化之慢查询优化

SQL语句优化