[20250718]WINDOW SORT PUSHED RANK Performance Regression in Oracle 21c.txt

[20250718]WINDOW SORT PUSHED RANK Performance Regression in Oracle 21c.txt

https://nenadnoveljic.com/blog/window-sort-pushed-rank-performance-regression-in-oracle-21c/

--//重复测试看看。

1.环境:
SCOTT@book01p> @ver2
==============================
PORT_STRING                   : x86_64/Linux 2.4.xx
VERSION                       : 21.0.0.0.0
BANNER                        : Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
BANNER_FULL                   : Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
Version 21.3.0.0.0
BANNER_LEGACY                 : Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
CON_ID                        : 0
PL/SQL procedure successfully completed.

2.测试例子建立:
create table t1 (n1 number, c1 varchar2(10)) ;
create table t2 (n1 number, c1 varchar2(10), n2 number) ;

insert into t1 select level, null from dual connect by level <= 5*1e5 ;
insert into t2 select 5*1e5 + level, 'X', 1 from dual connect by level <= 4*1e5 ;
commit ;

exec dbms_stats.gather_table_stats(null, 'T1');
exec dbms_stats.gather_table_stats(null, 'T2');

$ cat g8.txt
merge /*+ gather_plan_statistics monitor */ --nenad
into t1 using
( select n1,c1 from
  ( select n1,c1,
      row_number() over (partition by n1 order by n2 desc) rn
        from t2
  ) where rn = 1
) v1
on ( v1.n1 = t1.n1 )
when matched then
update set t1.c1 = v1.c1 ;

3.测试:
SCOTT@book01p> set timing on
SCOTT@book01p> @ g8.txt
0 rows merged.
Elapsed: 00:02:32.95
--//差不多150秒。

SCOTT@book01p> @ dpc '' outline ''
PLAN_TABLE_OUTPUT
-------------------------------------
SQL_ID  2r8rr3pht5cvn, child number 0
-------------------------------------
merge /*+ gather_plan_statistics monitor */ --nenad into t1 using (
select n1,c1 from   ( select n1,c1,       row_number() over (partition
by n1 order by n2 desc) rn  from t2   ) where rn = 1 ) v1 on ( v1.n1 =
t1.n1 ) when matched then update set t1.c1 = v1.c1
Plan hash value: 3917951292
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                   | Name | Starts | E-Rows |E-Bytes|E-Temp | Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | MERGE STATEMENT             |      |      1 |        |       |       |  3356 (100)|          |      0 |00:02:32.95 |    1683 |       |       |          |
|   1 |  MERGE                      | T1   |      1 |        |       |       |            |          |      0 |00:02:32.95 |    1683 |       |       |          |
|   2 |   VIEW                      |      |      1 |        |       |       |            |          |      0 |00:02:32.95 |    1683 |       |       |          |
|*  3 |    HASH JOIN                |      |      1 |      1 |    38 |  8304K|  3356   (2)| 00:00:01 |      0 |00:02:32.95 |    1683 |    33M|  8026K|   32M (0)|
|   4 |     TABLE ACCESS FULL       | T1   |      1 |    500K|  2441K|       |   243   (3)| 00:00:01 |    500K|00:00:00.02 |     805 |       |       |          |
|*  5 |     VIEW                    |      |      1 |    400K|    12M|       |  1850   (2)| 00:00:01 |    400K|00:02:33.14 |     878 |       |       |          |
|*  6 |      WINDOW SORT PUSHED RANK|      |      1 |    400K|  3906K|  7856K|  1850   (2)| 00:00:01 |    400K|00:02:32.93 |     878 |    25M|  1830K|   22M (0)|
|   7 |       TABLE ACCESS FULL     | T2   |      1 |    400K|  3906K|       |   243   (3)| 00:00:01 |    400K|00:00:00.01 |     878 |       |       |          |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
   1 - MRG$1
   3 - SEL$F5BB74E1
   4 - SEL$F5BB74E1 / "T1"@"SEL$1"
   5 - SEL$3        / "from$_subquery$_007"@"SEL$2"
   6 - SEL$3
   7 - SEL$3        / "T2"@"SEL$3"
