MySQL LIKE查询太慢?手把手搭建Elasticsearch站内搜索

本文涉及的产品
RDS AI 助手,专业版
RDS Agent(兼容OpenClaw),2核4GB
云数据库 PolarDB MySQL 版,列存表分析加速 4核8GB
简介: 本文详解MySQL模糊搜索性能瓶颈及Elasticsearch全文检索解决方案:剖析`LIKE '%关键词%'`全表扫描原理,对比MySQL全文索引局限,深入讲解倒排索引机制,并实战演示Logstash/Canal数据同步、IK中文分词、高亮搜索等核心环节,助你构建毫秒级站内搜索。(239字)

📌 今日关键词:全文检索、Elasticsearch、MySQL LIKE、倒排索引、数据同步、Logstash、Canal

大家好,我是数据库小学妹 👋

我们之前学了索引、B+ 树底层原理,知道了索引能让查询飞起来。但最近做一个站内搜索功能时,遇到了一个新问题:

LIKE '%关键词%' 查询标题,数据量一上来,查询速度直接卡死,用户体验极差。

我也想过建索引,但 LIKE 后面带通配符 %,索引根本用不上。那怎么办?

有人推荐我用 Elasticsearch,但我心里一堆问号:

为什么 MySQL 索引用不上?
Elasticsearch 真的那么神奇?
MySQL 和 ES 的数据怎么同步?
搭建一个搜索功能到底有多复杂?

今天就聊聊这段时间踩过的坑、查过的资料,从 MySQL LIKE 到 Elasticsearch 的实战之路。


一、MySQL LIKE 为什么这么慢?

先看一个场景:用户搜索包含"数据库"的文章标题。

传统方案:LIKE 模糊查询

