mybatis查询SqlServer慢问题及解决
作者:Ikarosxx
背景
项目中用到了mybatis连接SqlServer,但遇到了一些查询慢的问题
问题1:一样的sql,换一个日期执行计划不同
# 一样的sql,djsj不同,第一条比第二条快很多 SELECT COUNT ( 1 ) FROM e_ld2022 a WITH ( nolock ) INNER JOIN m_user b ON b.MID = a.dbbh WHERE a.djsj = '2022-05-18 00:00:00' AND a.TQBH LIKE '01%' AND a.XMBH = 41; SELECT COUNT ( 1 ) FROM e_ld2022 a WITH ( nolock ) INNER JOIN m_user b ON b.MID = a.dbbh WHERE a.djsj = '2022-05-19 00:00:00' AND a.TQBH LIKE '01%' AND a.XMBH = 41;
解决
通过MSSM分析执行计划,发现两条查询执行计划不一致,查询慢的预估行数为1,走了LOOP,循环次数过多导致变慢
故怀疑是没有更新统计信息,导致优化器优化失败
# 更新统计信息 UPDATE STATISTICS databse.table;
执行更新统计信息后重新查询,正常。
问题2:#{}和${}导致查询效率不同
mybatis中使用到了如下语句
select a.*,x.CBX from e_ld${year} a inner join E_XM x on x.XMBH = a.XMBH <where> <if test="dbbh != null and dbbh !=''"> AND dbbh LIKE concat(#{dbbh},'%') </if> <if test="tqbh != null and tqbh !=''"> AND TQBH LIKE concat(#{tqbh},'%') </if> </where>
经过测试,只要条件增加了DBBH和TQBH,就是LIKE语句,查询就巨慢
但一样的SQL拎出来放到MSSM上执行,就很快
解决
经过偶然测试,发现如果使用AND TQBH LIKE '${tqbh}%',就很快
说明问题出现在${}与#{}的区别上
以往的认知上,只知道#{}就相当于PreparedStatement,能够防注入
${}可能出现注入风险
但不知道为啥这个会影响到查询性能
跟了一下源码,发现跟sendStringParametersAsUnicode和数据库字段类型(VARCHAR、NVARCHAR)有关系
总结来说就是
sendStringParametersAsUnicode\字段类型 | VARCHAR | NVARCHAR |
---|---|---|
true | × | √ |
false | √ | × |
分析
SendStringParametersAsUnicode文档
SendStringParametersAsUnicode={true | false}.
Determines whether string parameters are sent to the SQL Server databasein Unicode or in the default character encoding of the database.
True means that string parameters are sent to SQL Serverin Unicode.
False means that they are sent in the default encoding, which can improve performance because the serverdoes not need to convert Unicode characters to the default encoding.
You should, however, use default encoding only if theparameter string data that you specify is consistent with the default encoding of the database.The default is true.
如果SendStringParametersAsUnicode=true,会将String参数用Unicode的编码方式发送个服务器,即 JDBCType=NVARCHAR
如果SendStringParametersAsUnicode=false,会将String参数用默认的方式发送给服务器,即 JDBCType=VARCHAR
我们系统里 SendStringParametersAsUnicode=false
,所以指定的 JdbcType=VARCHAR
但是数据库字段是NVARCHAR,和我们指定的不一致,所以会发生转换性能损耗,导致时间查询慢
基于这个思路,我测试了一下
select a.*,x.CBX from e_ld${year} a inner join E_XM x on x.XMBH = a.XMBH <where> <if test="dbbh != null and dbbh !=''"> AND dbbh LIKE concat(#{dbbh, jdbcType=NVARCHAR},'%') </if> <if test="tqbh != null and tqbh !=''"> AND TQBH LIKE concat(#{tqbh, jdbcType=NVARCHAR},'%') </if> </where>
在mybatis xml中手动指定参数的jdbcType和数据库保持一致,和结论一样,可以正常执行,不会出现查询慢的情况。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。