Outline Data
-------------

  /*+
      BEGIN_OUTLINE_DATA
      IGNORE_OPTIM_EMBEDDED_HINTS
      OPTIMIZER_FEATURES_ENABLE('21.1.0')
      DB_VERSION('21.1.0')
      ALL_ROWS
      OUTLINE_LEAF(@"SEL$3")
      OUTLINE_LEAF(@"SEL$F5BB74E1")
      MERGE(@"SEL$2" >"SEL$1")
      OUTLINE_LEAF(@"SEL$5")
      OUTLINE_LEAF(@"SEL$4")
      OUTLINE_LEAF(@"MRG$1")
      OUTLINE(@"SEL$1")
      OUTLINE(@"SEL$2")
      NO_ACCESS(@"MRG$1" "from$_subquery$_009"@"MRG$1")
      NO_ACCESS(@"MRG$1" "V1"@"MRG$1")
      FULL(@"MRG$1" "T1"@"MRG$1")
      LEADING(@"MRG$1" "from$_subquery$_009"@"MRG$1" "V1"@"MRG$1" "T1"@"MRG$1")
      USE_MERGE_CARTESIAN(@"MRG$1" "V1"@"MRG$1")
      USE_MERGE_CARTESIAN(@"MRG$1" "T1"@"MRG$1")
      NO_ACCESS(@"SEL$F5BB74E1" "from$_subquery$_007"@"SEL$2")
      FULL(@"SEL$F5BB74E1" "T1"@"SEL$1")
      LEADING(@"SEL$F5BB74E1" "from$_subquery$_007"@"SEL$2" "T1"@"SEL$1")
      USE_HASH(@"SEL$F5BB74E1" "T1"@"SEL$1")
      SWAP_JOIN_INPUTS(@"SEL$F5BB74E1" "T1"@"SEL$1")
      NO_ACCESS(@"SEL$4" "from$_subquery$_004"@"SEL$4")
      FULL(@"SEL$5" "T2"@"SEL$5")
      FULL(@"SEL$3" "T2"@"SEL$3")
      END_OUTLINE_DATA
  */
Predicate Information (identified by operation id):
---------------------------------------------------
   3 - access("N1"="T1"."N1")
   5 - filter("RN"=1)
   6 - filter(ROW_NUMBER() OVER ( PARTITION BY "N1" ORDER BY INTERNAL_FUNCTION("N2") DESC )<=1)
73 rows selected.
--//时间在id=6上。

SCOTT@book01p> @ dpccpu 2r8rr3pht5cvn 0
PLAN_TABLE_OUTPUT
-------------------------------------
SQL_ID  2r8rr3pht5cvn, child number 0
-------------------------------------
merge /*+ gather_plan_statistics monitor */ --nenad into t1 using (
select n1,c1 from   ( select n1,c1,       row_number() over (partition
by n1 order by n2 desc) rn  from t2   ) where rn = 1 ) v1 on ( v1.n1 =
t1.n1 ) when matched then update set t1.c1 = v1.c1
Plan hash value: 3917951292
--------------------------------------------------------------------------------------------
| Id  | Operation                   | Name | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     | %ASH SAMPLES
--------------------------------------------------------------------------------------------
|   0 | MERGE STATEMENT             |      |       |       |       |  3356 (100)|          |
|   1 |  MERGE                      | T1   |       |       |       |            |          |
|   2 |   VIEW                      |      |       |       |       |            |          |
|*  3 |    HASH JOIN                |      |     1 |    38 |  8304K|  3356   (2)| 00:00:01 |   1% (   1% CPU   0% I/O)
|   4 |     TABLE ACCESS FULL       | T1   |   500K|  2441K|       |   243   (3)| 00:00:01 |
|*  5 |     VIEW                    |      |   400K|    12M|       |  1850   (2)| 00:00:01 |
|*  6 |      WINDOW SORT PUSHED RANK|      |   400K|  3906K|  7856K|  1850   (2)| 00:00:01 |  99% (  99% CPU   0% I/O)
|   7 |       TABLE ACCESS FULL     | T2   |   400K|  3906K|       |   243   (3)| 00:00:01 |
--------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   3 - access("N1"="T1"."N1")
   5 - filter("RN"=1)
   6 - filter(ROW_NUMBER() OVER ( PARTITION BY "N1" ORDER BY
              INTERNAL_FUNCTION("N2") DESC )<=1)
