Небольшая предыстория. У нашего заказчика есть сервер, на котором крутятся два инстанса. И вот мы заметили, что потихоньку начал использоваться swap. Здесь хочу описать кейс диагностики данной неприятности. Возможно, кому-нибудь пригодится. Oracle 11.2.0.2, downstream Streams репликация с 10.2.0.4, сторона apply. Все приведенные цифры пришлось немного подправить, но общая логика сохранилась.
Итак, первым признаком проблем с памятью являлось использование swap при том, что оба инстанса на сервере сконфигурированы таким образом, чтобы суммарный объем потребления не превышал объема физической памяти. Тем не менее, с каждым днем использование swap росло, прослеживался значительный пейджинг.
[ora@srv ~]$ free
total used free
Mem: 65975204 65633036 342168
Swap: 68026360 8142168 59884192
|
При анализе потребления памяти процессами Oracle выяснилось, что не смотря не установленное значение параметра PGA_AGGREGATE_TARGET (которое рекомендует Oracle ограничивать объем памяти, про выделяемой под процессы) равное 10Gb, реальное потребление памяти превышало 20 Gb и постепенно росло.
| SQL> select display_value from v$parameter 2 where name like ‘%pga_aggregate_target%’; DISPLAY_VALUE -------------------------------------------------------------------------------- 10G SQL> select round(value/1024/1024/1024,2)||'G' as real_pga_allocated 2 from v$pgastat s where name like '%total PGA allocated%'; REAL_PGA_ALLOCATED ------------------------------------------------------------------------ 21.51G |
Linux также показывал довольно большое потребление памяти инстансом, при SGA=25G. Эта команда показывает потребление физической памяти для инстанса в Гб.
[ora@srv ~]$ ps aux | grep -v agent | grep -v trace | grep ORCL | awk ‘{print $2}’ | xargs pmap -d | grep private | awk ‘{s +=$4} END {print (s + $6)/1024/1024}’
48.2101 |
При попытке выяснить какие процессы потребляют наибольшее количество памяти, стало ясно, что виноваты процессы Streams аpply, чье потребление памяти в разы превышало потребление остальных процессов. При этом никакого логического объяснения таких требований к ресурсам для данных процессов нет. Все сообщения хранятся в Streams pool, который находится в SGA. Соответственно, причин расти, да еще и с такой скоростью у данных процессов не было.
SQL> select p.pga_alloc_mem, p.pga_used_mem, s.sid, p.spid, s.osuser, s.machine, s.program, s.module, s.action 2 from v$session s, v$process p where s.PADDR = p.ADDR order by p.PGA_ALLOC_MEM desc;
PGA_ALLOC_MEM PGA_USED_MEM SID SPID OSUSER MACHINE PROGRAM MODULE ACTION ------------- ------------ ----- ------ -------- -------- --------------- ---------- ------------------ 1919372020 1917406014 144 11910 ora srv ora@srv (AS0X) Streams AP1 - Apply Server 1766271988 1759702918 4474 11902 ora srv ora@srv (AS0U) Streams AP2 - Apply Server |
Тогда был сделан дамп памяти по одному из процессов. Для того, чтобы сделать дамп памяти, необходимо было выполнить следующие команды в SQL Plus:
| SQL>oradebug setospid 11910SQL>oradebug dump heapdump 1 |
В результате сформировался дамп, содержание которого было следующего рода:
Received ORADEBUG command (#1) ‘dump heapdump 1′ from process ‘Unix process pid: 6996, image: <none>’
****************************************************** HEAP DUMP heap name="pga heap" desc=0xb7c8ba0 extent sz=0x20c0 alt=216 het=32767 rec=0 flg=2 opc=3 parent=(nil) owner=(nil) nex=(nil) xsz=0xfff0 heap=(nil) fl2=0x20, nex=(nil) EXTENT 0 addr=0x2b9464dbe010 Chunk 2b9464dbe020 sz= 16400 freeable "koh-kghu call " ds=0x2b943789a050 Chunk 2b9464dc2030 sz= 16368 freeable "koh-kghu call " ds=0x2b943789a050 Chunk 2b9464dc6020 sz= 32736 free " " EXTENT 1 addr=0x2b9464d5e010 Chunk 2b9464d5e020 sz= 49136 free " " Chunk 2b9464d6a010 sz= 16368 freeable "koh-kghu call " ds=0x2b943789a050 |
Потом осталось просто просуммировать куски, выделенные под кучу kog-khgu call и находящиеся в статусе freeable. Результат в Гб:
[oracle@srv trace]$ cat ORCL_as0u_11910.trc | grep free | grep koh-kghu | awk ‘{s+=$4} END {print s/1024/1024/1024}’
1.693747 |
Т.е. стало ясно, что подавляющее большинство памяти принадлежит куче kog-khgu call. Кроме этого, почти вся эта память имеет статус freeable. Данную ситуацию можно оценить как утечку памяти.
На металинке существуют баги с похожим описанием. Наиболее вероятно, что мы имеем дело с багом 11061508. К сожалению, для данного бага не определены пути обхода. Тем не менее, перезапуск APPLY процессов помогает возвратить память системе.
ЗЫ. Не могу подтвердить экспериментально, однако предполагаю, что значительное превышение реального потребления памяти процессами значения, установленного в параметре PGA_AGGREGATE_TARGET, может также негативно влиять на управление памятью всех процессов инстанса. Это ведет к недостаточному выделению памяти для рабочих процессов и, как следствие, общей деградации производительности.
Всем добрый день!
Сегодня отличный день чтобы написать очередную заметку. Посвящена она будет довольно популярному продукту от Oracle (теперь:) под названием GoldenGate. Установка самого GG отлично описана вот тут. Меня же заинтересовал процесс установки подсистемы Management Pack for Oracle GoldenGate (в прошлом GoldenGate Director). В ходе инсталляции этой программы, у меня возникла очень неприятная ошибка, которая как раз и побудила меня написать данный пост. Поэтому основное внимание будет уделено именно ей. Лично у меня на решение этой проблемы ушло несколько часов, поэтому я надеюсь, что смогу сэкономить вам довольно много времени в случае возникновения аналогичной проблемы. Итак.
Director состоит из двух частей. Серверной и клиентской. Клиентскую часть я использовал виндовую и с ее установкой проблем не возникло. Установить серверную часть оказалось значительно сложнее.
Итак, для установки серверной части GG Director(Management Pack for Oracle GoldenGate), на сервере вам потребуется наличие WebLogic. Для его установки мне пришлось скачать и поставить JDK и JRE версии 1.6. После этого:
1.Прописываем путь к папке jre/bin в PATH
export PATH=/u04/app/jre_1.6/bin:$PATH
2. Прописываем переменную DISPLAY
export DISPLAY=yourip:0.0
3. Запускаем X-сервер. Лично я предпочитаю Reflection-X
4. Запускаем установку WebLogic
java -jar wls1031_generic.jar
Ничего сложного. В конце установки отказываемся от QuickStart.
Теперь ставим GG Director.
Скачиваем дистрибутив, распаковываем.
1. Делаем файл исполняемым
chmod +x ggdirector-serversetup_v2.0.0.2_001.sh
2. Запускаем инсталлятор
./ggdirector-serversetup_v2.0.0.2_001.sh
3. Потом все просто. Указываем сначала директорию, куда установили weblogic.
4. Когда инсталлятор потребует ввести директорию для домена, я создал и указал ему папку в директории weblogic:
mkdir -p user_projects/domains
Домен назовем GGDomain
5. Когда нас спросят про подключение к базе, я использовал Oracle.
6. Вот тут самое инетересное. Когда он просит указать нас SID, сервер и порт, то вне зависимости от того, какой порт мы укажем, при установке будет использоваться порт 1521. Например, мне надо было использовать порт 1522.
Если не предпринимать никаких действий, то мы получим ошибку:
unable to run run-cds-tool script file.
На эту ошибку у меня сегодня ушло очень много времени. Но я придумал одну хитрость как это обойти ![]()
7. Продолжаем процесс инсталяции. Когда статусная строка будет подходить к концу, необходимо нажать cancel. Установка запаузится.
8. В параллельной сессии заходим в папку $weblogic/wlserver_10.3/server/bin. Здесь мы должны обнаружить файл db-config.cds. Если его нет, значит на кнопку cancel нажали слишком рано.
9. Правим в файлике порт, на нужный.
10. В окне устанвки отказываемся от отмены установки. Установка продолжается и успешно завершается.
11. Если вы все сделали правильно, то серверная часть будет установлена. Но работать ничего не будет ![]()
При запуске сервера будет следующая ошибка:
<Nov 6, 2009 4:18:04 PM MSK> <Error> <Security> <BEA-000000> <[Security:090737]An exception occurred while getting connection to validate the configuration settings
java.sql.SQLException: [Security:090735]The DBMS connection was not usable
at weblogic.security.providers.authentication.DBMSSQLAuthenticatorDelegateImpl.getManageableConnection(DBMSSQLAuthenticatorDelegateImpl.java:923)
at weblogic.security.providers.authentication.DBMSSQLAuthenticatorDelegateImpl.validateConfiguration(DBMSSQLAuthenticatorDelegateImpl.java:153)
at weblogic.security.providers.authentication.DBMSSQLAuthenticatorDelegateImpl.<init>(DBMSSQLAuthenticatorDelegateImpl.java:78)
at weblogic.security.providers.authentication.DBMSAuthenticatorDelegateImpl.getInstance(DBMSAuthenticatorDelegateImpl.java:459)
at weblogic.security.providers.authentication.DBMSSQLAuthenticationProviderImpl.initialize(DBMSSQLAuthenticationProviderImpl.java:56)
Truncated. see log file for complete stacktrace
>
<Nov 6, 2009 4:18:06 PM MSK> <Notice> <Security> <BEA-090082> <Security initializing using security realm ggRealm.>
<Nov 6, 2009 4:18:06 PM MSK> <Error> <Security> <BEA-000000> <[Security:090735]The DBMS connection was not usable>
<Nov 6, 2009 4:18:06 PM MSK> <Critical> <Security> <BEA-090402> <Authentication denied: Boot identity not valid; The user name and/or password from the boot identity file (boot.properties) is not valid. The boot identity may have been changed since the boot identity file was created. Please edit and update the boot identity file with the proper values of username and password. The first time the updated boot identity file is used to start the server, these new values are encrypted.>
12. Заходим в папку $weblogic/user_projects/domains/GGDomain/config/jdbc
13. Редактируем файл ggds-datasource-jdbc.xml, заменяя номер порта на нужный.
14. Запускаем $weblogic/user_projects/domains/GGDomain/startWebLogic.sh
15. При подключении указываем admin/admin. Также после имени сервера через двоеточие уазываем порт. Порт задавался при установке GG Director. По умолчанию 7001.
Ура! Процесс установки завершен! Возможно, что проблема проявляется только в сборке под Linux x86_64. На сколько мне известно, уже заведен баг по этому поводу.
До новых встреч!
UPD!!!
Как выяснилось, проблема с портом листенера возникает только при использование Reflection-X в качестве X сервера. С XMing все проходит на ура.
Наконец, выдалась свободная минутка написать.
Часто, работая с приложениями определенного типа, встречаешь стандартный набор ошибок. Встречаются они не очень часто, однако времени на поиски путей исправления требуется обычно достаточно много. Таким образом, решил, что стоит писать о часто встречающихся неочевидных проблемах хотя бы для того, чтобы потом была возможность быстро найти решение.
Итак, сегодня хотелось бы поговорить о возникшей на днях проблеме. Выглядела она следующим образом:
ORA-12801: error signaled in parallel query server P007
ORA-08103: object no longer exists
И возникала в процессе исполнения MERGE куска данных в таблицу. Около 20 млз в 160 млз. На таблице был построен набор битмап индексов. Табличное пространство ASSM.
В нашей практике данная ошибка встречается довольно часто. Oracle имеет ряд багов на эту тему.
Обычно это лечится пересозданием битмап индексов. Думаю, что как вариант можно сделать им rebuild online.
В этот раз убийство индексов не помогло. В качестве решения сработал перенос всей таблицы в другое табличное пространство. И существует два возможных предположений на счет причины возникновения проблемы.
1. В каком-то обновляемом блоке были проблемы, однако при переносе в другое ТП, они были исправлены.
2. В исходном ТП было недостаточно места. Похоже, что истинной причиной ошибки была как раз проблема со свободным пространством. Неясно, почему ошибка выглядела именно таким образом.
Вот так.
- Создается 3 таблицы
- Данные для merge(update): t_update
- Данные для insert: t_insert
- Данные для merge(insert/update): t_merge
- Устанавливаются наборы количества записей для update и insert. Таким образом, чтобы можно было сформировать пространство производительности. По первой оси будет кол-во записей для update. По второй – кол-во записей для insert. По третьей – время исполнения операции.
- Заполняем данными обновляемую таблицу t_uim, в которую будем производить insert, update и merge.
- Заполняются данными таблицы t_update и t_insert. Данные для заполнения берутся таким образом, чтобы в таблице t_uim присутствовали записи с ключами из t_update и отсутствовали записи с ключами t_insert.
- Производим insert из таблицы t_insert в таблицу t_uim и замеряем время.
- Производим merge с условием WHEN MATCHED THEN UPDATE, производя, таким образом, update записей в таблице t_uim на значения из таблицы t_update и замеряем время операции.
- Обновляем таблицу t_uim.
- Таблица t_merge заполняется объединением union all таблиц t_update и t_insert.
- Производится полноценный merge данных из таблицы t_merge в таблицу t_uim и замеряем время операции.
- Повторяем для следующих значений кол-ва записей insert и update.
- Таблица t_uim содержала 100 млн. записей.
- Таблицы t_insert и t_update заполнялись записями в следующих количествах:1.000, 10.000, 100.000, 1.000.000, 10.000.000
- Операции производились серийно
- В операциях insert и merge использовался хинт APPEND
- В операциях update и merge использовался хинт USE_HASH
- Производилось 3 запуска всего алгоритма для исключения влияния внешних факторов
|
Opname |
Target |
Target_desc |
TotalWork |
Units |
Elapsed_seconds |
|
Information |
Merge completed |
0 |
info |
0 |
|
|
Hash Join |
469575 |
Blocks |
786 |
||
|
Table Scan |
T_ium |
371476 |
Blocks |
479 |
|
|
Table Scan |
T_merge |
32688 |
Blocks |
35 |
|
|
Hash Join |
469575 |
Blocks |
628 |
||
|
Table Scan |
T_ium |
371476 |
Blocks |
365 |
|
|
Table Scan |
T_merge |
32688 |
Blocks |
39 |
|
|
Information |
Merge started |
0 |
info |
0 |
|
|
Information |
Update Completed |
0 |
info |
0 |
|
|
Hash Join |
469575 |
Blocks |
1194 |
||
|
Table Scan |
T_ium |
371476 |
Blocks |
262 |
|
|
Table Scan |
T_merge |
32688 |
Blocks |
31 |
|
|
Information |
Update started |
0 |
info |
0 |
|
Bug N |
Description |
|
< 9.2.0.7 |
|
|
MERGE does not work correctly for HASH clusters (wrong result) |
|
|
3447792 |
Multi-table insert can fail with ORA-24335 |
|
3469121 |
OERI from MERGE statement with database link and sequence |
|
< 9.2.0.8 |
|
|
2619895 |
Dump / OERI:kcbgcur_9 using PLSQL in SET clause of MERGE .. UPDATE |
|
2692199 |
ORA-12805 from PQ MERGE with update clause with a functional index |
|
3413826 |
ORA-904 from MERGE SQL across database link |
|
4206556 |
ORA-2002 and OERI:kzasps1 when not auditing system privileges |
|
4321292 |
LogMiner can fail for MERGE SQL on LOBs |
|
4572043 |
DML monitoring is incorrect for MERGE / MTI |
|
< 10.1.0.5 |
|
|
3413826 |
ORA-904 from MERGE SQL across database link |
|
4252999 |
OERI:12333 from JDBC MERGE SQL with a batch size > 1 |
|
< 10.2.0.2 |
|
|
4395296 |
MERGE with APPEND hint dumps instead of giving ORA-30926 |
|
4602031 |
Block corruption from UPDATE or MERGE into compressed table |
|
4630549 |
ORA-904 / dump from MERGE SQL |
|
< 10.2.0.3 |
|
|
4644798 |
Dump (kkmups) using MERGE SQL |
|
4685163 |
Merge SQL uses more CPU in 10.2 than 10.1 |
|
5049941 |
Direct path merge records incorrect information in ALL_SUMDELTA |
|
< 10.2.0.4 |
|
|
4216668 |
Dump from INSERT / MERGE on internal columns |
|
4311273 |
ORA-2064 using MERGE statement over a database link |
|
4406211 |
Dump (insLoadRowInfo) from MERGE SQL with false condition |
|
4572043 |
DML monitoring is incorrect for MERGE / MTI |
|
4771851 |
Dump (kxccsrw) from INSERT .. AS SELECT / MERGE |
|
5181547 |
Index corruption after insert-only merge /*+ append */ or PDML into table |
|
5387478 |
ORA-28132 for user with EXEMPT ACCESS POLICY privilege |
|
5609096 |
MERGE / UPDATE to IOT can cause corruption |
|
5731952 |
MERGE statement does not insert rows if source is a (inline) view with parallel |
|
6379123 |
EXPDP/IMPDP ORA-39083 due to changes in MERGE privilege between 10.1 and 10.2 |
|
< 11.1.0.7 |
|
|
5990542 |
OERI[kxfqenq1] / Dump (kxfqgky) from MERGE on one table partition |
|
6318678 |
ORA-904 from MERGE SQL over database link |
|
6379123 |
EXPDP/IMPDP ORA-39083 due to changes in MERGE privilege between 10.1 and 10.2 |
|
6694370 |
Update phase of serial direct path merge writes more redo than an update |
|
INSERT |
||||||
|
Сумма по полю Sec |
Ins |
|||||
|
Upd |
0 |
1000 |
10000 |
100000 |
1000000 |
10000000 |
|
10000000 |
0,003 |
0,07 |
0,176 |
0,122 |
1,094 |
9,59 |
|
1000000 |
0,002 |
0,017 |
0,021 |
0,115 |
1,048 |
9,749 |
|
100000 |
0,002 |
0,014 |
0,019 |
0,116 |
1,039 |
9,595 |
|
10000 |
0,002 |
0,024 |
0,032 |
0,108 |
1,072 |
9,926 |
|
1000 |
0,002 |
0,033 |
0,036 |
0,144 |
0,967 |
10,051 |
|
0 |
0,002 |
0,041 |
0,024 |
0,139 |
1,068 |
9,842 |
|
UPDATE |
||||||
|
Сумма по полю Sec |
Ins |
|||||
|
Upd |
0 |
1000 |
10000 |
100000 |
1000000 |
10000000 |
|
10000000 |
1409,773 |
1345,477 |
1527,403 |
1369,143 |
1515,165 |
1624,855 |
|
1000000 |
132,465 |
105,988 |
105,018 |
103,534 |
99,019 |
200,827 |
|
100000 |
42,853 |
50,684 |
42,962 |
48,343 |
39,869 |
79,14 |
|
10000 |
32,491 |
29,843 |
39,321 |
42,173 |
29,327 |
40,483 |
|
1000 |
26,531 |
26,341 |
27,136 |
26,617 |
27,257 |
46,443 |
|
0 |
0,005 |
0,003 |
0,003 |
0,004 |
0,003 |
0,003 |
|
MERGE |
||||||
|
Сумма по полю Sec |
Ins |
|||||
|
Upd |
0 |
1000 |
10000 |
100000 |
1000000 |
10000000 |
|
10000000 |
2245,225 |
2377,532 |
2375,714 |
2256,942 |
2307,594 |
2794,332 |
|
1000000 |
180,662 |
179,926 |
182,774 |
214,046 |
447,038 |
1060,22 |
|
100000 |
83,182 |
111,373 |
108,911 |
120,021 |
121,979 |
1168,561 |
|
10000 |
70,292 |
59,273 |
61,198 |
86,044 |
114,056 |
419,1 |
|
1000 |
54,088 |
52,992 |
55,005 |
74,97 |
106,443 |
412,714 |
|
0 |
0,009 |
55,482 |
55,265 |
72,643 |
106,133 |
383,344 |
|
M-(U+I) |
||||||
|
Сумма по полю Sec |
Ins |
|||||
|
Upd |
0 |
1000 |
10000 |
100000 |
1000000 |
10000000 |
|
10000000 |
2245,22 |
2377,391 |
2375,362 |
2256,698 |
2305,407 |
2775,152 |
|
1000000 |
180,657 |
179,892 |
182,732 |
213,816 |
444,942 |
1040,721 |
|
100000 |
83,178 |
111,346 |
108,873 |
119,788 |
119,902 |
1149,37 |
|
10000 |
70,288 |
59,224 |
61,134 |
85,829 |
111,912 |
399,248 |
|
1000 |
54,084 |
52,926 |
54,932 |
74,681 |
104,508 |
392,612 |
|
0 |
0,004 |
55,399 |
55,217 |
72,364 |
103,998 |
363,66 |
И еще несколько удобных скриптов. Стоит заметить, что все скрипты, требующие вызова CONTROL CENTER’а выполняются достаточно долго. Одним из методов их ускорения может являться (в случае деплоя или андеплоя группы маппингов) групповая процедура. Для этого необходимо при создании плана указать сразу все необходимые маппинги.
Скрипт деплоя маппинга. Запускается при нахождении в модуле. В этом скрипте интересным моментом является то, что при ошибке в деплоее, не генерируется исключения. Т.е. нельзя использовать стандартный механизм if { [catch {….} erm ] > 0 } {.…}. Решение проблемы видно в скрипте.
|
OMBCC ‘/project/module’; OMBCONNECT CONTROL_CENTER;
proc deploy_mapping {map} { log “Creating deploy plan for $map”; set cdir_t [OMBDCC]; regexp “/(\[A-Z\]|\[0-9\]|\/|\_)*” $cdir_t cdir; if { [catch {OMBCREATE TRANSIENT DEPLOYMENT_ACTION_PLAN '$map.CREATE' ADD ACTION '$map.CREATE' SET PROPERTIES (OPERATION) VALUES ('CREATE') SET REFERENCE MAPPING '$cdir/$map' } erm] > 0 } { log “*** ERROR ***”; log “$erm”; } log “Deploying mapping $map”; catch {OMBDEPLOY DEPLOYMENT_ACTION_PLAN ‘$map.CREATE’} erm; if { [ regexp "ORA" $erm ] > 0 || [ regexp "OMB" $erm ] > 0 || [ regexp "OALL8" $erm ] > 0 } { log “*** ERROR ***”; log “$erm”; } } |
Скрипт андеплоя маппинга. Запускается при нахождении в модуле. Применяется для удаления пакета из БД.
|
OMBCC ‘/project/module’; OMBCONNECT CONTROL_CENTER;
proc undepl_mapping {map} { set cdir_t [OMBDCC]; regexp “/(\[A-Z\]|\[0-9\]|\/|\_)*” $cdir_t cdir; log “Preparing for undeploying $map”; if { [OMBLIST MAPPINGS '$map'] == “$map” } { log “Creating drop plan for $map”; if { [catch {OMBCREATE TRANSIENT DEPLOYMENT_ACTION_PLAN '$map.DROP' ADD ACTION '$map.DROP' SET PROPERTIES (OPERATION) VALUES ('DROP') SET REFERENCE MAPPING '$cdir/$map' } erm] > 0 } { log “*** ERROR ***”; log “$erm”; } log “Undeploying mapping $map”; if { [catch {OMBDEPLOY DEPLOYMENT_ACTION_PLAN '$map.DROP'} erm] > 0 } { log “*** ERROR ***”; log “$erm”; } } } |
Ну и просто удаление маппинга. Запускается при нахождении в модуле.
|
OMBCC ‘/project/module’;
proc delete_mapping {map} { set cdir_t [OMBDCC]; regexp “/(\[A-Z\]|\[0-9\]|\/|\_)*” $cdir_t cdir; log “Preparing for dropping $map”; if { [OMBLIST MAPPINGS '$map'] == “$map” } { log “Droping mapping $map”; if { [catch {OMBDROP MAPPING '$map'} erm] > 0 } { log “*** WARNING ***”; log “$erm”; } } } |
Мне периодически приходят благодарности за публикацию этих скриптов. Самое забавное, что недавно пришло письмо от человека, который не знает русского, поэтому не понял что написано в блоге, однако сами скрипты ему очень пригодились. Значит, потребность в них есть. Поэтому сегодня решил опубликовать еще пару скриптов OMB+. Не уверен, что многие будут их использовать именно в этом виде, однако уверен, что эти скрипты будут полезны в качестве наглядного пособия по изучению предмета.
При выгрузке модели из OWBмы установили ряд требований к этим проектам. Требования диктовались внутренней спецификой, однако, вот они:
1. Business name всегда должно соответствовать physicalname.
Причина этого заключается в том, что часто маппинги создаются методом копирования из другого маппинга и некоторой модификацией его. Результатом такой практики становится то, что в итоге появляются объекты с businessname соответствующим physical name уже существующего объекта. Эта проблема возникает, когда необходимо сделать обновление нескольких маппингов на конечной базе. В любом случае, стоит быть уверенным, что вы контролируете названия своих объектов.
2. Target Load Ordering на маппингах должен быть выключен.
Так как эта опция входит только в EEверсию, мы от нее отказались. И соответственно, использовать ее мы не имеем права. Таким образом, есть необходимость проверять отсутствие ее на маппингах.
3. В модели должны отсутствовать индексы и партиции в таблицах.
Для создания маппингов наличие индексов и партиций у таблиц нам не важно. Однако, при импортировании таблицы с большим количеством партиций, все они создаются в модели, что в будущем замедляет ее экспорт и импорт. Таким образом, удаляя индексы и партиции из модели, мы отказываемся от лишних данных в модели и ускоряем в целом ее работу.
Итак, задача стоит написать процедуру, на вход которой передается название проекта, а она проверяет соответствующие характеристики и выдает предупреждение, если что-то не соответствует нашим требованиям. Также процедура в случае необходимости должна исправлять найденные ошибки. Процедура запускается перед экспортом, и при необходимости в нее можно добавить необходимые проверки. Стоит обратить внимание, что везде для вывода сообщение используется процедура log. Её можно безболезненно заменить процедурой puts, если вывод должен осуществляться только на экран. Однако, гораздо интереснее, использовать процедуру, которая будет вскоре описана здесь.
Таким образом, можно написать следующую процедуру:
|
#mode all,bn,tlo,tbl #fix yes|no proc check_model { prj {mode “all”} {fix “no”} } { set mlist [split $mode "[,;:]“] OMBCC ‘/$prj’; foreach mod [OMBLIST ORACLE_MODULES] { OMBCC ‘$mod’; foreach map [OMBLIST MAPPINGS] { #Проверка Business Names if { [lsearch $mlist "all"] >= 0 || [lsearch $mlist "bn"] >= 0 } { set bn [OMBRETRIEVE MAPPING '$map' GET PROPERTIES(BUSINESS_NAME)]; if { $map != $bn } { log “WARNING!!!: Mapping $map”; log “Business name is $bn”; if { $fix == “yes” } { if { [ catch { OMBALTER MAPPING '$map' SET PROPERTIES(BUSINESS_NAME) VALUES('$map'); log "FIXED!!!"; } err ] > 0 } { log “ERROR: $err”; } } } } #Проверка Target Load Ordering if { [lsearch $mlist "all"] >= 0 || [lsearch $mlist "tlo"] >= 0 } { set tlo [OMBRETRIEVE MAPPING '$map' GET PROPERTIES(TARGET_LOAD_ORDERING)]; if { $tlo != “false” } { log “WARNING!!!: Mapping $map”; log “Use Target Load Ordering flag is enabled!”; if { $fix == “yes” } { if { [ catch { OMBALTER MAPPING '$map' SET PROPERTIES(TARGET_LOAD_ORDERING) VALUES('false'); log "FIXED!!!"; } err ] > 0 } { log “ERROR: $err”; } } } } } #Проверка таблиц foreach tab [OMBLIST TABLES] { if { [lsearch $mlist "all"] >= 0 || [lsearch $mlist "tbl"] >= 0 } { set prts [OMBRETRIEVE TABLE '$tab' GET PARTITIONS]; set idxs [OMBRETRIEVE TABLE '$tab' GET INDEXES]; if { $prts != “” } { log “WARNING!!!: Table $tab”; log “Exists partitions : $prts”; if { $fix == “yes” } { if { [ catch { foreach prt $prts { OMBALTER TABLE '$tab' DELETE PARTITION '$prt'; log "Fixing: Partition $prt deleted"; } log "FIXED!!!"; } err ] > 0 } { log “ERROR: $err”; } } } if { $idxs != “” } { log “WARNING!!!: Table $tab”; log “Exists indexes : $idxs”; if { $fix == “yes” } { if { [ catch { foreach idx $idxs { OMBALTER TABLE '$tab' DELETE INDEX '$prt'; log "Fixing: Index $idx deleted"; } log "FIXED!!!"; } err ] > 0 } { log “ERROR: $err”; } } } } } OMBCC ‘..’; } } |
Хотелось бы рассмотреть чуть подробнее процедуру логирования. В принципе, ничего сложного как всегда нет. Итак, вот какие процедуры использую я.
|
proc createLogFile {flnm} { global workdir set LogFileName $workdir\$flnm.log if { [catch {set fileHandle [open $LogFileName w]} FileError] } { puts “Could not open $LogFileName for writingn$FileError” exit 1 } return $fileHandle }
proc log {info} { global LogFile puts “$info” if { [lsearch [info globals] “LogFile” ] >= 0 } { puts $LogFile “$info” flush $LogFile } }
proc closeLogFile {} { global LogFile close $LogFile unset LogFile } |
А вот пример их использования:
|
log “log only to screen” set workdir {C:\Logs} set LogFile [createLogFile "TESTLOG" ] log “log to screen and log file closeLogFile |
Всем, добрый день! К моему сожалению, обстоятельства не позволяли мне обновлять блог все это время. Сегодня хочу предоставить Вашему вниманию очередную статью. В этот раз она посвящена выражению MODEL, так незаслуженно обделенному вниманием многих разработчиков. Я постараюсь это исправить. К сожалению, статья не является законченной, но т.к. она лежит у меня на диске уже больше года, я все же решил опубликовать то, что есть.
MODEL – выражение позволяющее представлять результат запроса в виде многомерной модели и позволяющее производить с ней операции характерные для многомерных сред. Суть его очень проста – выражение MODEL позволяет нам представить результат запроса в виде многомерного куба и задавать выражения для расчета его произвольных ячеек. Выражение SQL MODEL очень сильно напоминает по смыслу объект MODEL в Oracle Olap – также задающего правила расчета ячеек куба. Поскольку, я расскажу лишь об основах этого выражения, за всеми подробностями обращаться прямо в документацию (http://download-uk.oracle.com/docs/cd/B19306_01/server.102/b14223/sqlmodel.htm)
Выражение MODEL особенно удобно применять в случае построения отчетов. Причина этого в том, что из-за многомерной структуры его, оно будет очень чувствительно к большому количеству данных. Тем не менее, при помощи него очень удобно обрабатывать уже агрегированные результаты отчетов. Отличным примером будет необходимость дополнительно вывести в помесячном отчете о продажах за год строчку о сумме продаж за несколько произвольных месяцев.
Начнем с простых вещей. Для начала определим многомерную модель.
Многомерная модель определяется следующим образом: указываем, какие колонки запроса рассматривать как измерения(dimensions), а какие как показатели(measures).
|
MODEL DIMENSION BY col1, col2, … MEASURES col3, col4, … |
При необходимости, куб может быть партиционирован(partitioned) по набору колонок:
|
MODEL PARTITION BY col0, … DIMENSION BY col1, col2, … MEASURES col3, col4, … |
Стоить заметить, что возможность партиционирования особенно полезна при параллельной обработке.
В этих трех полях должны быть перечислены все колонки, участвующие в SELECT. Интересной особенностью здесь является возможность определения исходно несуществующих измерений. Это означает, что возможна такая ситуация:
|
MODEL DIMENSION BY col1, 777 NEW_DIM MEASURES col3, col4, … |
Которая означает что будет создано еще одно измерение, с единственным элементом значение которого – 777. То же самое касается показателей.
Здесь же измерениям назначаются алиасы(псевдонимы), и в дальнейшем работа с ними ведется уже через алиасы.
Теперь перейдем к правилам(rules).
Правила – это выражения определяющие заполнение ячеек многомерной модели. Левая часть правила определяет набор ячеек, значения которых необходимо изменить. Правая часть правила определяет значения, которыми будут заполнены ячейки, выбранные левой частью выражения.
Теперь рассмотрим выбор ячеек. Каждая ячейка уникально определяется уникальным набором меток – элементов измерения и задается следующим выражением:
|
meas[dim1, dim2, …] |
Т.е. для указания одной ячейки нужно по каждому измерению выбрать один элемент. Порядок указания ссылок на элементы измерений определяется порядком измерений в выражении DIMENSION BY. Существует два типа меток – позиционные и символические.
1. Позиционные(positional) метки. Позиционная ссылка – это константа, уникально определяющая элемент измерения. Например, meas[‘Элемент1’,…]. В данном случае ‘Элемент1’ – это позиционная метка. При использовании этого типа меток выбираются ячейки у которых соответствующее измерение = значению ссылки.
2. Символические(symbolic) метки. Символическая метка – это выражение, имеющее логический тип. При использовании этого типа меток будут выбраны только те ячейки, для которых результат выражения равен true. Например, meas[dim1 > 10,…]. В данном случае будут выбраны те ячейки, у которых значение элемента измерения > 10. В общем случае, не обязательно указывать выражение относительно именно соответствующего измерения. Это может быть абсолютно любое логическое выражение. Например, допустимо следующее: meas[15 > 10,…].
Набор меток и показатель – является ссылкой. Ссылки делятся на одиночные и множественные.
1. Одиночные(single) ссылки – это такие ссылки, которые уникально определяют ячейку. Например, ссылки, измерения которых определяются исключительно позиционными метками – всегда одиночные. Также ссылка остается одиночной, если кроме позиционных меток, используются уникальные символические метки. Например: meas[10 , dim2=’Элемент2’, …].
2. Множественные(multi-cell) ссылки – это такие ссылки, которые определяют набор ячеек куба. Ссылка может быть позиционной, если применяются символические метки, определяющие более одного элемента измерения. Например: meas[dim1 > 10, dim2=’Элемент2’, …].
Рассмотрим пример простейшего правила:
Допустим, есть модель, определяющая продажи товаров в различных магазинах. Исходные данные для которой таковы:
|
Sales |
Shop1 |
Shop2 |
|
FirstGood |
1 |
|
|
SecGood |
6 |
|
Т.е. продажи определены только для Shop1. Тем не менее, мы знаем, что в магазине Shop2 каждого товара было продано в два раза больше. Используя выражение MODEL эту задачу можно решить следующим образом:
|
MODEL DIMENSION BY Shops, Goods MEASURES (Sales) RULES ( Sales[‘Shop2’, ‘FirstGood’] = 2 * Sales[‘Shop1’, ‘FirstGood’] , Sales[‘Shop2’, ‘SecGood’] = 2 * Sales[‘Shop1’, ‘SecGood’] ) |
Результат будет следующим:
|
Sales |
Shop1 |
Shop2 |
|
FirstGood |
1 |
2 |
|
SecGood |
6 |
12 |
Все хорошо пока товаров 2. Если их 100 – писать подобные правила быстро надоест. В таком случае на помощь приходит функция CV() – current value - текущее значение. Эта функция используется в правой части правила и возвращает текущее значение элемента измерения в левой части. Без параметров она возвращает текущее значение того измерения, в позиции которого она указана. Пример: meas[CV(), …]. Здесь CV() вернет текущее значение первого измерения. В качестве параметра функции CV() можно задавать название измерения, текущее значение которого необходимо вернуть. Здесь может быть не совсем понятно, что означает текущее измерение. Давайте рассмотрим на примере.
Предположим, что мы имеем исходную выборку, определяющую продажу товаров в магазинах, но магазины заданы номерами и определены продажи только для первого товара. Т.е.
|
Sales |
1 |
2 |
3 |
4 |
|
FirstGood |
3 |
5 |
1 |
7 |
|
SecGood |
|
|
|
|
При этом известно, что SecondGood во всех магазинах кроме 1-го продано в 4 раза больше чем FirstGood. В перовом же магазине – в 5 раз больше.
|
Sales[‘SecGood’, Shops > 1] = 4 * Sales[‘FirstGood’, CV()], Sales[‘SecGood’, 1] = 5 * Sales[‘FirstGood’, CV()] |
Сначала, разберем первое правило. Как видно – в левой части стоит множественная ссылка, которая указывает на набор ячеек, у которых товар = ‘SecGood’, а номер магазина > 1. В таком случае (если в левой части выбирается насколько ячеек), каждой из выбранных ячеек по очереди присваивается значение, вычисляемое в правой части. Т.е. это выражение можно представить в итерационной форме следующим образом:
|
FOR (i = 2; i<= 4; i++) { Sales[‘SecGood’, i] = 4* Sales[‘FirstGood’, i]; } |
Таким образом, функция CV() – возвращает текущее значение выбранного поля на данной итерации. Соответственно в результате таблица будет выглядеть слеудющим образом:
|
Sales |
1 |
2 |
3 |
4 |
|
FirstGood |
3 |
5 |
1 |
7 |
|
SecGood |
15 |
20 |
4 |
28 |
Также, CV() может принимать в качестве аргумента – название измерения. Тогда возвращать она будет значение не текущего измерения, а переданного в качестве параметра.
Если же необходимо указать однородное по измерению правило, т.е. такое правило, где для элементов со всеми значениями измерения, необходимо указывать значения, стоит использовать выражение ANY. Также допустимо выражение вида dim1 IS ANY. Стоит только помнить, что ANY является символической ссылкой, так как фактически интерпретируется как dim IS NULL or dim IS NOT NULL, даже, если оно указывается позиционно.
Если переформулировать предыдущую задачу в следующий вид:
При этом известно, что SecondGood во всех магазинах кроме 1-го продано в 4 раза больше чем FirstGood.
То правило будет выглядеть следующим образом:
|
Sales[‘SecGood’, ANY] = 4 * Sales[‘FirstGood’, CV()] |
Итак, если подводить промежуточный итог то можно сказать: если в левой части используется множественная ссылка, то правило необходимо рассматривать как «итеративное». Т.е. что для каждого значения ячейки, входящей в диапазон, определяемый левой частью, будет рассчитано выражение, стоящее в правой части.
Теперь стоит поговорить о множественных ссылках в правой части. Тут, пожалуй, все просто: необходимо помнить, что результатом правой части правила всегда должно являться скалярное (единичное) значение. Таким образом, в случае множественной ссылки (определяющей область значений) в правой части, должна использоваться некая агрегирующая функция. Для примера сформулируем задачу:
Есть исходные данные о продажах товаром в магазинах по годам. Необходимо вычислить для каждого магазина сумму продаж по всем годам.
|
Sales |
2004 |
2005 |
2006 |
TotalYears |
|
Shop1 |
3 |
5 |
1 |
|
|
Shop2 |
6 |
10 |
2 |
|
Т.е. необходимо для каждого магазина, в ячейку TotalYears записать значение суммы всех ячеек по этому магазину. В правилах MODEL это будет выглядеть так:
|
DIMENSION BY (Shops, Years) MEASURES (Sales) Rules( Sales[ANY, ‘TotalYears’]= Sum(Sales)[CV(), ANY] ) |
Если бы в исходных данных уже содержались бы данные по TotalYears, и было бы необходимо их затереть, то правило выглядело бы следующим образом:
|
DIMENSION BY (Shops, Years) MEASURES (Sales) Rules( Sales[ANY, ‘TotalYears’]= Sum(Sales)[CV(), Years != ‘TotalYears’] ) |
Таким образом, на выходе мы получим:
|
Sales |
2004 |
2005 |
2006 |
TotalYears |
|
Shop1 |
3 |
5 |
1 |
9 |
|
Shop2 |
6 |
10 |
2 |
18 |
Вообще, что касается правой части, то тут могут стоять практически любые выражения. Например, NVL, CASE, DECODE и т.д.
Скрипты старался оформалять ввиде процедур. Так, чтобы было максимально удобно их использовать в своем коде в дальнейшем.
Также хотелось бы услышать интересные задачи для OMB+. Пишите в комментариях.
Если у Вас есть свой набор часто применяемых OMB+ скриптов то пишите на мыло gostest[масямба]gmail[dot]com.
Итак, сначала несколько скриптов для работы с оператормаи в маппингах.
Первый скрипт IOC просто копирует все входящие аттрибуты оператора из одной группы в другую. Это удобно для построения выражений.
|
proc IOC {map oper {ingrp “INGRP1″} {outgrp “OUTGRP1″} } { |
Скрипт CPEXPR позволяет скопировать выражение из одного маппинга в другой.
|
proc CPEXPR {from to {name “EXPRESSION”} {new_name “”}} { |
Скрипт UPDNAME просто позволяет произвести замену по рег.выражению названий аттрибутов.
|
proc UPDNAME {map from to} { |
Скрипт SYNCR позволяет в текущем модуле синхронизировать все таблицы с таблицами из указываемого модуля.
Это может пригодиться в случае наличия нескольких модулей с таблицами и необходимости синхронизации с ними.
|
proc SYNCR {srcmod} { |
Параметры таблицы в маппинге
|
proc DSCRT {map {tabname “”}} {
|
Параметры представления в маппинге
|
proc DSCRV {map {tabname “”}} { |
Параметры аггрегатора в маппинге
|
proc DSCRAG {map {aname “”}} { |
Параметры ПРЕ- и ПОСТ- маппинг процедур.
|
proc DSCRPMP {map} { |
Параметры локейшнов
|
proc DSCRLOC { con } { |
Параметры аттрибутов
|
proc DSCRAT {map {aname “”}} { |
Параметры маппингов
|
proc DSCRM {map} { |
Параметры модулей
|
proc DSCRMOD {map} { |




