本文目录导读:

在Java开发中,避免全表扫描的核心思路是让数据库查询尽量使用索引,而不是在内存中处理大量数据,下面从设计层面、代码层面和数据库层面给出具体案例和解决方案。
核心原则:让查询走索引
全表扫描通常发生在:
- 查询条件没有索引
- 查询条件使用了函数或计算
- 使用了
LIKE '%keyword'模糊查询 - 隐式类型转换
- 数据量很小(MySQL 可能直接全表扫描)
具体案例与解决方案
案例1:模糊查询导致全表扫描
❌ 错误写法
String sql = "SELECT * FROM user WHERE name LIKE '%" + keyword + "%'";
✅ 解决方案
- 使用前缀匹配(走索引):
String sql = "SELECT * FROM user WHERE name LIKE '" + keyword + "%'";
- 或使用全文索引(MySQL):
ALTER TABLE user ADD FULLTEXT INDEX idx_name (name); SELECT * FROM user WHERE MATCH(name) AGAINST('keyword');
案例2:隐式类型转换
❌ 错误写法
String sql = "SELECT * FROM user WHERE phone = " + phone; // phone是varchar,phone是String
👉 phone 是 VARCHAR 类型,但传入的是数字,MySQL 会转成数字比较,导致索引失效。
✅ 正确写法
String sql = "SELECT * FROM user WHERE phone = '" + phone + "'";
👉 或者使用 PreparedStatement:
PreparedStatement ps = conn.prepareStatement("SELECT * FROM user WHERE phone = ?");
ps.setString(1, phone);
案例3:在索引列上使用函数
❌ 错误写法
String sql = "SELECT * FROM order WHERE DATE(create_time) = '2024-01-01'";
✅ 正确写法
String sql = "SELECT * FROM order WHERE create_time >= '2024-01-01 00:00:00' AND create_time < '2024-01-02 00:00:00'";
案例4:分页查询深度偏移
❌ 错误写法
String sql = "SELECT * FROM user LIMIT 100000, 10";
👉 先扫描10万行,再取最后10行。
✅ 优化方案
-
延迟关联:先查主键,再关联查数据
String sql = "SELECT u.* FROM user u " + "INNER JOIN (SELECT id FROM user ORDER BY id LIMIT 100000, 10) tmp " + "ON u.id = tmp.id"; -
游标分页:记录上一次查询的最后id
String sql = "SELECT * FROM user WHERE id > ? ORDER BY id LIMIT 10";
案例5:用 Java Stream 代替 SQL 过滤
❌ 错误写法
List<User> allUsers = userRepository.findAll(); // 全表扫描
List<User> activeUsers = allUsers.stream()
.filter(u -> "ACTIVE".equals(u.getStatus()))
.collect(Collectors.toList());
✅ 正确写法
List<User> activeUsers = userRepository.findByStatus("ACTIVE"); // 走索引
案例6:批量操作使用 IN 替代多个 OR
❌ 错误写法
String sql = "SELECT * FROM user WHERE id = 1 OR id = 2 OR id = 3";
✅ 正确写法
String sql = "SELECT * FROM user WHERE id IN (1, 2, 3)";
👉 IN 在 MySQL 中会使用 range 扫描,性能远优于 OR。
Java 代码层面的通用优化模板
使用 PreparedStatement + 绑定变量
// 避免SQL注入 + 减少SQL解析次数 + 让数据库使用缓存执行计划 String sql = "SELECT * FROM user WHERE name = ? AND age > ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, "张三"); ps.setInt(2, 20); ResultSet rs = ps.executeQuery();
使用 ORM 框架时要小心
- MyBatis:
- 避免在
<if>标签中拼接OR 1=1 - 用
<bind>或CONCAT控制模糊查询前缀
- 避免在
- JPA/Hibernate:
- 注意
@Query中的 HQL 是否有隐式类型转换 - 用
Specification+CriteriaBuilder避免全表
- 注意
数据量大时使用游标或流式查询
// JPA 流式查询(避免一次性加载全部数据到内存) @QueryHints(@QueryHint(name = "org.hibernate.fetchSize", value = "1000")) Stream<User> streamAllUsers();
避免全表扫描的检查清单
| 场景 | 错误做法 | 正确做法 |
|---|---|---|
| 模糊查询 | LIKE '%keyword%' |
LIKE 'keyword%' 或全文索引 |
| 隐式类型转换 | WHERE phone = 123 |
WHERE phone = '123' |
| 函数使用 | WHERE DATE(col)=... |
WHERE col BETWEEN ... |
| 分页深偏移 | LIMIT 100000,10 |
游标/延迟关联 |
| 代码过滤 | 查询全部后Java过滤 | SQL条件过滤 |
| 多个OR | OR 连接 |
IN (...) |
| 无索引查询 | 频繁查询无索引字段 | 加索引 |
一句话总结:
Java 中避免全表扫描,最有效的方法不是优化代码,而是让 SQL 走索引——包括明确 SQL 执行计划、检查索引覆盖、避免破坏索引的函数/计算/类型转换。