首页 > 数据库 >SQL优化汇总

SQL优化汇总

时间:2023-07-30 21:00:46浏览次数:29  
标签:CREATED MAX 汇总 T1 索引 SQL NULL 优化

IS NOT NULL的优化

1. 问题提出

客户系统有这样一条SQL,脱敏后如下:

SELECT NVL(MAX(T1.CREATED),SYSDATE) FROM DUAL LEFT JOIN TEST11 T1
ON T1.OWNER=’OUTLN’ AND OBJECT_TYPE IS NOT NULL;

SQL是TEST11表和DUAL表相关联,WHERE条件中OWNER字段有索引,SQL走了该字段索引范围扫描的执行计划,单次执行逻辑读2117。SQL执行频率非常高,一分钟数万次。执行计划如下:

SQL优化IS NOT NULL和IS NULL_SQL

2. 初步优化

WHERE条件有两个【OWNER=’OUTLN’】和【OBJECT_TYPE IS NOT NULL】,查询取出来的字段是CREATED,考虑创建OWNER+OBJECT_TYPE+CREATED三列联合索引,可以消除回表的成本,创建索引后逻辑读由2117降为82。执行计划如下:

SQL优化IS NOT NULL和IS NULL_SQL优化_02

3. 极致优化探究 – 索引原理

继续分析该SQL, 发现其实从逻辑上来说,SQL仅需要时间列CREATED的最小值,至于其他值是什么并不重要。那么是否有一种方法可以只取出最小值,而忽略掉其他数据呢? 如果可以做到那么逻辑读就会进一步降低。

考虑一下索引的结构:索引由根节点块(root block)、枝块(branch block)和叶子块(leaf block)组成,索引的数据在叶子块里是顺序排列的。也就是说最小值的数据会保存在索引块的最小那一端。理论上来说,完全可以从叶子块的其中一段取一个块,就可以得到特定索引的最小值。

SQL优化IS NOT NULL和IS NULL_执行计划_03

4. 简化版取min/max索引优化

为了更好理解,我们把问题简化成取表里CREATED最小值(或者最大值)。
需要取得TEST11表CREATED的最大/最小值:

SELECT MAX(CREATED) FROM TEST11;

假设存在CREATED字段的索引,那么完全可以只取叶子块的最靠边的一个块,就能得到所需要的的值。

下面做一个测试,创建一个测试表:

create table test11 tablespace DATA_TS as select * from dba_objects where rownum <1000;

begin 
for i in 0..10 loop
insert /*+append */into test11 select * from test11;
commit;
end loop;
end;
/

创建一个CREATED的索引,然后运行之前简化的SQL。根据索引原理可以判断应该需要3-4个逻辑读:分别是Root节点开始–>找最右边的Branch(可能是0-2个,根据索引的层级)–>再找到最右边的Leaf Block。

执行如下,结果和我们之前设想的一样,执行计划走的是INDEX FULL SCAN(MIN/MAX)。

SQL优化IS NOT NULL和IS NULL_SQL_04

设想稍微复杂一点场景:假设需要得到的是符合指定的条件的最大CREATED值呢?
如果我们需要取的是符合OWNER = 'OUTLN’的最大CREATED值。SQL如下:

SELECT MAX(CREATED) FROM TEST11 WHERE OWNER = 'OUTLN';

如果存在(OWNER,CREATED)组合索引,数据库就可以利用类似的方法只取其中一个叶子节点。执行计划走的是INDEX RANGE SCAN(MIN/MAX),逻辑读是3:

SQL优化IS NOT NULL和IS NULL_执行计划_05

那么假如是SELECT MAX(CREATED) FROM TEST11 WHERE OWNER= ‘OUTLN’ AND OBJECT_TYPE =‘TABLE’ ,就需要新的索引(OWNER,OBJECT_TYPE,CREATED)来完成同样的动作。执行计划走的是INDEX RANGE SCAN(MIN/MAX),需要3个逻辑读:

SQL优化IS NOT NULL和IS NULL_最小值_06

也就是说,++必须条件中的列和索引前导列完全匹配++,然后取的索引里最后一列的MAX/MIN值,它就可以走最优的INDEX RANGE SCAN (MIN/MAX)索引。

5. 转化IS NOT NULL条件到索引中

回到原始的SQL:

SELECT NVL(MAX(T1.CREATED),SYSDATE) FROM DUAL LEFT JOIN TEST11 T1
ON T1.OWNER=’OUTLN’AND OBJECT_TYPE IS NOT NULL;