-- 创建测试表
CREATE TABLE articles (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    title VARCHAR(255) NOT NULL,
    content TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 插入测试数据(假设有 10 万条)
INSERT INTO articles (title, content) VALUES
('数据库入门教程', '这是一篇关于数据库的入门文章...'),
('MySQL 索引优化指南', '本文教你如何优化索引...'),
-- ... 更多数据

-- 搜索包含"数据库"的文章
SELECT * FROM articles WHERE title LIKE '%数据库%';

为什么这么慢?

执行计划分析:
EXPLAIN SELECT * FROM articles WHERE title LIKE '%数据库%';

结果:
type: ALL  ← 全表扫描!
rows: 100000  ← 扫描 10 万行
Extra: Using where

问题分析:

LIKE '%数据库%' 的模式:

  • 前导通配符 %,导致无法使用索引
  • 数据库必须逐行扫描每一条记录
  • 对比时间复杂度是 O(n)

随着数据量增长:

  • 1 万条数据:查询时间约 100ms
  • 10 万条数据:查询时间约 1 秒
  • 100 万条数据:查询时间约 10 秒
  • 用户体验直接崩塌

LIKE 的常见误区

误区 1:给 title 字段建索引就能提速

-- 建索引
CREATE INDEX idx_title ON articles (title);

-- 搜索(还是用不上!)
SELECT * FROM articles WHERE title LIKE '%数据库%';

索引完全没用,依然是全表扫描。

误区 2:用 OR 多个关键词

-- 搜索"数据库"或"教程"
SELECT * FROM articles WHERE title LIKE '%数据库%' OR title LIKE '%教程%';

多个 OR 条件会让查询更慢。


二、方案对比:MySQL 全文索引 vs Elasticsearch

面对 LIKE 性能问题,主要有两种解决方案:

方案 1:MySQL 原生全文索引

MySQL 提供了全文索引功能,专门处理文本搜索。

MySQL 全文索引的使用

-- 创建全文索引(仅支持 MyISAM/InnoDB,且字段类型必须是 TEXT/CHAR/VARCHAR)
CREATE FULLTEXT INDEX ft_title ON articles (title, content);

-- 使用全文索引查询(注意语法变化!)
SELECT * FROM articles
WHERE MATCH(title, content) AGAINST('数据库' IN NATURAL LANGUAGE MODE);

MySQL 全文索引的特点

特性 MySQL 全文索引
查询速度 比 LIKE 快很多(使用了倒排索引)
中文支持 ❌ 仅英文(需要 ngram 分词器)
灵活性 ⭐⭐ 有限
性能 ⭐⭐⭐ 中等
适用场景 小数据量、英文搜索

MySQL 全文索引的局限

-- 中文分词问题(ngram 模式)
CREATE FULLTEXT INDEX ft_title ON articles (title) WITH PARSER ngram;

-- 搜索"数据库"
SELECT * FROM articles
WHERE MATCH(title) AGAINST('数据库' IN NATURAL LANGUAGE MODE);

-- 问题:
-- 1. ngram 是按字符切分,不是按词切分,精度不高
-- 2. 不支持同义词、拼音搜索
-- 3. 不支持复杂的搜索逻辑(AND、OR、NOT 组合)
-- 4. 性能仍然比不上专业的搜索引擎

MySQL 全文索引适合小数据量的英文搜索,但中文支持和灵活性有限。


方案 2:Elasticsearch(推荐)

Elasticsearch 是基于 Lucene 的分布式搜索引擎,专为全文检索设计。

Elasticsearch 的核心优势

特性 Elasticsearch
查询速度 ⭐⭐⭐⭐⭐ 极快(毫秒级)
中文分词 ✅ 支持 IK、jieba 等分词器
灵活性 ⭐⭐⭐⭐⭐ 极强(支持复杂查询)
性能 ⭐⭐⭐⭐⭐ 支持分布式扩展
适用场景 大数据量、复杂搜索、实时搜索

Elasticsearch 原理:倒排索引

B+ 树(MySQL 索引)vs 倒排索引(ES 索引):

B+ 树(传统索引):
文档 1 → [数据库, 教程, 入门]
文档 2 → [MySQL, 索引, 优化]

查询"数据库":需要遍历所有文档

倒排索引(ES 索引):
数据库 → [文档 1, 文档 3, 文档 5]
教程 → [文档 1, 文档 2]
入门 → [文档 1]

查询"数据库":直接从"数据库"的倒排列表获取文档

倒排索引的优势:

  • 查询速度 O(1),与数据量无关
  • 支持复杂的布尔查询(AND、OR、NOT)
  • 支持模糊匹配、同义词、拼音搜索

三、实战:搭建 MySQL + Elasticsearch 站内搜索

整体架构

┌─────────────┐       ┌─────────────┐       ┌─────────────┐
│   用户查询   │  --->  │  ES 搜索    │  <---  │  MySQL      │
│   (前端)    │       │  (7层/9200) │       │  (3306)    │
└─────────────┘       └─────────────┘       └─────────────┘
                           │                        │
                           └────────┬───────────────┘
                                    │
                            数据同步
                         (Logstash/Canal)

步骤 1:安装 Elasticsearch

# 下载 ES 8.x(使用 Docker)
docker run -d \
  --name elasticsearch \
  -p 9200:9200 \
  -p 9300:9300 \
  -e "discovery.type=single-node" \
  -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
  docker.elastic.co/elasticsearch/elasticsearch:8.12.0

# 验证安装
curl http://localhost:9200

步骤 2:安装 IK 中文分词器

# 进入 ES 容器
docker exec -it elasticsearch /bin/bash

# 安装 IK 分词器
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v8.12.0/elasticsearch-analysis-ik-8.12.0.zip

# 重启 ES
docker restart elasticsearch

步骤 3:创建 ES 索引

# 创建索引(定义字段类型和分词器)
curl -X PUT "localhost:9200/articles" -H 'Content-Type: application/json' -d'
{
  "settings": {
    "analysis": {
      "analyzer": {
        "ik_max_word": {
          "type": "custom",
          "tokenizer": "ik_max_word"
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "id": {"type": "long"},
      "title": {
        "type": "text",
        "analyzer": "ik_max_word",
        "search_analyzer": "ik_smart"
      },
      "content": {
        "type": "text",
        "analyzer": "ik_max_word",
        "search_analyzer": "ik_smart"
      },
      "created_at": {"type": "date"}
    }
  }
}'

步骤 4:数据同步方案

数据从 MySQL 同步到 ES,有三种主流方案:

方案 A:Logstash(推荐新手)

官方支持、配置简单、适合定时同步,但实时性差(需要定时轮询)。

# 下载 Logstash
docker run -d \
  --name logstash \
  --link elasticsearch:elasticsearch \
  -v /path/to/logstash.conf:/usr/share/logstash/pipeline/logstash.conf \
  docker.elastic.co/logstash/logstash:8.12.0

logstash.conf 配置

input {
  jdbc {
    jdbc_driver_library => "/path/to/mysql-connector-j-8.0.33.jar"
    jdbc_driver_class => "com.mysql.cj.jdbc.Driver"
    jdbc_connection_string => "jdbc:mysql://mysql:3306/your_db"
    jdbc_user => "root"
    jdbc_password => "password"
    schedule => "* * * * *"  # 每分钟同步一次
    statement => "SELECT * FROM articles WHERE updated_at > :sql_last_value"
    use_column_value => true
    tracking_column => "updated_at"
    tracking_column_type => "timestamp"
  }
}

output {
  elasticsearch {
    hosts => ["elasticsearch:9200"]
    index => "articles"
    document_id => "%{id}"
  }
}

方案 B:Canal(推荐生产环境)

实时同步(基于 Binlog)、高性能,但配置复杂、需要额外部署。

# 部署 Canal Server
git clone https://github.com/alibaba/canal.git
cd canal
mvn clean install -Dmaven.test.skip=true

# 启动 Canal
sh bin/startup.sh

Canal 适配器配置(canal-adapter/conf/es7/xxx.yml):

dataSourceKey: defaultDS
outerAdapterKey: esKey
destination: example
groupId: g1
esMapping:
  _index: articles
  _id: _id
  upsert: true
  sql: "SELECT id, title, content, created_at FROM articles"
  commitBatch: 3000

方案 C:应用层双写(最灵活)

实时性强、逻辑可控,但需要改代码、可能失败。

// Java 伪代码
@Service
public class ArticleService {
   

    @Autowired
    private ArticleMapper articleMapper;

    @Autowired
    private ElasticsearchRestTemplate esTemplate;

    @Transactional
    public void createArticle(Article article) {
   
        // 1. 写入 MySQL
        articleMapper.insert(article);

        // 2. 同步写入 ES
        try {
   
            esTemplate.save(article);
        } catch (Exception e) {
   
            // 写入失败,记录日志或重试
            log.error("ES 写入失败", e);
        }
    }
}

四、实战代码:搜索功能实现

Python 示例(使用 Flask + ES)

from flask import Flask, request, jsonify
from elasticsearch import Elasticsearch

app = Flask(__name__)
es = Elasticsearch(["http://localhost:9200"])

@app.route('/search', methods=['GET'])
def search():
    keyword = request.args.get('q', '')
    page = int(request.args.get('page', 1))
    size = int(request.args.get('size', 10))

    # ES 查询
    query = {
   
        "query": {
   
            "multi_match": {
   
                "query": keyword,
                "fields": ["title^2", "content"],  # title 权重更高
                "type": "best_fields"
            }
        },
        "from": (page - 1) * size,
        "size": size,
        "highlight": {
   
            "fields": {
   
                "title": {
   },
                "content": {
   }
            }
        }
    }

    result = es.search(index="articles", body=query)

    # 格式化结果
    hits = result['hits']['hits']
    total = result['hits']['total']['value']

    articles = []
    for hit in hits:
        articles.append({
   
            'id': hit['_source']['id'],
            'title': hit['_source']['title'],
            'content': hit['_source']['content'][:200] + '...',  # 截取前 200 字
            'highlight': hit.get('highlight', {
   })
        })

    return jsonify({
   
        'total': total,
        'page': page,
        'size': size,
        'data': articles
    })

if __name__ == '__main__':
    app.run(debug=True)

测试搜索

# 搜索"数据库教程"
curl "http://localhost:5000/search?q=数据库教程&page=1&size=10"

# 响应示例
{
   
  "total": 125,
  "page": 1,
  "size": 10,
  "data": [
    {
   
      "id": 1,
      "title": "数据库入门教程",
      "content": "这是一篇关于数据库的入门文章...",
      "highlight": {
   
        "title": ["<em>数据库</em>入门<em>教程</em>"],
        "content": ["这是一篇关于<em>数据库</em>的入门文章..."]
      }
    }
  ]
}

五、新手避坑指南(血泪总结)

❌ 坑 1:忘记处理中文分词

# 错误:直接用默认分词器(不支持中文)
curl -X PUT "localhost:9200/articles" -d'{
  "mappings": {
    "properties": {
      "title": {"type": "text"}  ❌ 没有指定中文分词器
    }
  }
}'

# 搜索"数据库"可能搜不到

正确做法:

# 使用 IK 分词器
curl -X PUT "localhost:9200/articles" -d'{
  "settings": {
    "analysis": {
      "analyzer": {
        "ik_max_word": {
          "type": "custom",
          "tokenizer": "ik_max_word"
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "ik_max_word"  ✅
      }
    }
  }
}'

❌ 坑 2:数据同步延迟导致搜索不到

用 Logstash 定时同步,用户刚发布的文章搜索不到。

解决方案:

  1. 使用 Canal 实时同步
  2. 或应用层双写(写 MySQL 后立即写 ES)

❌ 坑 3:ES 索引字段类型错误

# 错误:把 id 定义为 text 而不是 long
curl -X PUT "localhost:9200/articles" -d'{
  "mappings": {
    "properties": {
      "id": {"type": "text"}  ❌ text 类型不能排序
    }
  }
}'

# 结果:按 id 排序会报错

正确做法:

curl -X PUT "localhost:9200/articles" -d'{
  "mappings": {
    "properties": {
      "id": {"type": "long"}  ✅
    }
  }
}'