--//99%的CPU资源在id=6。

--//作者通过bpftrace定位smbgetqbPart频繁调用800001次。而19c调用0.
--//通过pstack观察也可以同样定位:
$ seq 10000 | xargs -IQ pstack 6849 >| gg.txt
--//注:我的测试环境密集执行pstack,按ctrl+c中断后,相关进程很容易出现挂起情况,不知道原因。

--//sql执行结束按ctrl+c中断pstack的执行。
 awk '/#0/{print $4}' gg.txt | sort  |uniq -c | sort -nr
    226 smbgetqbPart
      3 __read_nocancel
      1 rwosmf
      1 qerstUpdateStats
      1 expeal
--//大部分调用函数smbgetqbPart。

$ pstack 6849
#0  0x000000000e250f7d in smbgetqbPart ()
#1  0x000000001500e027 in sorgetqbf ()
#2  0x0000000004d9b504 in qerwnFetch ()
#3  0x000000000a261506 in qerstFetch ()
#4  0x0000000004cd1903 in qervwFetch ()
#5  0x000000000a261506 in qerstFetch ()
#6  0x0000000014dfbda7 in rwsfcd ()
#7  0x000000000a261506 in qerstFetch ()
#8  0x000000001516b8a5 in qerhnFetch ()
#9  0x000000000a261506 in qerstFetch ()
#10 0x0000000004cd1903 in qervwFetch ()
#11 0x000000000a261506 in qerstFetch ()
#12 0x0000000014dfbda7 in rwsfcd ()
#13 0x000000000a261506 in qerstFetch ()
#14 0x0000000004dd99dd in qerusFetch ()
#15 0x000000000a261506 in qerstFetch ()
#16 0x0000000014ffbf49 in updaul ()
#17 0x0000000014ff8f90 in updThreePhaseExe ()
#18 0x00000000041ab3e0 in upsexe ()
#19 0x0000000014ef6ab5 in opiexe ()
#20 0x0000000014f6cf9d in kpoal8 ()
#21 0x0000000014eefdf8 in opiodr ()
#22 0x0000000015294359 in ttcpip ()
#23 0x00000000030025c2 in opitsk ()
#24 0x0000000003007920 in opiino ()
#25 0x0000000014eefdf8 in opiodr ()
#26 0x0000000002ffe2ab in opidrv ()
#27 0x0000000003e8bd15 in sou2o ()
#28 0x0000000000e91ae0 in opimai_real ()
#29 0x0000000003e9915c in ssthrdmain ()
#30 0x0000000000e91924 in main ()

SCOTT@book01p> @ fix "PUSHED RANK"
SCOTT@book01p>  @pr
==============================
SESSION_ID                    : 147
BUGNO                         : 30822446
VALUE                         : 1
SQL_FEATURE                   : QKSFM_EXECUTION_30822446
DESCRIPTION                   : enable window pushed rank optimization with partition by keys
OPTIMIZER_FEATURE_ENABLE      : 21.1.0
EVENT                         : 0
IS_DEFAULT                    : 1
CON_ID                        : 3
PL/SQL procedure successfully completed.

SCOTT@book01p> alter session set "_FIX_CONTROL"='30822446:OFF'  ;
Session altered.

SCOTT@book01p> @ g8.txt
0 rows merged.
Elapsed: 00:00:00.60

