Oracle IN中动态条件的优化记录

早晨起来晨练,面向大海时,突发感想,想着开始记录些东西了,于是乎第一篇文章随即出现在脑海中。

问题背景

在上家公司,数据库随着业务的不断增长,数据库的瓶颈越来越明显;
有两个sql语句,由于业务逻辑的问题,需要动态拼接查询条件;
早期业务量不多,交易不大的情况下,并未产生问题,但随着业务量的上升,两个语句的执行频率也不断增加,数据库频繁的发现有较大数量的“硬解析”;
DBA的同事进行分析awr报告,发现硬解析大量出现在上述的两个语句中;
简单的模拟了下这两个语句的:

select * from user where id in (1, 5, 8)
select * from user where id not in (1, 5, 8)

这两个语句是通过in条件进行拼接的,并且in中的条件是动态变化的,即其中的参数可能是3个,也可能是4个甚至更多;

系统代码实现:
采用Mybatis的方式,生成对应的sql语句进行执行,这段代码在Mapper.xml中使用了$的拼接符 - ${str}
Mybatis的"$"官方说明:

默认情况下,使用 #{} 参数语法时,MyBatis 会创建 PreparedStatement 参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)。 这样做更安全,更迅速,通常也是首选做法,不过有时你就是想直接在 SQL 语句中直接插入一个不转义的字符串。

解决方案

为了能解决这种由于“动态变化”的业务需求造成的硬解析问题,一开始想的办法是全量的查询,通过java内部缓存筛选的方式,将符合(或不符合)的条件筛选(或过滤),这虽然可以解决硬解析问题,但同样带来了而外的与数据库之间的I/O交互;
并且我们的用户主表的数据量在千万级以上,上述的方法,在数据分布不均匀的情况下,会拖垮整个系统。

通过查询和学习,找到更加方便的改进方式,通过使用regexp_substr的oracle语句,可以有效的解决上述问题
模拟脚本如下:

SELECT
    regexp_substr ( : userIDs, '[^,]+', 1, LEVEL ) token 
FROM
    DUAL CONNECT BY LEVEL <= length( : userIDs ) - length( REPLACE ( : userIDs, ',', '' ) ) + 1

注意:该方法有字符串长度的限制,只允许4000字符串长度;

总结

代码中的语句尽量避免硬解析,特别是针对于高频的语句,大量硬解析会损耗过多的DB资源,如果放任自流,最终可能会成为压死系统的导火索。

你可能感兴趣的:(oracle,mybatis)