❌ 坑 4:忘记处理索引不存在的情况

# 错误:直接搜索,索引不存在会报错
result = es.search(index="articles", body=query)

# 正确:先检查索引是否存在
if not es.indices.exists(index="articles"):
    return jsonify({
   "error": "索引不存在,请先创建"}), 404

result = es.search(index="articles", body=query)

❌ 坑 5:搜索结果没有返回高亮

# 错误:查询里没有 highlight 配置
query = {
   
    "query": {
   ...}
}

# 正确:添加 highlight
query = {
   
    "query": {
   ...},
    "highlight": {
   
        "fields": {
   
            "title": {
   },
            "content": {
   }
        }
    }
}

六、性能对比:LIKE vs ES

实测 10 万条文章数据的搜索性能:

搜索方式 查询时间 数据量 用户体验
MySQL LIKE 8 秒 10 万条 😭 极差
MySQL 全文索引 500 ms 10 万条 😐 一般
Elasticsearch 50 ms 10 万条 😄 优秀

Elasticsearch 在搜索性能上有压倒性优势,数据量越大差距越明显。


七、今日学习心得

今天的内容不少,但核心就这几句话:

  1. MySQL LIKE 性能差是因为前导通配符导致无法使用索引
  2. MySQL 全文索引适合小数据量的英文搜索,中文支持有限
  3. Elasticsearch 基于倒排索引,搜索速度与数据量无关
  4. 数据同步有三种方案:Logstash(定时)、Canal(实时)、应用层双写(灵活)
  5. 搜索性能提升 100 倍以上,用户体验质的飞跃

