使用用户定义的函数和左加入 oracle 10g 的分页速度很慢
Posted
技术标签:
【中文标题】使用用户定义的函数和左加入 oracle 10g 的分页速度很慢【英文标题】:pagination slow with userdefined function and left join oracle 10g 【发布时间】:2018-06-06 09:52:51 【问题描述】:我正在尝试通过使用分页和每页显示 60 条记录来加速查询(大约 60k 行的用户列表) 这是我的查询(oracle 10g)
select *
from ( select
a.*, ROWNUM rnum
from ( select u.username,u.userfullname,u.usercomment,u.isInner,u.isTeacher,u.isEmployer,u.deleted,
ps_fio(u.personid, 1) teachername,
ps_fio(sr.personid, 1) studentname,
case when u.isInner=1 then '' end Inn,
case when u.isStudent=1 then '' end Stud,
case when u.isTeacher=1 then '' end Teach from AD_Users u
left join fc_studentrecords sr on sr.recordid=u.studentid
order by u.username) a
where ROWNUM <= 120)
where rnum > 60;
问题是,一旦我将左联接放入 fc_studentrecords 表并尝试选择 ps_fio(sr.personid, 1),使用我的查询执行时间为 8.5 秒,而仅使用左联接和 sr 则为 0.6 秒.personid。
我确实在 ad_users ps_fio(u.personid, 1) 和 fc_studentrecords ps_fio(sr.personid, 1) 上放置了 2 个基于函数的索引
fc_studentrecords 表有大约 80K 行
这是 ps_fio 函数的代码(基本上它显示给定 personid 的人的全名)
create or replace FUNCTION PS_FIO(PerId PS_PERSONS.PERSONID%Type, FioType NUMBER) RETURN VARCHAR2 DETERMINISTIC IS
result VARCHAR2(70);
gender_int NUMBER ;
BEGIN
select decode(m.message_id,'SEX_MALE',1,0) into gender_int
from ps_persons p
join rb_messages m on m.message_value=p.sex
where personid=PerId;
BEGIN
select case
when fiotype=0 then trim(familyname)||' '||trim(firstname)||' '||trim(secondname)
when fiotype=1 then trim(familyname)||' '||substr(firstname, 1, 1)||'. '||substr(secondname, 1, 1)||'.'
else '' end into result
from ps_persons
where personid=PerId;
EXCEPTION
WHEN no_data_found then result:= '';
END;
return result;
END PS_FIO;
here are the create table scripts for ad_users and fc_studenrecords table
CREATE TABLE "COPYREAL"."AD_USERS"
( "USERNAME" VARCHAR2(25 CHAR),
"USERFULLNAME" VARCHAR2(100 CHAR) NOT NULL ENABLE,
"USERCOMMENT" VARCHAR2(200 CHAR),
"PWD" RAW(16),
"PERSONID" NUMBER(10,0),
"ISINNER" NUMBER(1,0) DEFAULT 1 NOT NULL ENABLE,
"ISTEACHER" NUMBER(1,0) DEFAULT 0 NOT NULL ENABLE,
"ISSTUDENT" NUMBER(1,0) DEFAULT 0 NOT NULL ENABLE,
"STUDENTID" NUMBER(10,0),
"DELETED" NUMBER(1,0) DEFAULT 0 NOT NULL ENABLE,
"ISEMPLOYER" NUMBER(1,0) DEFAULT 0 NOT NULL ENABLE,
"USERCODE" NUMBER(5,0),
"MOBILE_NUMBER" NUMBER(10,0),
CONSTRAINT "PK_AD_USERS" PRIMARY KEY ("USERNAME")
USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "SYSTEM" ENABLE,
CONSTRAINT "CH_USER_PERSON" CHECK (personid is not null or isTeacher=0) ENABLE,
CONSTRAINT "CH_USER_STUDENT" CHECK (studentid is not null or isStudent=0) ENABLE,
CONSTRAINT "UK_USERCODE" UNIQUE ("USERCODE")
USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "USERS" ENABLE,
CONSTRAINT "FK_USER_PERSON" FOREIGN KEY ("PERSONID")
REFERENCES "COPYREAL"."PS_PERSONS" ("PERSONID") ENABLE,
CONSTRAINT "FK_USER_STUDENT" FOREIGN KEY ("STUDENTID")
REFERENCES "COPYREAL"."FC_STUDENTRECORDS" ("RECORDID") ENABLE
) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "SYSTEM" ;
CREATE INDEX "COPYREAL"."AD_USERS_FIO_IDX" ON "COPYREAL"."AD_USERS" ("COPYREAL"."PS_FIO"("PERSONID",1))
PCTFREE 10 INITRANS 2 MAXTRANS 167 COMPUTE STATISTICS
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "USERS" ;
CREATE INDEX "COPYREAL"."IX_FK_USER_PERSON" ON "COPYREAL"."AD_USERS" ("PERSONID")
PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "USERS" ;
CREATE INDEX "COPYREAL"."IX_FK_USER_STUDENT" ON "COPYREAL"."AD_USERS" ("STUDENTID")
PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "USERS" ;
CREATE TABLE "COPYREAL"."FC_STUDENTRECORDS"
( "RECORDID" NUMBER(10,0),
"PERSONID" NUMBER(10,0) NOT NULL ENABLE,
"BOOKNO" VARCHAR2(15 CHAR),
"BOOKDATE" DATE,
"DIPLOMANO" VARCHAR2(15 CHAR),
"PLANID" NUMBER(10,0) NOT NULL ENABLE,
"DOCDATE" DATE,
"DOCNO" VARCHAR2(15 CHAR),
"DOCSUM" NUMBER(15,2),
"BRANCHID" NUMBER(4,0) DEFAULT 1 NOT NULL ENABLE,
"STATEMENTID" NUMBER(10,0),
"DOCPRIVTYPE" NUMBER(2,0),
"DOCPRIVPER" NUMBER(3,0),
"PREVDOC" VARCHAR2(30 CHAR),
"ENTERED" VARCHAR2(30 CHAR),
"STUDYFORMID" NUMBER(1,0),
"BUDGET" NUMBER(1,0) DEFAULT 0 NOT NULL ENABLE,
"PLACE" VARCHAR2(40 CHAR),
"PAID_SUM" NUMBER(15,2),
"FINAL_PAYMENT_DATE" DATE,
"PAID_BY_CAPITAL" NUMBER(1,0) DEFAULT 0 NOT NULL ENABLE,
"USERNAME" VARCHAR2(25 CHAR),
"INDIV_STUDENTID" NUMBER(10,0),
"CLIENT" VARCHAR2(70 CHAR),
CONSTRAINT "PK_FC_STUDENTRECORDS" PRIMARY KEY ("RECORDID")
USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
STORAGE(INITIAL 720896 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "USERS" ENABLE,
CONSTRAINT "UK_BOOKNO" UNIQUE ("BOOKNO")
USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
STORAGE(INITIAL 917504 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "USERS" ENABLE,
CONSTRAINT "UK_FC_STYDENTRECORDS" UNIQUE ("PERSONID", "PLANID")
USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
STORAGE(INITIAL 2097152 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "USERS" ENABLE,
CONSTRAINT "FK_FCDSTUDRECS_RBCOMMONSTFORMS" FOREIGN KEY ("STUDYFORMID")
REFERENCES "COPYREAL"."RB_COMMONSTUDYFORMS" ("FORMID") ENABLE,
CONSTRAINT "FK_STUDENTRECORDS_DEPARTMENTS" FOREIGN KEY ("BRANCHID")
REFERENCES "COPYREAL"."RB_DEPARTMENTS" ("CODE") ENABLE,
CONSTRAINT "FK_PS_PERSONS" FOREIGN KEY ("PERSONID")
REFERENCES "COPYREAL"."PS_PERSONS" ("PERSONID") ENABLE,
CONSTRAINT "FK_FC_STUDENTREC_PL_EDUCPLANS" FOREIGN KEY ("PLANID")
REFERENCES "COPYREAL"."PL_EDUCPLANS" ("PLANID") ENABLE,
CONSTRAINT "FK_FS_STUDREC_FC_PRIVEL" FOREIGN KEY ("DOCPRIVTYPE")
REFERENCES "COPYREAL"."FC_PRIVILEGETYPES" ("PRIVILEGEID") ENABLE,
CONSTRAINT "FK_FC_STUDENTRECORDS_AD_USERS" FOREIGN KEY ("USERNAME")
REFERENCES "COPYREAL"."AD_USERS" ("USERNAME") ENABLE,
CONSTRAINT "FK_FCSTUDRECS_ENSTATEMENTS" FOREIGN KEY ("STATEMENTID")
REFERENCES "COPYREAL"."EN_STATEMENTS" ("STATEMENTID") ENABLE,
CONSTRAINT "FK_FC_STUDREC_IP_STUDENTS" FOREIGN KEY ("INDIV_STUDENTID")
REFERENCES "COPYREAL"."IP_STUDENTS" ("STUDENTID") ENABLE
) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING
STORAGE(INITIAL 3145728 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "USERS" ;
CREATE INDEX "COPYREAL"."FC_STUDENTRECORDS_FIO_IDX" ON "COPYREAL"."FC_STUDENTRECORDS" ("COPYREAL"."PS_FIO"("PERSONID",1))
PCTFREE 10 INITRANS 2 MAXTRANS 167 COMPUTE STATISTICS
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "USERS" ;
CREATE INDEX "COPYREAL"."FC_STUDENTRECORDS_FIO_IDX2" ON "COPYREAL"."FC_STUDENTRECORDS" ("COPYREAL"."PS_FIO"("PERSONID",0))
PCTFREE 10 INITRANS 2 MAXTRANS 167 COMPUTE STATISTICS
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "USERS" ;
CREATE INDEX "COPYREAL"."IX_FC_STUDENTRECORDS" ON "COPYREAL"."FC_STUDENTRECORDS" ("PERSONID")
PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
STORAGE(INITIAL 2097152 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "USERS" ;
CREATE INDEX "COPYREAL"."IX_FK_FCDSTUDRECS_RBCOMMONSTFO" ON "COPYREAL"."FC_STUDENTRECORDS" ("STUDYFORMID")
PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "USERS" ;
CREATE INDEX "COPYREAL"."IX_FK_FCSTUDRECS_ENSTATEMENTS" ON "COPYREAL"."FC_STUDENTRECORDS" ("STATEMENTID")
PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "USERS" ;
CREATE INDEX "COPYREAL"."IX_FK_FC_STUDENTREC_PL_EDUCPLA" ON "COPYREAL"."FC_STUDENTRECORDS" ("PLANID")
PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "USERS" ;
CREATE INDEX "COPYREAL"."IX_FK_FS_STUDREC_FC_PRIVEL" ON "COPYREAL"."FC_STUDENTRECORDS" ("DOCPRIVTYPE")
PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "USERS" ;
CREATE INDEX "COPYREAL"."IX_FK_STUDENTRECORDS_DEPARTMEN" ON "COPYREAL"."FC_STUDENTRECORDS" ("BRANCHID")
PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "USERS" ;
这是 sql developer 的自动跟踪输出
buffer is not pinned count 207278
bytes received via SQL*Net from client 880
bytes sent via SQL*Net to client 20975
calls to get snapshot scn: kcmgss 207199
consistent gets 674851
consistent gets - examination 310692
consistent gets from cache 674851
CPU used by this session 890
CPU used when call started 898
cursor authentications 4
DB time 914
enqueue releases 5
enqueue requests 5
execute count 103599
index fetch by key 103590
no work - consistent read gets 260481
opened cursors cumulative 7
OS Block input operations 872
OS Involuntary context switches 62
OS Page faults 2
OS Page reclaims 1714
OS System time used 159
OS User time used 739
OS Voluntary context switches 39
parse count (hard) 5
parse count (total) 7
parse time cpu 1
parse time elapsed 1
recursive calls 103634
recursive cpu usage 687
rows fetched via callback 103512
session logical reads 674851
session pga memory -327680
shared hash latch upgrades - no wait 78
sorts (memory) 3
sorts (rows) 53897
sql area evicted 1
SQL*Net roundtrips to/from client 9
table fetch by rowid 103600
table fetch continued row 78
table scan blocks gotten 260393
table scan rows gotten 1948362
table scans (short tables) 51797
user calls 11
workarea executions - optimal 7
我错过了什么吗?
【问题讨论】:
【参考方案1】:从 SQL 查询内部调用 PL/SQL 函数可能会非常昂贵,因为它会导致每行从 SQL 切换到 PL/SQL。
ad_users.person_id
似乎可以为空?如果u.personid
或sr.personid
是NULL
,我会排除对ps_fio
的调用,具体取决于这些空值的频率。
接下来,我将从函数ps_fio
中删除gender_int
的计算,因为它似乎没有被使用。
接下来,我将尝试将函数调用替换为表 persons
的连接,并直接在查询中计算全名。丑陋,但可能更快。
最后,您对trim(familyname)
等的调用表明,表 ps_persons 的名称列中有尾随和/或前导空格。速度上的好处是微乎其微的,但可能这种清理可以在表中完成一次,这样就不需要调用修剪函数了吗?
【讨论】:
【参考方案2】:您在两个FULL TABLE SCANS
执行计划上使用HASH JOIN
,这不是分页的好选择。
为什么?较小的表必须在哈希表中进行完全扫描和转换,并且必须先探测第二个表的第一个块,然后才能显示第一页。所以你有一个在第一页之前的时间惩罚。没有人这么有耐心。
你应该有一个快速显示第一页并惩罚更高页面的执行计划。
你使用的典型
索引来访问驱动表(避免排序) 内表的嵌套循环连接 对内表的索引访问这里是一个例子(注意我使用提示来强制执行所需的访问)
select *
from ( select
a.*, ROWNUM rnum
from ( select /*+ ordered use_nl(u sr) index(u AD_Users_idx) */ u.username,u.userfullname,u.usercomment,u.isInner,u.isTeacher,u.isEmployer,u.deleted,
--
case when u.isInner=1 then '' end Inn,
case when u.isStudent=1 then '' end Stud,
case when u.isTeacher=1 then '' end Teach from AD_Users u
left join fc_studentrecords sr on sr.recordid=u.studentid
order by u.username) a
where ROWNUM <= 120)
where rnum > 60;
请确保username
列已编入索引并且NOT NULL可以(索引访问所必需)。
列 recordid
也必须被索引。
预期的执行计划如下:
---------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 100K| 16M| 2022K (1)| 06:44:26 |
|* 1 | VIEW | | 100K| 16M| 2022K (1)| 06:44:26 |
|* 2 | COUNT STOPKEY | | | | | |
| 3 | VIEW | | 1000K| 150M| 2022K (1)| 06:44:26 |
| 4 | NESTED LOOPS OUTER | | 1000K| 182M| 2022K (1)| 06:44:26 |
| 5 | TABLE ACCESS BY INDEX ROWID| AD_USERS | 1000K| 169M| 19871 (1)| 00:03:59 |
| 6 | INDEX FULL SCAN | AD_USERS_IDX | 1000K| | 272 (1)| 00:00:04 |
|* 7 | INDEX RANGE SCAN | FC_STUDENTRECORDS_IDX | 1 | 13 | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------
此计划立即返回第一页,更高的页面需要更多时间,因为您必须遍历索引。所以我的测试设置中的第 10.000 页需要几秒钟。但是,如果您有足够的耐心来分页,这将没有问题;)
请注意,我消除了函数调用 - 在最外面的查询中首先调用它们是一个明智的主意,即仅针对选定的行 - 不适用于跳过的行。
还要注意,在 12g 中您可以使用 Oracle supported pagination。
【讨论】:
以上是关于使用用户定义的函数和左加入 oracle 10g 的分页速度很慢的主要内容,如果未能解决你的问题,请参考以下文章