vlambda博客
学习文章列表

把MySql当作列存数据库使用时多条件and查询如何实现?

Java艺术
坚持技术分享、坚持原创、深耕后端架构、探索底层实现原理,关注Java艺术,我们在架构师道路上一起成长!
135篇原创内容
Official Account
可能是为了优化查询性能,也可能是做大数据的惯性思维,才会把 MySql数据库当分析型数仓去使用吧。
本篇分享的内容是:当我们把 MySql当作列存数据库( OLAP分析型数仓)使用时多条件 and查询如何实现。
假设某个表有 n列,那么把一行记录按列存储后就需要存约 n-1行记录。同时,转为列存后每行都需要多出几列标记这一条记录属于哪个“行记录”,以及存储的是“行记录”哪一列的值。
以用户表为例,假设 tb_row_user表结构如下。
id
name
sex
age




那么转为列存后创建的表 tb_column_user结构如下。
id
user_id
column_name
column_value




现在我们往表 tb_row_user里面插入一条记录。
id
name
sex
age
1
就业

26
如果使用列存,则需要往表 tb_column_user 插入如下 3 条记录。
id
user_id
column_name
column_value
1
1
name
就业
2
1
sex

3
1
age
26
当然,我们还需要为列 user_id column_name column_value 分别创建索引。
现在我们需要查询同时满足年龄大于 25岁且性别为男的所有用户的 id,我们应该怎样写 sql?现在条件只有两个,假如条件有 5个, 10个呢?
下面是笔者首先想到的一种方法:
 
   
   
 
select user_id, count(id) as cnt
from tb_column_user where
(column_name = 'age' and column_value > '25')
or
(column_name = 'sex' and column_value = '男')
group by user_id having cnt = 2;
如果把 group by去掉,那么 sql就是查询条件满足 age大于 25或者性别是男的用户,即。
 
   
   
 
select user_id from tb_column_user where
(column_name = 'age' and column_value > '25')
or
(column_name = 'sex' and column_value = '男');
如果一个用户同时满足这两个条件,那么查询结果同个 user_id就会有两条记录,否则如果只满足其中一个条件,那么查询结果这个 user_id就只会有一条记录。比如:
user_id
1(匹配年龄 >25 )
1(匹配性别 =男 )
所以我们按 user_id分组统计得到的 cnt就是匹配的条件个数,最后通过 having就可以筛选出同时满足两个条件( having cnt = 2)的用户的 id
这种方法的 sql更便于动态拼接,不过使用这个 sql有个前提条件,要求 tb_row_user表里面的每一行记录的每一列对应到 tb_column_user表都只有一行记录(也可以没有)。
最终的 SQL的执行计划如下:
把MySql当作列存数据库使用时多条件and查询如何实现?
由于使用了 OR查询,因此 Typeindex,即全索引扫描(遍历索引树),因此性能会随着数据量的增加而越来越慢。
另一种方法,使用 join查询, sql如下:
 
   
   
 
select u1.user_id from tb_column_user as u1 inner join (
select user_id from tb_column_user where column_name = 'sex' and column_value = '男'
) as u2
where u1.user_id =u2.user_id and u1.column_name = 'age' and u1.column_value > '25';
这条 sql的执行计划如下。
如果再加一个条件 sql就会看得很头疼了:
 
   
   
 
select u1.user_id from tb_column_user as u1 inner join (
select uu1.user_id from tb_column_user uu1 inner join(
select user_id from tb_column_user where column_name = 'tag' and column_value = '穷'
)uu2
where uu1.user_id =uu2.user_id and uu1.column_name = 'sex' and uu1.column_value = '男'
) as u2
where u1.user_id =u2.user_id and u1.column_name = 'age' and u1.column_value > '25';
执行计划如下。
可以看出,使用 join查询每个查询都能用到索引,并且外层查询扫描的行数总比内层查询扫描的行数少,经过了层层过滤,最终扫描的行数就是最内层的查询语句所扫描的行数。

总结

方法一:优点是 sql清晰易读,缺点是性能差;
方法二:优点是性能相对更好,但缺点是条件越多 sql看着越难理解。
是否有更好的方法,欢迎留言你的观点!