SCOTT@book01p> @ dpc '' '' ''
PLAN_TABLE_OUTPUT
-------------------------------------
SQL_ID  2r8rr3pht5cvn, child number 1
-------------------------------------
merge /*+ gather_plan_statistics monitor */ --nenad into t1 using (
select n1,c1 from   ( select n1,c1,       row_number() over (partition
by n1 order by n2 desc) rn  from t2   ) where rn = 1 ) v1 on ( v1.n1 =
t1.n1 ) when matched then update set t1.c1 = v1.c1
Plan hash value: 3917951292
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                   | Name | Starts | E-Rows |E-Bytes|E-Temp | Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | MERGE STATEMENT             |      |      1 |        |       |       |  3356 (100)|          |      0 |00:00:00.56 |    1683 |       |       |          |
|   1 |  MERGE                      | T1   |      1 |        |       |       |            |          |      0 |00:00:00.56 |    1683 |       |       |          |
|   2 |   VIEW                      |      |      1 |        |       |       |            |          |      0 |00:00:00.56 |    1683 |       |       |          |
|*  3 |    HASH JOIN                |      |      1 |      1 |    38 |  8304K|  3356   (2)| 00:00:01 |      0 |00:00:00.56 |    1683 |    33M|  8026K|   32M (0)|
|   4 |     TABLE ACCESS FULL       | T1   |      1 |    500K|  2441K|       |   243   (3)| 00:00:01 |    500K|00:00:00.02 |     805 |       |       |          |
|*  5 |     VIEW                    |      |      1 |    400K|    12M|       |  1850   (2)| 00:00:01 |    400K|00:00:00.56 |     878 |       |       |          |
|*  6 |      WINDOW SORT PUSHED RANK|      |      1 |    400K|  3906K|  7856K|  1850   (2)| 00:00:01 |    400K|00:00:00.46 |     878 |    25M|  1830K|   22M (0)|
|   7 |       TABLE ACCESS FULL     | T2   |      1 |    400K|  3906K|       |   243   (3)| 00:00:01 |    400K|00:00:00.01 |     878 |       |       |          |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
   1 - MRG$1
   3 - SEL$F5BB74E1
   4 - SEL$F5BB74E1 / "T1"@"SEL$1"
   5 - SEL$3        / "from$_subquery$_007"@"SEL$2"
   6 - SEL$3
   7 - SEL$3        / "T2"@"SEL$3"
Predicate Information (identified by operation id):
---------------------------------------------------
   3 - access("N1"="T1"."N1")
   5 - filter("RN"=1)
   6 - filter(ROW_NUMBER() OVER ( PARTITION BY "N1" ORDER BY INTERNAL_FUNCTION("N2") DESC )<=1)
--//逻辑读一样。执行计划一样。

4.附上测试使用dpccpu.sql脚本:

$ cat dpccpu.sql
with
 "sql" as (select SQL_ID,CHILD_NUMBER,PLAN_HASH_VALUE,'' FORMAT from v$sql where sql_id='&1'),
 "ash" as (
          select sql_id,sql_plan_line_id,child_number,sql_plan_hash_value
          ,round(count(*)/"samples",2) load
          ,nvl(round(sum(case when session_state='ON CPU' then 1 end)/"samples",2),0) load_cpu
          ,nvl(round(sum(case when session_state='WAITING' and wait_class='User I/O' then 1 end)/"samples",2),0) load_io
          from "sql" join
          (
            select sql_id,sql_plan_line_id,sql_child_number child_number,sql_plan_hash_value,session_state,wait_class,count(*) over (partition by sql_id,sql_plan_hash_value) "samples"
            FROM V$ACTIVE_SESSION_HISTORY
          ) using(sql_id,child_number) group by sql_id,sql_plan_line_id,child_number,sql_plan_hash_value,"samples"
 ),
 "plan" as (
        -- get dbms_xplan result
        select
         sql_id,child_number,n,plan_table_output
         -- get plan line id from plan_table output
         ,case when regexp_like (plan_table_output,'^[|][*]? *([0-9]+) *[|].*[|]$') then
          regexp_replace(plan_table_output,'^[|][*]? *([0-9]+) *[|].*[|]$','\1')
          END SQL_PLAN_LINE_ID
         from (select rownum n,plan_table_output,SQL_ID,CHILD_NUMBER from "sql", table(dbms_xplan.display_cursor("sql".SQL_ID,"sql".CHILD_NUMBER,"sql".FORMAT)))
 )
select PLAN_TABLE_OUTPUT||CASE
       -- ASH load to be displayed
       WHEN LOAD >0 THEN TO_CHAR(100*LOAD,'999')||'% (' || TO_CHAR(100*LOAD_CPU,'999')||'% CPU'|| TO_CHAR(100*LOAD_IO,'999')||'% I/O)'
       -- header
       WHEN REGEXP_LIKE (PLAN_TABLE_OUTPUT,'^[|] *Id *[|]')  THEN ' %ASH SAMPLES'
       end plan_table_output
from "plan" left outer join "ash" using(sql_id,child_number,sql_plan_line_id) order by sql_id,child_number,n;

posted @ 2025-07-18 21:18  lfree  阅读(15)  评论(0)    收藏  举报