Медленное разбиение на страницы с пользовательской функцией и оракулом 10g - PullRequest
0 голосов
/ 06 июня 2018

Я пытаюсь ускорить запрос (список пользователей около 60 тыс. Строк), используя нумерацию страниц и показывая 60 записей на странице, вот мой запрос (оракул 10 г)

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 с, по сравнению с 0,6 с только с левым соединением и sr.personid.

Я поместил 2 функциональных индекса на ad_users ps_fio (u.personid, 1) и fc_studentrecords ps_fio (sr.personid, 1)

В таблице fc_studentrecords около 80 тыс. Строк

воткод для функции 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

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

explain plan output

autotrace output

Я что-то упустил?

Ответы [ 2 ]

0 голосов
/ 06 июня 2018

Вы используете HASH JOIN в плане выполнения с двумя FULL TABLE SCANS, это не хороший выбор для нумерации страниц .

Почему?Меньшая таблица должна быть полностью отсканирована и преобразована в хеш-таблицу, и первый фрагмент второй таблицы должен быть прощупан, прежде чем будет показана первая страница.Таким образом, у вас есть штраф времени до первой страницы .Никто не является таким пациентом.

У вас должен быть план выполнения, который быстро показывает первую страницу и штрафует страницы более высокого уровня.

Обычно вы используете

  • indexдля доступа к управляющей таблице (чтобы избежать сортировки)
  • присоединение вложенного цикла к внутренней таблице
  • индекс доступа к внутренней таблице

Вот пример (примечание Iиспользуйте подсказку для обеспечения требуемого доступа)

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, поддерживаемую нумерацию страниц .

0 голосов
/ 06 июня 2018

Вызов функции PL / SQL изнутри запроса SQL может быть довольно дорогим, так как он вызывает переход с SQL на PL / SQL для каждой строки.

ad_users.person_id кажется обнуляемым?Я бы исключил вызовы на ps_fio, если u.personid или sr.personid равно NULL, в зависимости от частоты этих нулей.

Далее я бы удалил вычисление gender_int изфункция ps_fio, как кажется, не используется.

Далее я попытаюсь заменить вызов функции соединением с таблицей persons и вычислить полное имя непосредственно в запросе.Ужасно, но может быть и быстрее.

Наконец, ваши звонки на trim(familyname) и т. Д. Предполагают, что в столбцах имен таблицы ps_persons есть конечные и / или начальные пробелы.Выигрыш в скорости был бы минимальным, но может быть, эту очистку можно было бы выполнить один раз в таблице, чтобы не вызывать ненужные вызовы функции усечения?

...