全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-20 6 分钟 ✍️ juanwangdev

SQL 语句优化

SQL 语句质量直接影响数据库性能。本文梳理 MyBatis 开发中的 SQL 优化核心技巧。

避免全表扫描

全表扫描是性能杀手,尤其在大数据量表中。

问题场景

XML
<!-- 反例:无 WHERE 条件,全表扫描 -->
<select id="selectAllUsers" resultType="User">
    SELECT * FROM user
</select>

<!-- 反例:WHERE 条件未使用索引 -->
<select id="findByEmail" resultType="User">
    SELECT * FROM user WHERE email LIKE '%@example.com'
</select>

注意:LIKE '%...' 左模糊查询无法使用索引,必然导致全表扫描。

优化方案

XML
<!-- 正例:WHERE 条件命中索引字段 -->
<select id="findByUsername" resultType="User">
    SELECT * FROM user WHERE username = #{username}
</select>

<!-- 正例:右模糊查询可走索引 -->
<select id="findByEmailSuffix" resultType="User">
    SELECT * FROM user WHERE email LIKE 'admin@%'
</select>

索引覆盖

索引覆盖是指查询所需的所有列都在索引中,无需回表查询。

覆盖索引示例

假设表上有联合索引 (username, email)

XML
<!-- 覆盖索引查询,只需扫描索引树 -->
<select id="findUsernameAndEmail" resultType="UserVO">
    SELECT username, email FROM user WHERE username = #{username}
</select>
XML
<!-- 反例:SELECT * 导致回表 -->
<select id="findUserById" resultType="User">
    SELECT * FROM user WHERE username = #{username}
</select>

注意:SELECT * 查询超出索引范围的列时,需要回表查询数据行,增加 IO 开销。

只查需要的列

XML
<!-- 正例:明确指定所需列 -->
<select id="findUserNameById" resultType="String">
    SELECT username FROM user WHERE id = #{id}
</select>

执行计划分析

执行计划是分析 SQL 性能的核心工具。

使用 EXPLAIN 分析

在数据库中直接执行:

SQL
EXPLAIN SELECT * FROM user WHERE username = 'admin';

关键字段解读:

字段说明关注点
type连接类型至少达到 range 级别
key实际使用的索引不应为 NULL
rows预估扫描行数越小越好
Extra额外信息避免出现 Using filesort

type 性能从高到低:

type 值性能说明
system最优表中只有一行
const优秀通过唯一索引查询
eq_ref优秀联表查询,使用主键或唯一索引
ref良好使用普通索引
range可接受索引范围扫描
index较差全索引扫描
ALL最差全表扫描

MyBatis 中验证执行计划

开发阶段可用以下方式验证 SQL 执行计划:

Java
@Test
public void testExplain() {
    try (SqlSession session = sqlSessionFactory.openSession()) {
        Connection conn = session.getConnection();
        try (PreparedStatement ps = conn.prepareStatement(
                "EXPLAIN SELECT * FROM user WHERE username = ?")) {
            ps.setString(1, "admin");
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) {
                    System.out.println("type: " + rs.getString("type"));
                    System.out.println("key: " + rs.getString("key"));
                    System.out.println("rows: " + rs.getInt("rows"));
                }
            }
        }
    }
}

常见 SQL 反模式

1. SELECT *

XML
<!-- 反例 -->
<select id="findAll" resultType="User">
    SELECT * FROM user
</select>

<!-- 正例 -->
<select id="findActiveUsers" resultType="User">
    SELECT id, username, email FROM user WHERE status = 1
</select>

2. 在 WHERE 中使用函数

XML
<!-- 反例:对索引列使用函数,索引失效 -->
<select id="findByYear" resultType="User">
    SELECT * FROM user WHERE YEAR(create_time) = #{year}
</select>

<!-- 正例:使用范围查询 -->
<select id="findByYear" resultType="User">
    SELECT * FROM user
    WHERE create_time >= #{start} AND create_time &lt; #{end}
</select>

3. 隐式类型转换

XML
<!-- 反例:phone 是 VARCHAR 类型,传入数字导致隐式转换和索引失效 -->
<select id="findByPhone" resultType="User">
    SELECT * FROM user WHERE phone = #{phone}
</select>

注意:Java 代码中确保参数类型与数据库字段类型一致,避免隐式类型转换导致索引失效。

要点总结

  • 避免全表扫描,WHERE 条件必须命中索引
  • 左模糊查询 LIKE '%...' 无法使用索引
  • 使用覆盖索引减少回表查询,避免 SELECT *
  • 用 EXPLAIN 分析执行计划,type 至少达到 range 级别
  • 避免在 WHERE 中对索引列使用函数
  • 确保参数类型与数据库字段类型一致,避免隐式转换

📝 发现内容有误?点击此处直接编辑

← 上一篇 ExecutorType 批量执行
下一篇 → 批量操作事务控制
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库