原始SQL中除了OWNER=’OUTLN’,还有一个OBJECT_TYPE IS NOT NULL,直接创建 (OWNER,OBJECT_TYPE,CREATED)联合索引消耗逻辑读82,并不能达到最优的效果,因为OBJECT_TYPE IS NOT NULL不能对应到索引的一个特定值,执行计划如下:

SQL优化IS NOT NULL和IS NULL_SQL_07

我们需要把OBJECT_TYPE IS NOT NULL的条件固化成一个特定的值,这样就可以匹配索引中的特定值了。SQL需要改写一下:

1) 去掉DUAL, 并不会影响结果集:

SELECT NVL(MAX(T1.CREATED),SYSDATE) FROM TEST11 T1
WHERE T1.OWNER=’OUTLN’AND OBJECT_TYPE IS NOT NULL;

2) 把OBJECT_TYPE IS NOT NULL变成一个特定值,这里用了CASE WHEN,SQL变成:

SELECT MAX(CREATED) FROM TEST11
WHERE OWNER ='OUTLN'
AND CASE WHEN OBJECT_TYPE IS NOT NULL THEN 1 END = 1;

3) 创建一个函数索引。也就是说OBJECT_TYPE IS NOT NULL的记录在索引中存储为1。如下:

CREATE INDEX IDX_TEST11_MAX ON TEST11(OWNER,CASE WHEN OBJECT_TYPE IS NOT NULL THEN 1 END,CREATED) PARALLEL 4 NOLOGGING;
ALTER INDEX IDX_TEST11_MAX NOPARALLEL;

此时执行计划如下,逻辑读降为3:

SQL优化IS NOT NULL和IS NULL_SQL优化_08

6. 优化结果

为生产SQL创建CASE WHEN索引并改写SQL后逻辑读降为75,并没有走上最优的INDEX RANGE SCAN (MIN/MAX)执行计划,这是因为这种写法MAX(T1.CREATED)被放在最外层,也就是说对MAX的取值是基于关联后的结果集而不是基于TEST11表。

SQL优化IS NOT NULL和IS NULL_执行计划_09

SQL是TEST11和DUAL做关联,取NVL(MAX(T1.CREATED),SYSDATE)的值,即使没有数据也会返回SYSDATE的值,去掉DUAL表不影响,去掉DUAL表后SQL逻辑读降为3:

SQL优化IS NOT NULL和IS NULL_SQL_10

最终完成了一个高频SQL从2117到3的极致优化,提升达数百倍。

IS NULL的优化

系统中还发现另一个SQL, 脱敏后SQL如下:

UPDATE TEST T
SET T.TCODE = (SELECT T1.TCODE FROM TEST1 T1 WHERE T.SCODE = T1.SCODE)
WHERE T.TCODE IS NULL
AND EXISTS (SELECT 1 FROM TEST1 T2 WHERE T.SCODE = T2.SCODE)

SQL优化IS NOT NULL和IS NULL_最大值_11

SQL走的是全表扫描的执行计划,TEST表数据量1100万,每次更新数据0条。执行计划最慢的步骤是ID=3的TEST表全表扫描步骤。WHERE条件中TCODE IS NULL可以过滤掉所有数据,但是我们知道NULL值是不存在索引中的,一般情况下IS NULL只能走全表,如果全表数据非常多SQL性能就会很差。和IS NOT NULL同样的思路,能不能让这部分过滤性非常好的NULL值存在索引中呢?

SElECT * FROM TEST WHERE TCODE IS NULL;

no rows selected

也就是换个思路,用个函数把NULL值转为一个表中该字段不存在的固定值(假设0),非NULL值的转为NULL。这样索引中存储的就是原先的NULL值,也就是经过函数转换后的0,这样可以保证索引最小化,因为原本IS NOT NULL的数据不存在其中。
转换如下:CODE IS NULL <==> CASE WHEN TCODE IS NULL THEN 0 END

使用CASE WHEN而不使用NVL(TCODE,0)函数是为了将索引最小化,因为原有的TCODE存在的值不用保存到索引中,同时也不用考虑NVL之后可能和原有的值相同的情况。

创建函数索引并修改原SQL,逻辑读由原来的80多万降为1:

CREATE INDEX IDX_YHEMTEST ON TEST(CASE WHEN TCODE IS NULL THEN 0 END);

UPDATE TEST T
   SET T.TCODE = (SELECT T1.TCODE FROM TEST1 T1 WHERE T.SCODE = T1.SCODE)
 WHERE CASE WHEN T.TCODE IS NULL THEN 0 END = 0
   AND EXISTS (SELECT 1 FROM TEST1 T2 WHERE T.SCODE = T2.SCODE)

