结论:千万级表TEXT字段高效查询需避开LIKE全模糊扫描和普通索引,应选全文索引、虚拟列哈希、字段拆分或外置搜索引擎;全文索引搜不到“AI”“go”等短词,因默认ft_min_word_len=4。

直接说结论:对千万级表的 TEXT 字段做高效查询,不能靠 LIKE '%xxx%' 硬扫,也不能只建普通索引;必须结合业务场景选方案——全文索引、虚拟列哈希、字段拆分或外置搜索引擎,否则查一次就卡住。
全文索引搜不到“AI”“go”这类短词?检查 ft_min_word_len
这是最常被忽略的配置项。MySQL 默认 ft_min_word_len=4,意味着所有长度
- 先确认当前值:
SHOW VARIABLES LIKE 'ft_min_word_len'; - 若需支持短词,动态修改(MySQL 5.7+ 支持):
SET GLOBAL ft_min_word_len = 2; - 必须重建索引才能生效:
ALTER TABLE articles DROP INDEX ft_content, ADD FULLTEXT(content); - 注意:
ft_stopword_file在 MySQL 8.0 默认不可删减,设太小会引入大量无意义停用词(如 “a”、“to”),且索引体积可能翻倍、写入变慢
LIKE '%xxx%' 永远用不上前缀索引
很多人给 content 加 INDEX(content(100)),指望它加速模糊搜索,这是徒劳的。B-tree 索引只能从左匹配,% 开头等于放弃索引扫描能力。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
-
WHERE content LIKE 'xxx%'→ 可走前缀索引 -
WHERE content LIKE '%xxx'或'%xxx%'→ 全表扫描,索引无效 - 真正要支持任意位置关键词,优先把结构化信息抽出来:比如日志中固定含
error code: 500,就加error_code VARCHAR(32)并建普通索引 - 若必须全文扫描式模糊查,用
MATCH(content) AGAINST('xxx' IN NATURAL LANGUAGE MODE),但注意它不支持+/-布尔语法,也不保证精确命中
查“内容是否已存在”?用虚拟列 + 哈希索引绕开大字段 I/O
当你要判断某段长文本是否重复插入(比如防日志重复入库),每次 SELECT * 都会触发溢出页读取,I/O 开销巨大。虚拟列能让你在写入时预计算哈希,查询走索引,完全避开读 TEXT 本体。
- 加虚拟列(STORED,物理存储):
ALTER TABLE logs ADD COLUMN content_md5 CHAR(32) AS (MD5(content)) STORED; - 建索引:
CREATE INDEX idx_content_md5 ON logs(content_md5); - 查询写法:
SELECT id FROM logs WHERE content_md5 = MD5('your raw text'); - 注意:MD5 冲突概率低但非零,生产环境建议加一层
content = ?二次校验(只对极少数哈希匹配行执行)
为什么 SELECT * 是真瓶颈,而不是索引没建好?
很多优化者盯着索引调优,却忘了:哪怕你 WHERE 条件走对了索引,只要语句里有 SELECT *,MySQL 就得为每条匹配记录去磁盘读取整个 TEXT 字段(通常存于溢出页),网络传输+内存拷贝开销远超索引查找本身。
- 永远明确指定需要的字段,尤其是避免在应用层拉取完整
TEXT后再过滤 - 高频展示场景,把摘要、关键词、标签等提前抽取到独立字段,加普通索引
- 纯搜索+高并发场景,别硬扛,
MySQL不是搜索引擎——Elasticsearch或Meilisearch的倒排+分词+缓存机制才是正解
最易被忽略的一点:全文索引对 TEXT 字段有效,但对 BLOB 和 JSON 类型直接不支持,建了也报错;而 ngram 解析器虽支持中文分词,但要求 MySQL ≥ 5.7.6 且建索引时显式声明 WITH PARSER ngram,漏掉就退化成单字切分,效果极差。

















