写在前面
工作当中遇到了一些慢查询,通常我遇到的慢查询基本都是没有命中索引或者全表扫描了
这一次遇到的比较特别
查询语句当中大量出现find_in_set
函数,这个函数我平时接触的比较少
字面意思解释应该是在集合当中查找的函数
含义
FIND_IN_SET()
是 MySQL 中用于在逗号分隔的字符串列表中查找特定值位置的函数
也就是数据表的某一个字段,比如user_ids
这样的字段,类型当然是var_char
存储的是一些以id
和逗号拼起来的字符串,就像这样
1,2,3,4,5,8,9,15
那find_in_set()
函数怎么用呢?
比如,我需要查看评论表当中包含user_id=1
的评论,但是这个评论表没有user_id
的单独字段,而是user_ids
这样的字符串字段,就需要这样写SQL
语句了
SELECT
*
FROM
user_reviews
WHERE
FIND_IN_SET('1', user_reviews.user_ids);
这样就可以实现这个评论数据的查询了。
劣势
这个函数其实很差劲,在小数据量的时候不明显,甚至可以减少工作量
但是数据量一大,并发一多,就会死得很惨!
性能较差:
- 全表扫描:无法利用索引,需逐行检查字段。
- O (n) 复杂度:列表越长,查询越慢。
数据结构不规范:
- 违反数据库范式,不利于数据维护(如增删标签需修改整个字符串)。
- 无法单独对标签建立索引。
功能受限:
- 仅支持逗号分隔,无法处理其他分隔符或嵌套结构。
- 无法直接排序或聚合标签。
解决方案
方案其实有不少,但是都需要做一个很大的改动
毕竟当时偷了多少懒,现在就得还多少……
如果要从这些方案当中选一个的话,我认为是选方案一:拆表,是最合适的,后续兼容也更方便
1. 拆表,多表关联
方案来说,就是将原本的关联字段删除
创一个新的表,将关联字段和主键放进去,查询的时候连表查询或提前缓存查询。
比如这里的user_id
和review_id
放到新表里
后续的方案三可以并行,减少连表的时间损耗。
2. 改造为json
结构
这种方案有局限性,MySQL5.7+
才支持JSON
格式的结构,旧版本直接就麻爪了
而且JSON
格式比起拆表的方式性能是略低的,JSON
也无法直接用作连表条件
方式如下:
CREATE TABLE articles (
id INT PRIMARY KEY,
title VARCHAR(100),
tags JSON, -- 存储 ["tag1", "tag2"]
INDEX (tags) -- MySQL 5.7+ 支持 JSON 索引
);
-- 查询包含 'mysql' 标签的文章
SELECT *
FROM articles
WHERE JSON_CONTAINS(tags, '"mysql"');
3. 增加多重缓存机制,减少直接数据库查询
采用redis
或mongoDB
或Memache
等nosql
的方式,提前将相关的条件,常用的查询方式进行存储
请求先进这类数据库进行查询,当缓存穿透时,才进到MySQL
里查询
使用内存检索的方式,降低查询耗时,减少MySQL
参与查询
4. 全文索引方式
这种方式类似于直接查询关键字
实现如下:
ALTER TABLE articles ADD FULLTEXT(tags);
-- 使用 MATCH AGAINST 查询
SELECT *
FROM articles
WHERE MATCH(tags) AGAINST('mysql' IN NATURAL LANGUAGE MODE);
但是要注意,这种方式有很大的隐患,毕竟查询4
,出来45
也是合理的
毕竟是关键字匹配,而且还涉及到全表扫描的问题,需要权衡清楚。
5. 引入三方库,如ES
建立更多索引查询
这种方式主要是在大数据大体量下才需要,本质其实跟第一种有些类似
给表中的这个ids
字段建立各种索引,方便查询,但ES
主要用处是在更复杂的json
格式数据上
颇有种杀鸡用牛刀的感觉了。