如何优化结果量极大的MySQL/MyISAM全文搜索?
首先得戳破你遇到的核心痛点:MyISAM的全文索引和普通B-tree索引没法协同工作。当你同时用MATCH...AGAINST和date筛选时,MySQL会先执行全文搜索(硬扫200万条匹配YES的行),再去过滤日期条件——哪怕最终结果是0条,前面的全量扫描已经耗光了时间,这就是加了date筛选还是慢的原因。
下面给你几个落地性强的解决方案,按优先级排序:
1. 切换到InnoDB存储引擎
这是最省心的方案。InnoDB从MySQL 5.6开始支持全文索引,而且优化器能智能组合全文索引和普通索引(比如你date字段的B-tree索引)。针对你的例子,当执行SELECT count(*) FROM books WHERE date > "2019-09-23" AND MATCH(keywords) AGAINST ('+YES' IN BOOLEAN MODE)时,InnoDB会先通过date索引快速定位符合日期条件的行(如果没有直接返回0),再对这些行做全文搜索,彻底避免了高频词的全量扫描开销。
切换步骤很简单,记得先备份数据:
ALTER TABLE books ENGINE=InnoDB;
2. 对表按日期分区
如果暂时不想换引擎,可以试试MyISAM的分区功能,按date字段分区(比如按年月拆分)。这样查询时可以直接指定分区范围,MySQL只会扫描符合条件的分区,再在分区内执行全文搜索,大幅压缩扫描的数据量。
比如按年分区的示例:
ALTER TABLE books PARTITION BY RANGE (TO_DAYS(date)) ( PARTITION p2019 VALUES LESS THAN (TO_DAYS('2020-01-01')), PARTITION p2020 VALUES LESS THAN (TO_DAYS('2021-01-01')), -- 根据你的数据范围继续添加后续分区 );
查询时显式指定分区,进一步缩小扫描范围:
SELECT count(*) FROM books PARTITION (p2020,p2021) WHERE date > "2019-09-23" AND MATCH(keywords) AGAINST ('+YES' IN BOOLEAN MODE);
3. 新增高频词汇总表
对于YES这类高频词,可以定期生成一个汇总表,记录每个日期区间内各高频词的出现数量。比如:
CREATE TABLE books_keyword_summary ( keyword VARCHAR(255) NOT NULL, stat_date DATE NOT NULL, count INT NOT NULL, PRIMARY KEY (keyword, stat_date) );
然后用定时任务(比如crontab+SQL脚本)每天跑统计:
REPLACE INTO books_keyword_summary SELECT 'YES', DATE(date), COUNT(*) FROM books WHERE MATCH(keywords) AGAINST ('+YES' IN BOOLEAN MODE) GROUP BY DATE(date);
查询时先查汇总表,如果对应日期区间的count是0,直接返回0;否则再去主表查询,避免无效的全文扫描。
4. 迁移到专业全文搜索工具
如果数据量还在增长,或者需要更复杂的搜索能力(比如分词、权重排序),建议把数据同步到Elasticsearch这类工具。它天生支持多条件联合查询,处理高频词搜索的性能比MySQL原生全文索引好几个量级,还能支持更灵活的搜索规则。
内容的提问来源于stack exchange,提问作者christianb35