SQL优化IS NOT NULL和IS NULL_执行计划_12

经优化,该SQL性能提升数万倍。

标签:CREATED,MAX,汇总,T1,索引,SQL,NULL,优化
From: https://blog.51cto.com/u_13482808/6902666

相关文章

  • 崩铁7属性主题色颜色代码汇总
    参考:角色属性命途一览属性文字HEXRGBHSVHSL物理#7f7f7frgb(127,127,127)hsv(0,0%,50%)hsl(0,0%,50%)火#ed453crgb(237,69,60)hsv(3,75%,93%) hsl(3,83%,58%)冰#2592d2rgb(37,146,210)hsv(202,82%,82%)hsl(202,70%,48%)雷......
  • requestAnimationFrame优化动画
    requestAnimationFrame优化动画总结:requestAnimationFrame与setInterval的区别setInterval是在任务队列里执行的,也就是说上一帧没有执行完下一帧不可能执行。而requestAnimationFrame是在差异队列里执行的,也就是说没有延迟。requestAnimationFrame可以准时执行每一帧<!......
  • mysql 简单进阶 ———— 重构查询[二]
    前言简单整理一下重构查询。正文为什么我们需要重构查询,原因也很简单,那就是查询慢。为什么会查询慢?查询性能慢底下的最基本的原因是访问的数据太多。某些查询不可避免地需要筛选大量的数据,但这并不常见。大部分性能低下的查询都可以通过减少访问的数据流的方式进行优化。......
  • SQLite3基本操作
    目录SQLite3基本操作SQLite3安装启动和退出表关键字函数SQLite3基本操作SQLite3SQLite,是一款轻型的数据库,是遵守ACID的关系型数据库管理系统,它包含在一个相对小的C库中。它是D.RichardHipp建立的公有领域项目。它的设计目标是嵌入式的,而且已经在很多嵌入式产品中使用了它,它占用......
  • MySQL使用binlog恢复数据
    1.什么是BinlogMySQL的二进制日志(Binlog)是一种事务日志,用于记录对数据库的更改操作。Binlog主要用于MySQL复制和恢复:复制:从库通过拉取主库的binlog实现主从数据一致恢复:通过重放binlog恢复数据丢失或误操作情况1.1.Binlog的工作原理在MySQL中,每个事务都会在提交后......
  • 【笔记】DP 优化(WIP)
    7.30DP凸相关决策单调性、斜率优化、凸、四边形不等式,都是凸相关。前置知识四边形不等式:交叉小于包含。\(l_1<l_2<r_1<r_2\tow(l_1,r_1)+w(l_2,r_2)\leqw(l_1,r_2)+w(l_2,r_1)\)。区间包含单调性:包含区间值单调。\(l_1<l_2<r_2<l_1\tow(l_1,r_1)\geqw(l_2,r_2)\)。满足......
  • 通过Redis+Mysql来自定义Spring-Statemachine的持久化
    我们在使用Spring状态机的时候,往往需要对于StateMachine持久化操作,但是官方为我们提供的基于redis的持久化并不是特别好,一方面是因为只存redis容易导致数据丢失,另一方面因为状态机的特性需要对应的StateMachine的数据永久有效,导致redis中的key永不过期。我现在希望实现将StateMac......
  • Postgresql 在Ubuntuserver 22.04上部署
    安装与卸载系统环境:1.ubuntu22.04server安装1.检查是否已经安装#psql服务sudoservicepostgresqlstatus#版本查看psql--version2.安装命令#更新安装源内容sudoapt-getupdate#postgresql-contrib额外特性安装sudoaptinstallpostgresqlpostgresql......
  • sql语句
    SQL通用语法SQL语句可以单行或多行书写,以分号(“;”)结尾。SQL语句可以使用空格/缩进来增强语句的可读性。MySQL数据库的SQL语句不区分大小写,关键字建议使用大写。注释:单行注释:--注释内容或#注释内容(MySQL特有)多行注释:/*注释内容*/SQL分类分类全称说明......
  • 索引优化
    联合索引第一个字段如果是范围查询则不会走索引如果第一个查询条件用范围查询,那么MySQL会以为你查询的表数据比较多,那么它就会进行全表扫描而不进行索引,,但是如果第一个查询条件是主键范围查询的话,它依旧会走索引,不过在开发中应该不需要进行主键范围查询。主键范围查询EXPLAINS......