商品搜索需避免like导致性能崩溃,应采用预处理+前缀索引+全文检索(如mysql ngram或elasticsearch),集合筛选优先位运算,分页用search_after,聚合统计禁用实时count(*),改用估算值或异步汇总。

商品搜索怎么实现关键词模糊匹配
直接用 LIKE '%keyword%' 最快,但线上大表会拖垮查询性能,尤其当 keyword 很短(比如“i”“a”)时,MySQL 索引基本失效。真正能扛住流量的方案是预处理 + 前缀索引配合全文检索。
实操建议:
- 对商品标题、品牌、类目字段做标准化清洗(去空格、转小写、删标点),存入额外的
search_text字段 - MySQL 8.0+ 可直接用
MATCH() AGAINST(),但注意必须建FULLTEXT索引,且默认不支持单字分词——需要配置ngram_token_size=1(否则搜“iPhone”匹配不到“phone”) - 更稳的选择是引入
Elasticsearch,用match_phrase_prefix实现“输一半就出结果”,但同步延迟要控制在 1 秒内,否则用户改完商品信息搜不到 - 别在模糊匹配里硬塞正则——
REGEXP在千万级商品表上一次查询可能卡死连接池
集合过滤为什么用位运算比 IN 更快
当用户勾选多个筛选条件(如“安卓 + 5G + 旗舰芯片”),后端常把每个条件映射成一个整数 bit 位,用 & 运算一次性判断是否全命中。这比拼接长 IN 列表或多次 JOIN 快得多,尤其在属性维度固定、变化少的场景(比如手机参数)。
实操建议:
- 提前为每个属性值分配唯一 bit 位:比如
os_android = 1(1 ),<code>network_5g = 2(1 ),<code>chip_flagship = 4(1 ) - 商品表加一个
feature_bits字段,插入时按规则累加(如安卓+5G →1 | 2 = 3) - 查询时写
WHERE feature_bits & 3 = 3,确保所有指定 bit 都为 1;别漏等号右边的值,写成= 0或> 0就逻辑全错 - 位数超 64 就得拆字段或换方案——MySQL
BIGINT最多 64 位,硬扩会导致计算溢出和可读性灾难
搜索结果怎么避免分页跳变
用户翻到第 10 页突然发现第 9 页的某商品不见了,大概率是排序字段有重复值(比如多款商品销量同为 999),加上新上架/下架导致偏移错乱。这不是前端问题,是 SQL 分页没加稳定锚点。
实操建议:
- 强制用复合排序:主键必须参与,例如
ORDER BY sales_count DESC, item_id ASC,哪怕item_id对业务无意义,它能保证顺序绝对唯一 - 不用
LIMIT offset, size做深分页——查第 10000 页时 MySQL 仍要扫描前 100000 行;换成游标分页:WHERE sales_count ? OR item_id > ?) - ES 里对应的是
search_after,传上一页最后一条的sort值数组,不是from;漏传或传错类型(比如把数字当字符串)会导致结果重复或跳过 - 缓存分页结果时,key 必须包含完整筛选参数哈希值,否则“A 用户搜‘手机’第 3 页”和“B 用户搜‘iphone’第 3 页”会互相污染
为什么聚合统计不能只靠 SQL COUNT
搜索页顶部显示“共找到 2,341,567 件商品”,这个数字如果每次请求都跑 COUNT(*),在亿级商品库上就是数据库的定时炸弹。真实系统里,这个数要么异步更新,要么允许小幅误差。
实操建议:
- 用
SHOW TABLE STATUS查rows字段——它是 InnoDB 的估算值,误差通常在 ±10%,但响应在毫秒级,适合展示型总数 - 高频变更场景(比如秒杀中库存实时归零)必须走 Redis HyperLogLog 统计去重 UV,但注意它不支持精确 count,也做不了条件过滤
- 如果真要准确实时总数,得建汇总表,用触发器或 CDC 同步变更,但代价是写放大——每新增一个商品,要额外更新 N 张汇总表
- 千万别在搜索接口里嵌套子查询算总数再查数据,像
SELECT *, (SELECT COUNT(*) FROM ...) AS total FROM ...,这个子查询会为每一行执行一次
模糊匹配的边界词、位运算的 bit 位上限、分页锚点的字段选择、聚合数的精度取舍——这些地方看着是细节,但线上一崩就是整页搜不出来。调的时候别只盯着日志报错,多看慢查询日志里那条实际执行的 SQL 是什么样子。