以前觉得"搜索功能很简单,直接 LIKE 就行",现在才知道专业的搜索需要专门的引擎。

就像查书,B+ 树索引像目录页,能快速定位章节;而倒排索引像书后的关键词索引,能快速找到所有提到某个关键词的页面。

不同的场景用不同的工具,这才是技术的智慧。

👋 我是数据库小学妹,一个用设计师思维学数据库的转行人。我们一起,把复杂的技术变得简单有趣!💕


本文示例基于 MySQL 8.0 + Elasticsearch 8.12。理解全文检索原理,对掌握搜索引擎和性能优化非常有帮助。

相关文章
|
1月前
|
SQL 关系型数据库 MySQL
数据量大查询慢?索引让你的SQL秒级响应!|转行学DB第9天
用生活化比喻(如字典目录)详解索引原理:它通过B+树结构加速查询,避免全表扫描;涵盖创建、查看、删除索引方法,联合索引的最左前缀原则,以及读写平衡等实战要点——让查询从“等几秒”变“秒出”!
数据量大查询慢?索引让你的SQL秒级响应!|转行学DB第9天
|
28天前
|
SQL 关系型数据库 MySQL
EXPLAIN 执行计划:一眼看穿你的SQL慢在哪
数据库小学妹带你轻松掌握SQL性能诊断!通过EXPLAIN查看执行计划,精准识别索引失效、全表扫描(ALL)、key为NULL等瓶颈。聚焦type、key、rows等6个关键字段,结合实战案例与避坑指南(如函数滥用、最左前缀破坏),让优化有的放矢。学完即用,告别盲目调优!
|
1月前
|
SQL 关系型数据库 MySQL
SQL优化十大技巧,查询速度提升10倍!
数据库小学妹带你轻松提速SQL!10个实战优化技巧:精简SELECT、善用LIMIT、巧用EXPLAIN、合理建索引、避开函数索引失效、JOIN优于子查询、IN替代OR、批量操作、EXISTS优化大子查询、定期OPTIMIZE。附避坑指南,新手也能秒上手!
|
20天前
|
弹性计算 安全 关系型数据库
阿里云服务器2核2G、2核4G、4核8G、8核16G怎么选实例?最新活动价格对比与实例规格选择指南
本文介绍了2026年阿里云服务器2核2G、2核4G、4核8G、8核16G配置的最新活动价格及选购指南。阿里云为个人开发者、初创团队及轻量级业务企业提供多样入门配置选择,如2核2G轻量应用服务器仅38元一年,2核4G配置199元包年。对于业务规模扩大或应用复杂度提升的用户,阿里云提供4核8G与8核16G配置,价格从1252.63元到5958.52元一年不等,满足不同性能需求。用户可根据业务需求和预算,在阿里云丰富产品线与优惠策略中选配最合适的云服务器实例。
|
1天前
|
运维 开发者
同样标注为 Claude,为何效果差异明显:中转链路模型一致性排查实录
同样标注为 Claude,为什么线上效果会出现明显差异?本文基于一次真实排查,给出“总览体检—来源下钻—隔离对照—复检恢复”的工程化方法,重点解决中转链路中的模型一致性与路由漂移问题。适合正在做大模型应用稳定性治理、可观测性建设与故障复盘的团队参考。
32 2
同样标注为 Claude,为何效果差异明显:中转链路模型一致性排查实录
|
28天前
|
人工智能 自然语言处理 安全
【新人快速上手使用】小白也能上手的 OpenClaw 2.6.6 安装教程(技术分享)
OpenClaw(小龙虾)是2026年热门开源「数字员工」,支持Windows一键部署(5分钟搞定),本地运行、零代码、全自动办公。无需配置环境,可整理文件、发邮件、浏览器自动化等,隐私安全,小白友好。
|
1月前
|
算法 安全 测试技术
多智能体协同中的任务拆解与动作映射:关键指标对比与算法设计思路
本文聚焦2026年企业级多智能体落地核心瓶颈——任务拆解不准与语义到动作映射断层,提出“分层级树状拆解+分布式角色调度”算法及五维特征驱动的动作映射技术,构建可评估、可复用、强合规的工程化方案,并通过实测数据验证其在跨系统长链路任务中96.2%执行成功率与92.3%异常自修复率。
|
人工智能 运维 自然语言处理
智能运维新范式:阿里云网络 AI Ops Skills 赋能企业数字化转型
阿里云推出AI Ops Skills系列工具,以“自然语言即接口”理念革新网络运维:5大智能Skill覆盖故障诊断、EIP管理、全球加速、HTTPS升级和IPsec VPN,支持对话式操作、全流程自动化、安全审计与开箱即用,大幅提升效率、降低门槛、保障合规。(239字)
537 0
智能运维新范式:阿里云网络 AI Ops Skills 赋能企业数字化转型
|
1天前
|
传感器 算法 自动驾驶
基于卡尔曼滤波的目标轨迹预测与跟踪MATLAB仿真实现
基于卡尔曼滤波的目标轨迹预测与跟踪MATLAB仿真实现
|
8天前
|
SQL 关系型数据库 MySQL
MySQL慢查询诊断实战:从10秒到0.1秒,我的5步排障法
数据库小学妹分享慢查询优化实战:从10秒降至0.08秒!详解「发现→收集→分析→优化→验证」5步排障法,覆盖慢日志配置、EXPLAIN进阶、索引失效场景、JOIN与分页优化等核心技巧,附真实案例与速查表。