MyBatis的性能优化——SQL语句优化

 MyBatis作为一款优秀的持久层框架,凭借其灵活性和易用性在Java开发领域广受欢迎。本文将深入探讨MyBatis的性能优化策略,帮助开发者构建高效稳定的数据访问层。

 一、MyBatis的优化方向

MyBatis的优化方向有以下几个方面:SQL语句优化、缓存机制优化 、批量操作优化、结果集处理优化、高级特性优化、性能监控与调优,下面就需要详细讲一下这些方向。

二、SQL语句优化

1、避免使用SELECT (* )

1.1 问题分析

  • 数据传输冗余:返回未使用的字段浪费网络贷款和内存资源。
  • 扩展性问题:表结构变更可能导致映射错误。
  • 索引失效:若使用覆盖索引,select * 会强制回表查询。

1.2 优化方案

明确指定字段列表,仅获取必要数据

<select id="getUserBasic" resultType="User">
    SELECT id, username, email FROM users WHERE id = #{id}
</select>

1.3 动态字段选择

通过<if>动态控制返回字段(适用于不同场景字段需求差异大的情况):

<select id="getUser" resultType="User">
    SELECT 
    <choose>
        <when test="isBasic == true">id, username</when>
        <otherwise>*</otherwise>
    </choose>
    FROM users WHERE id = #{id}
</select>
2、索引优化策略

2.1 索引命中原则

  • 最左前缀匹配:联合索引(a,b,c)仅对aa,ba,b,c生效。

  • 避免索引失效场景

    • 对索引字段使用函数或表达式:WHERE YEAR(create_time) = 2023

    • 类型不匹配:字符串字段用数字查询

    • LIKE以通配符开头:WHERE name LIKE '%abc'

2.2 执行计划分析 

使用EXPLAIN命令查看查询执行计划:

EXPLAIN SELECT * FROM users WHERE age > 20;

关注关键指标:

  • type:访问类型(const > ref > range > index > ALL)

  • key:实际使用的索引

  • rows:扫描行数

  • Extra:Using filesort、Using temporary等警告

2.3 索引优化示例
场景:用户表分页查询优化
原始SQL

SELECT * FROM users ORDER BY create_time DESC LIMIT 100000, 10;

问题:深度分页导致全表扫描
优化方案

  • 延迟关联:先查主键,再回表

SELECT * FROM users u1
INNER JOIN (SELECT id FROM users ORDER BY create_time DESC LIMIT 100000, 10) u2
ON u1.id = u2.id;
  • 游标分页:基于上一页最大ID查询

SELECT * FROM users 
WHERE create_time < #{lastCreateTime} 
ORDER BY create_time DESC LIMIT 10;
3. JOIN优化技巧

3.1 关联字段索引
确保JOIN条件的字段有索引:

-- 为department表的id建立索引
ALTER TABLE department ADD INDEX idx_id (id);

SELECT u.name, d.dept_name 
FROM users u
LEFT JOIN department d ON u.dept_id = d.id;

3.2 小表驱动大表

  • 原则:用数据量小的表作为驱动表

  • 示例:当部门表数据远小于用户表时:

SELECT /*+ STRAIGHT_JOIN */ d.dept_name, u.name
FROM department d
LEFT JOIN users u ON d.id = u.dept_id;

通过STRAIGHT_JOIN强制指定驱动表顺序。

3.3 子查询转JOIN
低效写法

SELECT * FROM users 
WHERE dept_id IN (SELECT id FROM department WHERE status = 1);

优化方案

SELECT u.* FROM users u
INNER JOIN department d ON u.dept_id = d.id 
WHERE d.status = 1;
4. 动态SQL优化

4.1 <where>标签智能处理
自动去除首尾多余的AND/OR,避免语法错误:

<select id="searchUsers" resultType="User">
    SELECT * FROM users
    <where>
        <if test="name != null">AND name LIKE #{name}</if>
        <if test="age != null">AND age = #{age}</if>
    </where>
</select>

生成SQL

  • 当所有条件为空时:SELECT * FROM users

  • 当只有age存在时:SELECT * FROM users WHERE age = ?

4.2 避免动态SQL陷阱

  • 过度嵌套问题:超过3层的<if>嵌套应重构为多个独立查询。

  • 参数空值处理:使用<if test="param != null">避免无效条件。

4.3 使用<script>标签(MyBatis 3.2+)
在注解Mapper中编写动态SQL:

@Select("<script>" +
        "SELECT * FROM users " +
        "<where>" +
        "  <if test='name != null'>AND name = #{name}</if>" +
        "</where>" +
        "</script>")
List<User> findUsers(@Param("name") String name);
5. 预编译与参数绑定

5.1 #{}与${}的区别

  • #{}:参数占位符,生成预编译语句(PreparedStatement),防止SQL注入。

  • ${}:字符串替换,直接拼接到SQL中,适用于动态表名/列名。

正确用法示例

<!-- 安全的分表查询 -->
<select id="getLogs" resultType="Log">
    SELECT * FROM logs_${tableSuffix} WHERE create_time > #{time}
</select>

5.2 参数类型处理

  • 日期类型处理:指定jdbcType避免类型推断错误

<insert id="insert">
    INSERT INTO orders(create_time) 
    VALUES(#{createTime, jdbcType=TIMESTAMP})
</insert>
6. 批处理优化

6.1 批量插入(foreach方式)

<insert id="batchInsert">
    INSERT INTO users (name, age) VALUES
    <foreach collection="list" item="user" separator=",">
        (#{user.name}, #{user.age})
    </foreach>
</insert>

注意

  • 单次批量插入数据量建议控制在500-1000条以内,避免超长SQL。

  • MySQL的max_allowed_packet参数限制单个包大小(默认4MB)。

6.2 批量更新(CASE WHEN方式)

UPDATE users 
SET age = CASE 
    WHEN id = 1 THEN 25 
    WHEN id = 2 THEN 30 
END
WHERE id IN (1, 2);

MyBatis实现

<update id="batchUpdate">
    UPDATE users
    SET age = 
    <foreach collection="list" item="user" open="CASE" close="END">
        WHEN id = #{user.id} THEN #{user.age}
    </foreach>
    WHERE id IN 
    <foreach collection="list" item="user" open="(" separator="," close=")">
        #{user.id}
    </foreach>
</update>
7. SQL注入防御

7.1 预编译强制使用

  • 全局配置:在mybatis-config.xml中禁用不安全操作:

<settings>
    <setting name="useActualParamName" value="false"/>
    <!-- 禁止${}方式传递普通参数 -->
</settings>

运行 HTML

7.2 危险字符过滤
在Java层对输入参数进行过滤:

public void setUsername(String username) {
    if (username.matches(".*[\\\\/'\"].*")) {
        throw new IllegalArgumentException("Invalid username");
    }
    this.username = username;
}

7.3 白名单校验
动态表名/列名场景:

// 校验表名是否合法
private boolean isValidTableName(String tableName) {
    return Arrays.asList("user", "order").contains(tableName);
}

// Mapper接口
@Select("SELECT * FROM ${tableName}")
List<Map<String, Object>> selectFromTable(@Param("tableName") String tableName);

总结

SQL优化需要结合具体场景,通过以下步骤系统实施:

  1. 分析慢查询日志:定位高频低效SQL

  2. EXPLAIN解析执行计划:识别全表扫描、临时表等问题

  3. 索引优化:增加缺失索引,删除冗余索引

  4. 重写复杂SQL:拆分子查询,优化JOIN顺序

  5. 压力测试验证:使用JMeter等工具验证优化效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值