Java模糊查询提速案例优化

wen java案例 31

本文目录导读:

Java模糊查询提速案例优化

  1. 数据库索引优化(最基础)
  2. Elasticsearch搜索引擎(高性能方案)
  3. 分库分表+中间表策略
  4. 缓存策略(Redis)
  5. SQL优化技巧
  6. 实际案例:综合优化方案
  7. 优化对比表
  8. 关键优化建议

针对Java模糊查询(LIKE)的优化,以下是几种有效的提速方案,按推荐优先级排序:

数据库索引优化(最基础)

前缀模糊查询

-- 使用普通B-Tree索引即可
CREATE INDEX idx_name ON users(name);
-- 查询必须使用前缀匹配
SELECT * FROM users WHERE name LIKE '张%';

全文索引(推荐)

// MySQL全文索引查询
@Query(value = "SELECT * FROM articles WHERE MATCH(title, content) AGAINST(?1 IN BOOLEAN MODE)", nativeQuery = true)
List<Article> searchByFullText(String keyword);
-- 创建全文索引
CREATE FULLTEXT INDEX idx_fulltext ON articles(title, content);
-- 查询语法
SELECT * FROM articles WHERE MATCH(title, content) AGAINST('+关键词*' IN BOOLEAN MODE);

Elasticsearch搜索引擎(高性能方案)

基础配置

// 1. 添加依赖
implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
// 2. 实体映射
@Document(indexName = "users")
public class UserDocument {
    @Id
    private Long id;
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String name;
    @Field(type = FieldType.Text, analyzer = "ik_smart")
    private String description;
}
// 3. Repository
public interface UserSearchRepository extends ElasticsearchRepository<UserDocument, Long> {
    List<UserDocument> findByNameContaining(String keyword);
}
// 4. 服务层查询
@Service
public class SearchService {
    @Autowired
    private UserSearchRepository searchRepository;
    public List<UserDocument> searchByName(String keyword) {
        return searchRepository.findByNameContaining(keyword);
    }
}

高级查询(支持模糊、高亮)

public class ElasticsearchService {
    public List<UserDocument> fuzzySearch(String keyword) {
        // 构建模糊查询
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 模糊查询
        queryBuilder.withQuery(QueryBuilders.fuzzyQuery("name", keyword)
            .fuzziness(Fuzziness.AUTO));
        // 或通配符查询
        queryBuilder.withQuery(QueryBuilders.wildcardQuery("name", "*" + keyword + "*"));
        // 高亮显示
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("name");
        queryBuilder.withHighlightBuilder(highlightBuilder);
        return elasticsearchTemplate.queryForPage(queryBuilder.build(), UserDocument.class).getContent();
    }
}

分库分表+中间表策略

创建搜索专用表

-- 1. 创建搜索中间表
CREATE TABLE user_search_index (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT,
    search_text VARCHAR(500),
    INDEX idx_search_text(search_text)
) ENGINE=InnoDB;
-- 2. 数据同步(定时任务或触发器)
INSERT INTO user_search_index(user_id, search_text)
SELECT id, CONCAT(name, ',', email, ',', phone) FROM users;
-- 3. 搜索查询
SELECT u.* FROM users u
INNER JOIN user_search_index si ON u.id = si.user_id
WHERE si.search_text LIKE '%关键词%'
LIMIT 20;

缓存策略(Redis)

缓存热点搜索词

@Service
public class SearchCacheService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    private static final String SEARCH_CACHE_PREFIX = "search:keyword:";
    private static final long CACHE_TTL = 300; // 5分钟
    public List<User> searchWithCache(String keyword) {
        String cacheKey = SEARCH_CACHE_PREFIX + keyword;
        // 1. 尝试从缓存获取
        List<User> cachedResult = (List<User>) redisTemplate.opsForValue().get(cacheKey);
        if (cachedResult != null) {
            return cachedResult;
        }
        // 2. 缓存未命中,查询数据库
        List<User> result = userRepository.findByNameContaining(keyword);
        // 3. 异步写入缓存
        CompletableFuture.runAsync(() -> {
            redisTemplate.opsForValue().set(cacheKey, result, CACHE_TTL, TimeUnit.SECONDS);
        });
        return result;
    }
}

