数据库版本升级都会强烈建议功能和性能测试,但有时还是不具备这样的条件或未测试全面, 对于版本上线后的问题再见招拆招。最近遇到了一个11.2.0.3 升级 12.2 后有个存储过程无法执行,提示“ORA-00979: not a GROUP BY expression” 错误,显然是个SQL语法的问题。那很可能是哪个优化器新特性在查询转换过程中出现问题,下面还原这个问题, 和介绍一种简单粗暴的方法。
构建表结构,还原错误
create table anbob.test_t1(minbillcycle varchar2(10));
insert into anbob.test_t1 select to_char(sysdate,'yyyymm') from dual;
create table anbob.test_t2 as select * from anbob.test_t1 where 1=0;
SQL> declare
cycle# number :=201911;
aa number;
begin
SELECT 1 into aa
FROM(
select months_between(to_date(CYCLE#,'yyyymm'),to_date(minbillcycle,'yyyymm')) DEBTTIME,count(distinct minbillcycle) DEBTSUBS
FROM anbob.test_t1
GROUP BY months_between(to_date(CYCLE#,'yyyymm'),to_date(minbillcycle,'yyyymm'))
union all
SELECT 0 DEBTTIME, 0 DEBTSUBS
FROM anbob.test_t2 )
COMMIT;
end;
/
declare
*
ERROR at line 1:
ORA-00979: not a GROUP BY expression
ORA-06512: at line 5
Note:
特殊的地方这是一个变量聚合汇总子查询。
方法1
对上面的SQL,我们可以启用10053 event trace 分析查询转换后的SQL, 有一种测试方法就是session 级修改optimizer_features_enable的版本, 其实经过测试发现降到10.2.0.5时,这个PL/SQL 就不在报错,判断应该是11G时引入的一个新特性(聚合参数转换),很可能在原来的11G库时是禁用了某个优化器参数,而12c中没有禁用。
SQL> alter session set optimizer_features_enable='10.2.0.5'; Session altered. SQL> declare 2 cycle# number :=201911; 3 aa number; 4 begin 5 SELECT 1 into aa 6 FROM( 7 select months_between(to_date(CYCLE#,'yyyymm'),to_date(minbillcycle,'yyyymm')) DEBTTIME,count(distinct minbillcycle) DEBTSUBS 8 FROM system.test_t1 9 GROUP BY months_between(to_date(CYCLE#,'yyyymm'),to_date(minbillcycle,'yyyymm')) 10 union all 11 SELECT 0 DEBTTIME, 0 DEBTSUBS 12 FROM system.test_t2 ) 13 COMMIT; 14 end; 15 / PL/SQL procedure successfully completed.
方法2
修改optimizer_features_enable参数的影响范围太大, 那缩小到最小的优化器特性是一种不错的选择,优化器相关的那么多参数,我们人工尝试太耗费时间, 下面就写一个PLSQL 遍历所有的SESSION 级优化器参数,改变现在的值,暴力找出修改某参数可以解决当前的问题。
(基于x$qksceses、x$ksppi、x$ksppcv、x$kspvld_values、v$session_fix_control)
1, 生成测试脚本 sql_test.sql
-- file: params_test.sql
-- author: anbob.com
set serveroutput on
DECLARE
l_unique_id VARCHAR2(200);
l_test_id NUMBER := 0;
l_test_id_rp_i NUMBER := 0;
l_spoolfile_name_p VARCHAR2(100);
l_spoolfile_name_vs VARCHAR2(100);
l_spoolfile_name_rp_i_p VARCHAR2(100);
l_spoolfile_name_rp_i_vs VARCHAR2(100);
l_alter_session VARCHAR2(4000);
l_alter_session_bck VARCHAR2(4000);
l_skip_string_script VARCHAR2(4000);
l_skip_string_driver VARCHAR2(4000);
l_child_list VARCHAR2(4000);
PROCEDURE print (p_alter_session IN VARCHAR2)
IS
BEGIN
l_test_id := l_test_id + 1;
l_spoolfile_name_p := LPAD(l_test_id, 5, '0');
dbms_output.put_line('PRO '||l_test_id||') "'||replace(p_alter_session,'ALTER SESSION SET',''));
dbms_output.put_line(p_alter_session);
dbms_output.put_line('@script');
END;
begin
FOR i IN (
WITH cbo_param AS (
SELECT /*+ materialize */ pname_qksceserow name
FROM x$qksceses
WHERE sid_qksceserow = SYS_CONTEXT('USERENV', 'SID')
)
SELECT x.indx+1 num,
x.ksppinm name,
x.ksppity type,
y.ksppstvl value,
y.ksppstdvl display_value,
y.ksppstdf isdefault,
x.ksppdesc description,
y.ksppstcmnt update_comment,
x.ksppihash hash
FROM x$ksppi x,
x$ksppcv y,
cbo_param
WHERE x.indx = y.indx
AND BITAND(x.ksppiflg, 268435456) = 0
AND TRANSLATE(x.ksppinm, '_', '#') NOT LIKE '##%'
AND x.ksppinm = cbo_param.name
AND x.inst_id = USERENV('Instance')
AND DECODE(BITAND(x.ksppiflg/256, 1), 1, 'TRUE', 'FALSE') = 'TRUE'
AND x.ksppity IN (1, 2, 3)
--and lower(x.ksppinm) || ' ' || lower(x.ksppdesc) like lower('%parallel%')
ORDER BY x.ksppinm)
LOOP
IF SUBSTR(i.name , 1, 1) = CHR(95) -- "_"
THEN
l_alter_session := 'ALTER SESSION SET "'||i.name ||'" = ';
ELSE
l_alter_session := 'ALTER SESSION SET '||i.name ||' = ';
END IF;
IF i.type = 1 THEN -- Boolean
IF LOWER(i.value) = 'true' THEN
l_alter_session := l_alter_session||' FALSE;';
ELSIF LOWER(i.value) = 'false' THEN
l_alter_session := l_alter_session||' TRUE;';
ELSE
dbms_output.put_line('--');
dbms_output.put_line('-- skip test on '||i.name ||'. baseline value: '||i.value);
END IF;
print(l_alter_session);
ELSIF i.type = 2 THEN -- String
-- this is used as base ALTER SESSION for the LOV
l_alter_session_bck := l_alter_session;
FOR j IN (SELECT value_kspvld_values value
FROM x$kspvld_values
WHERE LOWER(name_kspvld_values) = i.name
AND LOWER(value_kspvld_values) <> i.value
ORDER BY value_kspvld_values)
LOOP
l_alter_session := l_alter_session_bck||' '''||j.value||''';';
print(l_alter_session);
END LOOP;
end if;
end loop;
FOR i IN (SELECT * FROM v$session_fix_control WHERE session_id = SYS_CONTEXT('USERENV', 'SID') ORDER BY bugno) LOOP
IF i.value = 0 THEN --number
l_alter_session := 'ALTER SESSION SET "_fix_control" = '''||i.bugno||':1'';';
ELSIF i.value = 1 THEN
l_alter_session := 'ALTER SESSION SET "_fix_control" = '''||i.bugno||':0'';';
ELSE
l_alter_session := 'ALTER SESSION SET "_fix_control" = '''||i.bugno||':0'';';
END IF;
print(l_alter_session);
END LOOP;
end;
/
生成如下SQL:
PRO 1) " "_adaptive_window_consolidator_enabled" = FALSE; ALTER SESSION SET "_adaptive_window_consolidator_enabled" = FALSE; @script PRO 2) " "_add_stale_mv_to_dependency_list" = FALSE; ALTER SESSION SET "_add_stale_mv_to_dependency_list" = FALSE; @script PRO 3) " "_allow_level_without_connect_by" = TRUE; ALTER SESSION SET "_allow_level_without_connect_by" = TRUE; @script PRO 4) " "_always_anti_join" = 'CHOOSE'; ALTER SESSION SET "_always_anti_join" = 'CHOOSE'; @script PRO 5) " "_always_anti_join" = 'CUBE'; ALTER SESSION SET "_always_anti_join" = 'CUBE'; @script ... ... ...
2, 把原PL/SQL 或测试的用户SQL 存放在当前目录的script.sql中
3, 运行sql_test.sql
如果运行成功可以编辑sql_test.sql 把某参数删掉,继续尝试, 最终我们发现修改以下三个参数任何一个都可以正常运行:
set “_optimizer_distinct_agg_transform”=false
set “_gby_hash_aggregation_enabled”=false
“_fix_control” = ‘11657903:1’
上面的两个隐藏参数还是有一些BUG的,但12.2中没有完全相似的, 在11g是就是问题特性, 我的建议是系统级禁用上面的两个参数,如
alter system set "_gby_hash_aggregation_enabled" = false SCOPE=SPFILE;
方法3,
SQL级禁用该参数。
SQL> declare
2 cycle# number :=201911;
3 aa number;
4 begin
5 SELECT /*+opt_param('_optimizer_distinct_agg_transform','false')*/1 into aa
6 FROM(
7 select months_between(to_date(CYCLE#,'yyyymm'),to_date(minbillcycle,'yyyymm')) DEBTTIME,count(distinct minbillcycle) DEBTSUBS
8 FROM system.test_t1
9 GROUP BY months_between(to_date(CYCLE#,'yyyymm'),to_date(minbillcycle,'yyyymm'))
10 union all
11 SELECT 0 DEBTTIME, 0 DEBTSUBS
12 FROM system.test_t2 )
13 COMMIT;
14 end;
15 /
PL/SQL procedure successfully completed.
SQL>
Summary:
这里尝试了一种方法,当排查某个优化器参数时改变SQL的执行,遍历所有SESSION级优化器参数修改为有效的参数值,运行业务SQL, 同时还可以禁用或启用一些补丁修复的fix编号, 同样的方法还可以应用于SQL执行性能的测试。