SQL优化技巧

强制索引与查询优化

@Repository
public class UserRepositoryImpl {
    @PersistenceContext
    private EntityManager entityManager;
    public List<User> searchByKeyword(String keyword) {
        // 1. 使用HINT强制索引
        Query query = entityManager.createNativeQuery(
            "SELECT * FROM users USE INDEX(idx_name) WHERE name LIKE ?1", User.class);
        query.setParameter(1, keyword + "%"); // 前缀查询
        // 2. 限制返回行数
        query.setMaxResults(100);
        return query.getResultList();
    }
    // 3. 分批查询,避免大结果集
    public List<User> searchByBatch(String keyword, int batchSize) {
        List<User> results = new ArrayList<>();
        int offset = 0;
        List<User> batch;
        do {
            batch = entityManager.createNativeQuery(
                "SELECT * FROM users WHERE name LIKE ?1 LIMIT ?2 OFFSET ?3", User.class)
                .setParameter(1, "%" + keyword + "%")
                .setParameter(2, batchSize)
                .setParameter(3, offset)
                .getResultList();
            results.addAll(batch);
            offset += batchSize;
        } while (batch.size() == batchSize);
        return results;
    }
}

实际案例:综合优化方案

// 完整优化方案
@Service
@Slf4j
public class OptimizedSearchService {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private UserSearchRepository searchRepository; // ES
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    /**
     * 智能搜索:先ES,再数据库,最后缓存
     */
    public Page<User> smartSearch(String keyword, int page, int size) {
        long startTime = System.currentTimeMillis();
        try {
            // 1. 尝试ES搜索(高性能)
            Page<UserDocument> esResults = searchRepository.search(
                buildNativeSearchQuery(keyword), 
                PageRequest.of(page, size)
            );
            if (esResults.hasContent()) {
                log.info("ES search took {}ms", System.currentTimeMillis() - startTime);
                return convertToUserPage(esResults);
            }
        } catch (Exception e) {
            log.warn("ES search failed, falling back to database", e);
        }
        // 2. ES失败时使用数据库+缓存
        String cacheKey = "search:" + keyword + ":" + page + ":" + size;
        // 从缓存获取
        Page<User> cached = (Page<User>) redisTemplate.opsForValue().get(cacheKey);
        if (cached != null) {
            log.info("Cache hit for keyword: {}", keyword);
            return cached;
        }
        // 3. 数据库查询(使用优化索引)
        Page<User> dbResults = userRepository.findByNameStartingWith(
            keyword, 
            PageRequest.of(page, size)
        );
        // 4. 写入缓存(异步)
        if (dbResults.hasContent()) {
            CompletableFuture.runAsync(() -> {
                redisTemplate.opsForValue().set(cacheKey, dbResults, 300, TimeUnit.SECONDS);
            });
        }
        log.info("Database search took {}ms", System.currentTimeMillis() - startTime);
        return dbResults;
    }
}

优化对比表

方案 性能提升 复杂度 适用场景
索引优化 2-10倍 前缀查询
全文索引 5-20倍 全文搜索
Elasticsearch 10-100倍 大规模搜索
缓存策略 3-10倍 热点数据
分表策略 3-5倍 超大数据表

关键优化建议

  1. 避免使用%keyword%:这种查询无法使用索引
  2. 限制结果集大小:使用LIMIT或分页
  3. 使用覆盖索引:查询字段全部在索引中
  4. 考虑业务容忍度:对实时性要求不高的场景,可以接受秒级延迟

根据你的实际数据量和并发情况,选择最合适的优化方案,通常建议先尝试数据库索引优化,再考虑引入ES等重型方案。

抱歉,评论功能暂时关闭!