JavaScript in Quick Picks in Oracle APEX

Wer diesen Blog schon öfter besucht hat, wird das Feature der Quick Picks bereits kennen. Diese dienen dazu, Usereingaben zu optimieren, indem mittels eines Klicks auf ein entsprechendes Quick Pick direkt ein Wert in ein Feld eingetragen wird.

 Quickpicks_dynamic_01

Ein Nachteil der Quick Picks ist aber, dass standardmäßig nur vorgefertigte (sprich beim Rendern der Seite bereits definierte) Werte eingesetzt werden können. Auch im letzten Blogeintrag über Quick Picks, in dem wir die Anzahl der Quick Picks dynamisch variierbar gemacht haben, haben wir dieses Problem.
Würde man mit den Quick Picks nun beispielsweise immer den aktuellen Wert eines anderen Feldes übernehmen wollen, so ist dies nicht einfach so möglich.
Angenommen es wäre zum Beispiel gewünscht, den Wert des Feldes „Betrag“ (in der oben angeführten Abbildung) mittels eines Klicks auf einen Quick Pick in das Feld „Betrag bezahlt“ zu übernehmen.
Mit den bisher bekannten Methoden kann man zwar das Feld als Quick Pick angeben (siehe folgende Abbildung), allerdings wurde der zu setzende Betrag bereits beim Rendern der Seite erstellt. War dieser davor „1234“ und wurde nachträglich vom Endbenutzer auf „12345678“ geändert, so wäre auch der Quick Pick immer noch mit dem Wert „1234“ definiert und zwar solange, bis die Region (oder die gesamte Seite) erneut geladen werden.

Quickpick_Eigenschaften_marked

Wie Leser des letzten Beitrages über Quick Picks bereits wissen, arbeiten diese selbst mit JavaScript-Code. Es ist also möglich, beim Erstellen eigener Quick Picks beliebigen JavaScript-Code auszuführen. Anstatt also einen Quick Pick zu generieren, der mittels JavaScript ein Item mit einem vordefinierten Wert befüllt, kann dieser Code auch direkt den Wert zum aktuellen Zeitpunkt aus einem anderen Item auslesen.
Der HTML-Code für das entsprechende Quick Pick würde in diesem Beispiel also wie folgt aussehen:

<span class="apex-quick-picks"><a href="javascript:$s('#CURRENT_ITEM_NAME#',$v('P1_BETRAG'), 'JS-QP')">JS-QP</a></span>

„$s“ ist die in Oracle APEX verwendete JavaScript-Methode zum setzten von Items, während „$v“ die Methode zum Auslesen eines Wertes eines beliebigen Items verwendet werden kann. In diesem Beispiel wird also der Wert aus Item „P1_BETRAG“ in das Item „#CURRENT_ITEM_NAME#“ gesetzt.
„#CURRENT_ITEM_NAME#“ ist nur ein Platzhalter für das gewünschte Item. Wenn dieser HTML-Code nun im Post-Text eines gewünschten Items eingetragen wird (in diesem Fall im Item „P1_BETRAG_BEZAHLT“), wird dieser durch den korrekten Itemnamen ersetzt.
Quickpicks advanced

Um das Layout der Quick Picks korrekt darzustellen muss noch im selben Eigenschaftsfenster im Bereich „Quick Picks“ das Flag „Show Quick Picks“ auf „Yes“ gesetzt werden.

Nun wird der entsprechende HTML-Code mittels CSS in die bekannte Formatierung und Positionierung gebracht. Bei Klick auf den Quick Pick wird immer der aktuelle Wert des Feldes „Betrag“ übernommen, ganz egal, wann dieser geändert wurde.

Dynamic Quick Picks

Um die Quick Picks nun wieder dynamischer zu erstellen, kann man natürlich auf „Shortcuts“ in den Shared Components“ zurückgreifen und die Quick Picks über PL/SQL dynamisch erstellen.

Oracle APEX

Wenn man einen neuen Shortcut erstellt muss als Type „PL/SQL Function Body“ ausgewählt und im Feld „Shortcut“ ein entsprechender PL/SL-Code geschrieben werden.

declare
  v_qp varchar2(32767);
begin
  v_qp := apex_string.format(q'{%1}', 'P1_BETRAG', 'JS-QP');
  return apex_string.format('%0', v_qp);
end;

Create Shortcut

Danach muss der Name des Shortcuts noch im Eigenschaftsfenster des gewünschten Items unter „Post Text“ mittels Anführungszeichen angegeben werden, um APEX mitzuteilen, dass hier der aus dem Shortcut generierte HTML-Code ausgegeben werden soll.

Advanced Post

Man erhält dasselbe gewünschte Ergebnis, es ist aber bei der Umsetzung mit Shortcuts möglich, beliebig viele Quick Picks mit unterschiedlicher Funktion zu erstellen, ohne ständig das Item anpassen zu müssen.

Dynamic Quick Picks

Generell ist es so nun möglich, jeden erdenklichen JavaScript-Code in Quick Picks zu verpacken, es muss nicht einmal eine Quick Pick-Funktionalität dahinterstehen.

Dynamische Quick Picks in Oracle APEX

Wer seine Web-Applikation in Oracle APEX auf Usability optimiert, wird wahrscheinlich schon einmal über das Feature der sogenannten Quick Picks gestoßen sein.
Unter Quick Picks versteht man vordefinierte Usereingaben, die ein Endanwender mittels Mausklick auslösen kann und das entsprechende Feld mit einem vordefinierten Wert befüllt, ohne diesen extra eintippen zu müssen.

 Quickpicks_dynamic_01

Konfiguriert werden diese Quick Picks über das Eigenschafts-Fenster des jeweiligen Page-Items.

Quickpick Eigenschaften

Quick Picks können also die Eingabe des Endbenutzers ein wenig beschleunigen, allerdings sieht es auf den ersten Blick so aus, als ob nur statische Werte vergeben werden können. Dies ist nicht ganz korrekt, denn mit der üblichen Notation in Oracle APEX können auch Werte aus beliebigen Items angesprochen werden (Kaufmännisches Und + Item-Name + Punkt), dies sieht zum Beispiel wie folgt aus:

Quickpick_Eigenschaften_marked

Diese Art der Definition führt dazu, dass der Wert aus dem jeweiligen Item als Quick Pick gesetzt wird.

Quickpick dynamic zweiter wert

Einzige Bedingung hierfür ist allerdings, dass der Wert bereits beim Aufbau der Seite gesetzt wird, denn der Quick Pick wird vorab mit dem Wert des entsprechenden referenzierten Items erstellt und ändert sich nicht dynamisch mit. Auch ist die Anzahl der Quick Picks vorab auf die statisch definierten Labels und Values beschränkt, es ist also auf diese Art beispielsweise nicht möglich dynamisch einmal zwei und einmal drei Quick Picks unter einem Feld anzuzeigen.

Um nun beliebig viele Werte dynamisch als Quick Picks anzuzeigen, muss man etwas tiefer in die Trickkiste greifen. Um dies zu ermöglichen, bedient man sich dem Wissen, dass die Quick Picks über eine CSS-Klasse positioniert und gestaltet werden und der entsprechende HTML-Code einfach an das gewünschte Item angehängt wird. Mit diesem Wissen ist es nun möglich, die Quick Picks eigenständig zu erstellen.

Für die hier verwendete Testseite wurde eine simple Tabelle erstellt, welche beliebig viele Werte und Bezeichnungen für Quick Picks enthält. Die Tabelle wurde QP_TAB genannt und beinhaltet aktuell folgende Werte:

Werte Tabelle Quickpick

Ziel soll es nun sein für jede Zeile dieser Tabelle einen separaten Quick Pick für ein Item zu generieren. (Allgemein ist es egal, woher die Daten für die Quick Picks stammen, einzige Bedingung ist, dass man diese mittels PL/SQL ermitteln kann.)

Um die Anzahl der Quick Picks nun auch dynamisch umzusetzen zu können, benötigt man ein weiteres Oracle APEX Feature: die sogenannten „Shortcuts“. Diese befinden sich in den Shared Components wie im folgenden Screenshot ersichtlich ist.

Oracle Apex

Klickt man nun auf Shortcuts, kann man auf der Folgeseite ein neues Objekt erstellen.

Create Shortcut

Wichtig hierbei ist, dass als „Type“ „PL/SQL Function Body” ausgewählt wird und dann im Feld „Shortcut“ ein entsprechender Code geschrieben wird, der die gewünschten Daten in eine entsprechende HTML-Struktur bringt. Der Code für das hier verwendete Beispiel sieht nun folgendermaßen aus:

declare
  v_qp varchar2(32767);
begin
  for rec_qp in (select bezeichnung, wert from qp_tab) loop
    if v_qp is not null then
      v_qp := v_qp || ', ';
    end if;
      v_qp := v_qp || apex_string.format(q'{%1}', rec_qp.wert, rec_qp.bezeichnung);
  end loop;
  return apex_string.format('%0', v_qp);
end;

Wichtig ist hier eigentlich nur, dass die einzelnen Links (die resultierenden Quick Picks) in einem Span-Container der CSS-Klasse „apex-quick-picks“ liegen.

Der eigentliche Link ist dann Javascript-Code, welcher mit „$s“ das Setzen eines Items mit der entsprechenden Angabe des Wertes umsetzt. (Der Platzhalter „#CURRENT_ITEM_NAME#“ wird im folgenden Schritt aufgelöst.)

Wieder zurück auf der Seite kann man im Eigenschaftsfenster des gewünschten Items nun im „Advanced“-Bereich im „Post Text“-Feld den Namen des zuvor erstellten Shortcuts in Anführungszeichen eintragen. Dadurch wird der durch den Shortcut generierte HTML-Text nach dem Item im Quellcode eingefügt. Oracle APEX weiß nun auch, dass der Platzhalter „#CURRENT_ITEM_NAME#“ mit dem Namen des Items ersetzt wird (diese Funktionalität des Ansprechens des Item-Namens ist über den Post Text allgemein möglich).

Bei den Quick Picks werden diesmal keine statischen Werte angegeben (da der Code für die Quick Picks ja bereits durch den Shortcut an das Item angehängt wurde). Um aber die Positionierung des durch den Shortcut erstellten HTML-Code auch korrekt anzuzeigen, wird das Flag „Show Quick Picks“ auf „Yes“ gesetzt.

So erhält man die dynamisch erstellten Quick Picks:

Würde man in Tabelle QP_TAB nun einen weiteren Eintrag erfassen, würde dieser auch entsprechend auf der APEX-Seite als weiterer Quick Pick erscheinen, ohne dass man dafür das Item anpassen müsste.

Die einzige Limitierung der Anzahl der Quick Picks entsteht durch die Zeichenanzahl des VARCHAR2 im angelegten Shortcut.

Oracle Active Dataguard and Cloning of PDBs – the easy way

In diesem Artikel werden wir mehrere Features der 12.1.0.2 Datenbank beschreiben.

Active Data Guard, ein kostenpflichtiges Feature um Reporting auf die Standby Seite zu bringen (und tolle Administrationsfeatures beinhaltet, wie z. B. vereinfachtes Cloning (wie im Artikel beschrieben) oder Global Data Services).
Multitenant, ein Feature, das man für Datenbank-Konsolidierung benutzt und neue Möglichkeiten zur Administration anbietet.
PDB Cloning in einem ADG Environment: Clones kann man als eine Art von Backup sehen, wenn man Applikation Upgrades oder irgendwelche invasiven Tätigkeiten durchführt und einen schnellen Fallback haben möchte.
In höheren Versionen gibt es bessere Möglichkeiten, wie Flashback PDB oder PDB PITR.

Dieser Artikel dient zur Verbesserung des Daily Business, um Kunden zufriedener machen zu können. Wenn man schon Lizenzen besitzt, sollte man auch die zur Verfügung stehenden Features benutzen, nicht wahr?

Manche unserer Kunden haben Multitenant und Active Data Guard lizensiert, wobei man Multitenant sehr selten für Cloning oder verschiedene andere Tätigkeiten benutzt. Es ist auch klar, weshalb dies so ist, denn viele Kunden kennen diese Features nicht. Es gehört zu unseren Aufgaben, unseren Kunden diese Features vorzustellen und deren Einsatzgebiete zu erklären. Ein glücklicher Kunde ist der beste Kunde.

Bis vor kurzen war es so, dass wir entweder Restore Points erstellt haben, die in Wahrheit in einem 12.1.0.2 CDB Environment nicht wirklich benutzbar sind, denn man kann nur den ganzen Container zurückspielen oder wir mussten Duplicates (Restore) erstellen, was Zeit beanspruchte und unnötige Downtime verursachte. Da wir gern diese Tätigkeiten optimieren, ist es nötig Kunden zu informieren wie man es in Zukunft machen könnte, damit die Projekte weitergehen und jeder zufrieden ist.
Dieses Dokument beschreibt die Möglichkeiten, die sehr einfach sind, wenig Zeit kosten, nur freien Storage und einen DBA brauchen, der diese Features kennt.

Active Data Guard erlaubt es den Kunden das Reporting auf die Disaster Systeme zu schwenken, damit die Performance der Produktivsysteme, bei performance-lastigen Reports, nicht beeinträchtigt wird. Es ermöglicht uns weiterhin die Dinge zu tun, die schnell sind, Downtimes vermeiden und Service Agreements leicht erfüllen.

Wenn man in einem Active Data Guard Environment eine lokale PDB klont, werden die Standby PDBs automatisch erstellt und in einer 12.1.0.2 sind dazu nur 5 Kommandos notwendig (denn man muss die PSORGER PDB wieder READ WRITE öffnen).
In höheren Versionen kann man einen Hot Clone erstellen, wenn man Local Undo benutzt, das heißt Zero Downtime.

 

SYS@CDB1 SQL> alter pluggable database psorger close immediate instances=all ;
Pluggable database altered.
SYS@CDB1 SQL> alter pluggable database psorger open read only instances=all ;
Pluggable database altered.
SYS@CDB1 SQL> create pluggable database psorgerc from psorger ;
Pluggable database created.


[oracle@exa2vm01 ~]$ tail -f $al
  Mem# 0: +DATAX6C1/E2CDB1/ONLINELOG/group_22.2191.970881745
  Mem# 1: +RECOX6C1/E2CDB1/ONLINELOG/group_22.9224.970881745
1: +RECOX6C1/E2CDB1/ONLINELOG/group_22.9224.970881745
Thu Sep 10 20:36:27 2020
Recovery created pluggable database PSORGERC
Thu Sep 10 20:36:54 2020
Recovery copied files for tablespace SYSTEM
Recovery successfully copied file +DATAX6C1/E2CDB1/AEFAD6D19935CDF7E0537D001BAC3B4F/DATAFILE/system.2920.1050784587 from +DATAX6C1/E2CDB1/647EDCDA7FB28F8DE0538B001BACEE69/DATAFILE/system.1628.967329427
Datafile 23 added to flashback set
Successfully added datafile 23 to media recovery
Datafile #23: '+DATAX6C1/E2CDB1/AEFAD6D19935CDF7E0537D001BAC3B4F/DATAFILE/system.2920.1050784587'
Thu Sep 10 20:37:23 2020
Recovery copied files for tablespace SYSAUX
Recovery successfully copied file +DATAX6C1/E2CDB1/AEFAD6D19935CDF7E0537D001BAC3B4F/DATAFILE/sysaux.2929.1050784615 from +DATAX6C1/E2CDB1/647EDCDA7FB28F8DE0538B001BACEE69/DATAFILE/sysaux.1629.967329427
Datafile 24 added to flashback set

Wie man sieht reichen 3 Kommandos aus, um eine Pluggable Database zu klonen (EE 12.1.0.2 mit ADG). Eine 100GB Datenbank kann auf einer Exadata in wenigen Minuten geklont werden und somit verfügt man über eine „Point in Time“ Kopie der PDB, die man im Fall eines schnellen „Restores“ wieder benutzen kann. Es handelt sich hierbei nicht um einen Restore, aus Kundensicht sieht es jedoch so aus, als würde man etwas aus der Vergangenheit zurückbringen. Letztendlich wird hierbei nur ein mächtiges Feature der Enterprise Edition von Oracle genutzt.

Natürlich gibt es aber auch Situationen, in denen ein Restore erforderlich wird, da keine Clones erstellt wurden. In diesem Fall ist es am besten einen DUPLICATE vom Backup zu erzeugen. Dies ist einfach umzusetzen, braucht nur wenige Kommandos und ist automatisiert, so dass der DBA wenig Zeit mit der Kommandoeingabe verliert. Mehr Informationen zu diesem Thema folgen in einem weiteren Artikel.

Jetzt werden wir uns aber dem Thema cloning weiter widmen und zeigen in wenigen Kommandos, wie wir aus dem Duplikat die Pluggable Database wieder in den korrekten Container einfügen können.

Das Scenario beinhaltet:
1x Active Data Guard Configuration (12.1.0.2.200114) – CDB1 mit mehreren PDBs, PDB1, PDB2, PDB3
1x Duplikat der CDB Point in Time Recovered (12.1.0.2.200114) – CDBDUP mit PDB1

Die technischen Schritte um die PDB1 aus der CDBDUP in die CDB1 zu kopieren sind folgende:

1. In der CDB1 löschen wir die PDB1

alter pluggable database pdb1 close immediate instances=all ;
drop pluggable database pdb1 including datafiles ;

2. In der CDBDUP der PDB1 erstellen wir einen Benutzer mit Berechtigungen zum Klonen

ALTER SESSION SET CONTAINER=pdb1 ;
CREATE USER remote_clone_user IDENTIFIED BY remote_clone_user;
GRANT CREATE SESSION, CREATE PLUGGABLE DATABASE TO remote_clone_user;

3. In den tnsnames.ora beider DB Systeme definieren wir einen Connect String der im Scenario benutzt wird. Es ist gut nicht produktive Connect Strings zu benutzen, so weiß man zu 100% wo man sich einloggt

PDB1_PS=(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=dbhost)(PORT=1521))(CONNECT_DATA=(SID=CDBDUP)(SERVICE_NAME=pdb1)(INSTANCE_NAME=CDBDUP)))

4. In der CDB1 auf der Standby Seite setzen wir einen Parameter damit die PDB automatisch in das Standby Environment kopiert wird.

alter system set standby_pdb_source_file_dblink='pdb1_ps' ;

5. In der CDBDUP setzen wir die PDB1 in READ ONLY Modus (nach Duplicate ist die DB im MOUNT Zustand, in höheren Versionen ist diese Read Only Restriction nicht mehr da, da könnte man dann einen Relocate zB. machen)

alter pluggable database PDB1 open read only ;

6. In der CDB1 erstellen wir einen Database Link zu der PDB1 im CDBDUP

create database link pdb1_ps connect to remote_clone_user identified by remote_clone_user using 'pdb1_ps' ;

7. Wir erstellen einen Klon der PDB in der CDB1 und warten bis es fertig wird. Auf einer Exadata X6-2 dauerte eine 110GB PDB 10 Minuten. Im Background wird der Database Link für den Klon auf die Standby Seite benutzt.

create pluggable database PDB1 from pdb1@pdb1_ps ;

8. Wir öffnen die PDB1 und sind mit der Bereitstellung der PDB1 fertig. Wir öffnen die PDB mit dem Application Service damit es auf den korrekten Instanzen startet (im RAC kann es 1 Node sein oder auch 10)

alter pluggable database PDB1 close immediate instances=all ;
srvctl start service -db CDB1 -pdb pdb1_svc

Somit haben wir die PDB1 wieder in Betrieb genommen und die Welt ist für heute gerettet.
In einem Data Guard Environment geht es leider nicht so einfach, man muss manuell die Datafiles auf die Standby Seite transferieren. Um mehr Informationen darüber zu erfahren bitte den Oracle Note durchlesen.
Using standby_pdb_source_file_dblink and standby_pdb_source_file_directory to Maintain Standby Databases when Performing PDB Remote Clones or Plugins (Doc ID 2274735.1)

Oracle Statspack verbessern: Historische Pläne – Teil 2

Grundlegendes: Ändern des Statspack Code

English version here : Improving Statspack: Add Support for Plan Stability

Natürlich sollte man den Statspack Code nicht leichtfertig ändern. Das wird Niemand wollen. Die hier besprochene Änderung erlaubt es, schnell und ohne großes Nachdenken einen Plan aus der Vergangenheit wieder her zu stellen.

Für manche Applikationen und Datenkonstellationen ist diese Möglichkeit wirklich wichtig.
In diesem Fall sollte man es sich gründlich überlegen, ob man die hier beschriebene Veränderung nicht doch machen will.

Schließlich hat man den Source Code des Statspack. Man sollte die originale Codeversion behalten und kann daher jederzeit den Originalcode schnell wiederherstellen. Die zusätzliche optionale Spalte stört da nicht.

Ausgangslage

Bei meinem Webinar „The good plan and the bad Plan” für die DOAG ging es darum, gute Pläne aus der Vergangenheit wieder zu aktivieren.

Kann man die AWR Daten lesen, so kann man beispielsweise aus den DBA_HIST Daten mittels dbms_xplan.display_awr ein Outline erzeugen. Dieses Outline kann man dann in aktuelle Pläne einpflanzen.

Ich wurde damals gefragt, ob eine solche Methode auch auf einer Oracle Standard Edition Datenbank funktioniert.

Mit dem SQL_PATCH [2] gibt es eine legale Möglichkeit, ein Outline in einen SQL Befehl einzupflanzen.

Die Frage ist nur, wie man auf einer Oracle Standard Edition zu einem Outline eines alten Planes kommt.
Gibt es mit Statspack eine Funktion ähnlich dem dbms_xplan.display_awr?

Uwe Küchler, alias Oraculix hat eine sehr kreative Lösung parat [1].

Man verwendet einfach dbms_xplan.display im Zusammenhang mit einer Suchfunktion:

select * from table(dbms_xplan.display(
  table_name   => 'perfstat.stats$sql_plan',
  statement_id => null,
  format       => 'ALL -predicate -note',
  filter_preds => 'plan_hash_value = '|| &&phv
);

Man muss dazu noch eine Spalte in der Tabelle stats$sql_plan ergänzen, die Einzelheiten finden Sie im Oraculix Blog.

Was passiert nun, wenn man jetzt einfach im Format noch +OUTLINE angibt?
Dann müsste man doch das Outline bekommen. Da könnte man dann über einen SQL_PATCH in ein Statement schreiben und voila, hat man den alten Plan zurück.

Oder etwa nicht?

Nein, die Format Anweisung wird scheinbar einfach ignoriert.
Der Grund hierfür ist, dass in den Statspack Plänen einige Spalten fehlen. Konkret geht es hier um die Spalte OTHER_XML, die nicht mitgespeichert wird.

Die Spalte other_xml

In dieser Spalte findet sich unter anderen das Outline. Wer also die sehr wichtige Funktion der historischen Pläne haben will, darf nicht davor zurückscheuen, den Statspack code zu verändern. Wenn Oracle schon die der MOS Note 2182680.1 vorschlägt das Statspack zu verändern, weshalb sollte wir nicht ein wenig weiter gehen?

Wir beginnen, in dem wir die Spalte hinzufügen.

ALTER TABLE perfstat.stats$sql_plan ADD timestamp INVISIBLE AS (cast(NULL AS DATE));
ALTER TABLE perfstat.stats$sql_plan ADD OTHER_XML CLOB;

Danach müssen wir den Code anpassen. Analog zum Vorgehen in MOS Note 2182680.1 habe ich zuerst eine Kopie des aktuellen Codes gemacht.
Wieder ist es das Skript spcpkg, welches angepasst werden muss.

Zu ändern ist das Insert Statement beginnend mit:

insert into stats$sql_plan
                 ( plan_hash_value
                 , id
                 , operation
                 , options
                 , object_node
                 , object#

Hier muss die Spalte Other_XML ergänzt werden. Dabei gibt es jedoch eine Schwierigkeit.

Im Select Teil des Inserts steht eine Maximum Funktion, z.B. so:

                 , max(sp.operation)
                 , max(sp.options)
                 , max(sp.object_node)
                 , max(sp.object#)
                 , max(sp.object_owner)
                 , max(sp.object_name)
                 , max(sp.object_alias)
                 , max(sp.object_type)

Die Maximum Funktion ist ziemlich sicher nur dafür da, um duplizierte Sätze zu vermeiden.

Also schreiben wir doch einfach: Max(other_xml), oder?

Prompt kommt ORA-00932. Der Grund dafür ist, dass CLOB nicht sortierbar sind, daher kann auch kein Maximum berechnet werden.

ANY_VALUE

Natürlich gäbe es noch andere Möglichkeiten, um die Werteliste eindeutig zu machen.
Beispielsweise analytische Funktionen wie row_number ().

Ich wolle jedoch nicht so viel am Original Code verändern.

Mir fiel eine Idee aus dem Oracle Technologie Network ein, bei der es darum ging, für genau solche Probleme eine neue Art von Gruppenfunktion ein zu führen, genannt ANY_VALUE [3.].

Die Idee wird anscheinend mit Version 20c umgesetzt. Zu schade. Mit Version 19 wäre besser.

Als ehemaliger Oracle Mitarbeiter weiß ich jedoch, dass manchmal die Vorgängerversionen undokumentiert schon Änderungen der Folgeversion enthalten.

Und in der Tat: any_value gibt es schon.
Leider wirft auch diese Implementation völlig unnötig ORA-00932.

Ich habe Chris Saxon darauf aufmerksam gemacht und hoffe das Oracle die Implementierung noch ändert.
Nun, wenn Oracle nicht liefert muss ich das selbst machen.

Ich habe mir über das User-Defined Aggregate Functions Interface die Funktion selbst definiert und any_lob genannt.

Den Code finden sie am Ende dieses Blogs. Hier erst einmal der vollständige Insert Befehl. Die alle vom Standard abweichenden Änderungen sind mit Rot gekennzeichnet. Die Hints habe ich im vorigen Blog zum Thema Statspack beschrieben.

            insert into stats$sql_plan
                 ( plan_hash_value
                 , id
                 , operation
                 , options
                 , object_node
                 , object#
                 , object_owner
                 , object_name
                 , object_alias
                 , object_type
                 , optimizer
                 , parent_id
                 , depth
                 , position
                 , search_columns
                 , cost
                 , cardinality
                 , bytes
                 , other_tag
                 , partition_start
                 , partition_stop
                 , partition_id
                 , other
                 , other_xml                 
                 , distribution
                 , cpu_cost
                 , io_cost
                 , temp_space
                 , access_predicates
                 , filter_predicates
                 , projection
                 , time
                 , qblock_name
                 , remarks
                 , snap_id
                 )
            select /*+ leading(spu@np ssp@sq  s sp) */
                   new_plan.plan_hash_value
                 , sp.id
                 , max(sp.operation)
                 , max(sp.options)
                 , max(sp.object_node)
                 , max(sp.object#)
                 , max(sp.object_owner)
                 , max(sp.object_name)
                 , max(sp.object_alias)
                 , max(sp.object_type)
                 , max(sp.optimizer)
                 , max(sp.parent_id)
                 , max(sp.depth)
                 , max(sp.position)
                 , max(sp.search_columns)
                 , max(sp.cost)
                 , max(sp.cardinality)
                 , max(sp.bytes)
                 , max(sp.other_tag)
                 , max(sp.partition_start)
                 , max(sp.partition_stop)
                 , max(sp.partition_id)
                 , max(sp.other)
                 , any_lob(sp.other_xml)
                 , max(sp.distribution)
                 , max(sp.cpu_cost)
                 , max(sp.io_cost)
                 , max(sp.temp_space)
                 , 0 -- should be max(sp.access_predicates) (2254299)
                 , 0 -- should be max(sp.filter_predicates)
                 , max(sp.projection)
                 , max(sp.time)
                 , max(sp.qblock_name)
                 , max(sp.remarks)
                 , max(new_plan.snap_id)
              from (select /*+ QB_NAME(NP)  */  
                           spu.plan_hash_value
                         , spu.hash_value    hash_value
                         , spu.address       address
                         , spu.text_subset   text_subset
                         , spu.snap_id       snap_id
                      from stats$sql_plan_usage spu
                     where spu.snap_id         = l_snap_id
                       and spu.dbid            = p_dbid
                       and spu.instance_number = p_instance_number
                       and not exists (select /*+ QB_NAME(SQ)  */ *
                                         from stats$sql_plan ssp
                                        where ssp.plan_hash_value 
                                            = spu.plan_hash_value
                                      )
                   )          new_plan
                 , v$sql      s      -- join reqd to filter already known plans
                 , v$sql_plan sp
             where s.address         = new_plan.address
               and s.plan_hash_value = new_plan.plan_hash_value
               and s.hash_value      = new_plan.hash_value
               and sp.hash_value     = new_plan.hash_value
               and sp.address        = new_plan.address
               and sp.hash_value     = s.hash_value
               and sp.address        = s.address
               and sp.child_number   = s.child_number
             group by 
                   new_plan.plan_hash_value, sp.id
             order by
                   new_plan.plan_hash_value, sp.id; -- deadlock avoidance

Zusammenfassung

Der Code scheint recht gut zu funktionieren.

Nur eine Sache war etwas ärgerlich: Immer, wenn ich Syntaxfehler im Insert hatte, musste ich auch die Funktion any_lob neu kompilieren.

Hinweis: Man muss den Snap Level auf 6 oder höher schalten, damit das Statspack Pläne sammelt.  Z.B.:

EXECUTE statspack.snap(i_snap_level => 7);

Create or replace type any_lob_type as object
(
  v_clob CLOB,

  static function ODCIAggregateInitialize
    ( sctx in out any_lob_type )
    return number ,

  member function ODCIAggregateIterate
    ( self  in out any_lob_type ,
      value in     CLOB
    ) return number ,

  member function ODCIAggregateTerminate
    ( self        in  any_lob_type,
      returnvalue out CLOB,
      flags in number
    ) return number ,

  member function ODCIAggregateMerge
    ( self in out any_lob_type,
      ctx2 in     any_lob_type
    ) return number
);
/

create or replace type body any_lob_type
is
  static function ODCIAggregateInitialize
  ( sctx in out any_lob_type )
  return number
  is
  begin
    sctx := any_lob_type( null ) ;
    return ODCIConst.Success ;
  end;

  member function ODCIAggregateIterate
  ( self  in out any_lob_type ,
    value in     CLOB
  ) return number
  is
  begin
    self.v_clob := value ;
    return ODCIConst.Success;
  end;

  member function ODCIAggregateTerminate
  ( self        in  any_lob_type ,
    returnvalue out CLOB ,
    flags       in  number
  ) return number
  is
  begin
    returnvalue := self.v_clob;
    return ODCIConst.Success;
  end;

  member function ODCIAggregateMerge
  ( self in out any_lob_type ,
    ctx2 in     any_lob_type
  ) return number
  is
  begin
      return ODCIConst.Success;
  end;
end;
/

create or replace function any_lob
  ( input CLOB )
  return CLOB
  deterministic
  parallel_enable
  aggregate using any_lob_type
;
grant execute on any_lob to public;
create public sysnonym for sys.any_lob;

Quellen

 

Oracle Statspack verbessern: Schnellere Snapshots – Teil1

Oracle Statspack verbessern: Schnellere Snapshots – Teil1

Ausgangslage: Langsamer Statspack Snapshot

Auf verschiedenen Standard Edition Datenbanken sieht man immer wieder den Statspack Snapshot lange laufen.

Dies ist für mich als Consultant sehr unangenehm.
Oft muss ich den Kunden sagen, dass das Statspack auf einer Standard Edition Datenbank unverzichtbar ist.

Und dann sieht mein Kunde, wie der Statspack Snapshot minutenlang einen der kostbaren, limitierten Cores blockiert.

Ich beschloss, etwas dagegen zu unternehmen.
Bei näherer Betrachtung erkennt man zwei Statements, die die Datenbank belasten.

In erster Linie ist dies:

INSERT INTO stats$sql_plan

Aber auch dieser Befehl läuft lange:

INSERT INTO stats$seg_stat

Grundsätzliches: Hints im Statspack

Man hört oft, dass man Hints so wenig wie möglich machen soll und lieber andere Mittel verwenden soll, um den Optimizer zu steuern.

Gute Statistiken zum Beispiel.
Ich bin auch dieser Meinung.
Allerdings gibt es auch Ausnahmen.

Bei Tools wie Statspack kann man sich nicht darauf verlassen, dass die Statistiken immer aktuell sind.

Es werden auch interne Tabellen X$ verwendet. Nicht jeder DBA macht fixed table stats und selbst wenn, schwanken diese Statistiken oft stark.

Ein Tool wie Statspack muss immer zuverlässig laufen.
Glücklicherweise ist bei den zwei Statspack Queries die wir betrachten werden, ziemlich klar wie der Plan auszusehen hat.

Hints stellen also kein großes Risiko dar. Jedoch habe ich dennoch so wenig wie möglich festgelegt.

Statement : INSERT INTO stats$sql_plan

Das Problem wird in der MOS Note 2182680.1 behandelt.

In dieser Note wird vorgeschlagen eine alternative Implementation des Statspack Package (SCPPKG.SQL) herunter zu laden und zu implementieren.

Diese Implementation enthält einen geänderten Hint, den ich im folgenden Beispiel mit Rot hervorgehoben habe.

Vom ganzen Insert Statement zeige ich nur den SELECT Teil, weil der Eintrag sonst zu lange wird.
Auch vom Select Teil habe ich die Liste der Spalten gekürzt, damit der Code Teil übersichtlich bleibt.

SELECT /*+ no_merge(new_plan) leading(new_plan s sp) use_nl(s) use_nl(sp) */
            new_plan.plan_hash_value,
            sp.id,
            MAX(sp.operation),
            MAX(sp.options),  
            . . .
            MAX(new_plan.snap_id)
        FROM
            (
                SELECT /*+ index(spu) */
                    spu.plan_hash_value,
                    spu.hash_value    hash_value,
                    spu.address       address,
                    spu.text_subset   text_subset,
                    spu.snap_id       snap_id
                FROM
                    stats$sql_plan_usage spu
                WHERE
                    spu.snap_id = :b3
                    AND spu.dbid = :b2
                    AND spu.instance_number = :b1
                    AND NOT EXISTS (
                        SELECT /*+ nl_aj */
                            *
                        FROM
                            stats$sql_plan ssp
                        WHERE
                            ssp.plan_hash_value = spu.plan_hash_value
                    )

Wie unschwer zu erkennen ist, befinden sie relevanten Suchkriterien auf der Tabelle stats$sql_plan_usage mit dem Alias spu.

Es ist also wichtig, dass im Execution Plan mit dem Lesen dieser Tabelle begonnen wird.

Der Hint Leading(new_plan .. ist also folgerichtig.

Das Problem besteht jedoch darin, dass new_plan keine Tabelle, sondern der Name einer Unterfrage ist.
Obwohl Version 19c den Hint als korrekt meldet, wird er in tieferen Versionen oft nicht akzeptiert.

Der Alias für den Hint wäre eigentlich spu, jedoch ist dieser in der Hauptabfrage nicht zugänglich, weil die Tabelle stats$sql_plan_usage in einer Unterabfrage angesprochen wird. (Achtung: wenn ein Alias vorhanden ist, muss der Alias im Hint angegeben werden und nicht der Tabelle Name.)

Diese Unterabfrage bildet einen eigenen Queryblock und dessen Inhalt ist von der Hauptabfrage nicht direkt referenzierbar.

In der Tat zeigt der Plan des Insert Befehles, dass der Leading Hint ignoriert wird.

Hier ein Beispiel von einem unserer Kunden:

Operation | Name | A-Time 
-------------------------------------------------------------------------------
INSERT STATEMENT | |00:05:07.72
 LOAD TABLE CONVENTIONAL | STATS$SQL_PLAN |00:05:07.72
  SORT GROUP BY | |00:05:07.72
   NESTED LOOPS | |00:05:07.71
    NESTED LOOPS | |00:05:07.71
     FIXED TABLE FULL | X$KGLCURSOR_CHILD |00:00:02.30
     VIEW PUSHED PREDICATE | |00:05:05.33
      NESTED LOOPS ANTI | |00:05:05.23
       TABLE ACCESS BY INDEX ROWID BATCHED| STATS$SQL_PLAN_USAGE |00:03:42.53
        INDEX RANGE SCAN | STATS$SQL_PLAN_USAGE_PK|00:02:22.49
       INDEX RANGE SCAN | STATS$SQL_PLAN_PK |00:01:03.19
   FIXED TABLE FIXED INDEX | X$KQLFXPL (ind:3) |00:00:00.01
-------------------------------------------------------------------------------

Wie man sieht, benötigt der Insert 5 Minuten und 8 Sekunden.

Queryblöcke

Um eine Queryblock in der Hauptabfrage referenzieren zu können, muss man dem untergeordneten Queryblock mit dem QB_NAME hint einen Namen geben.

Dann kann man die Tabellen des untergeordneten Queryblocks mittels „tabellenalias@queryblock“ ansprechen.
Dies ist ein dokumentiertes Vorgehen und sollte in allen Versionen stabil funktionieren.

In unserem Beispiel sieht das so aus:

SELECT /*+ leading(spu@np ssp@sq  s sp) */
            new_plan.plan_hash_value,
            sp.id,
... 
            MAX(new_plan.snap_id)
        FROM
            (
                SELECT /*+ QB_NAME(NP)  */  
                    spu.plan_hash_value,
                    spu.hash_value    hash_value,
                    spu.address       address,
                    spu.text_subset   text_subset,
                    spu.snap_id       snap_id
                FROM
                    stats$sql_plan_usage spu
                WHERE
                    spu.snap_id = :b3
                    AND spu.dbid = :b2
                    AND spu.instance_number = :b1
                            AND NOT EXISTS (
                        SELECT /*+ QB_NAME(SQ)  */
                            *
                        FROM
                            stats$sql_plan ssp
                        WHERE
                            ssp.plan_hash_value = spu.plan_hash_value
                    )
            ) new_plan,

Diesmal hält der Optimzer sich an die Hints, das Ergebnis sieht wie folgt aus:

--------------------------------------------------------------------------------
Operation                                | Name                     |   A-Time  
--------------------------------------------------------------------------------
INSERT STATEMENT                         |                          |00:00:00.05
 LOAD TABLE CONVENTIONAL                 | STATS$SQL_PLAN           |00:00:00.05
  SORT GROUP BY                          |                          |00:00:00.05
   NESTED LOOPS                          |                          |00:00:00.05
    NESTED LOOPS                         |                          |00:00:00.04
     HASH JOIN ANTI                      |                          |00:00:00.04
      TABLE ACCESS BY INDEX ROWID BATCHED| STATS$SQL_PLAN_USAGE     |00:00:00.01
       INDEX RANGE SCAN                  | STATS$SQL_PLAN_USAGE_PK  |00:00:00.01
      INDEX FAST FULL SCAN               | STATS$SQL_PLAN_PK        |00:00:00.01
     FIXED TABLE FIXED INDEX             | X$KGLCURSOR_CHILD (ind:1)|00:00:00.01
    FIXED TABLE FIXED INDEX              | X$KQLFXPL (ind:3)        |00:00:00.01
--------------------------------------------------------------------------------

Also 5 hundertstel Sekunden statt 5 Minuten.

Das ist über 6000x schneller und kann sich sehen lassen.

Nachdem ich die Hints gefunden hatte, habe ich eine Sicherheitskopie des Scriptes SCPPKG.SQL angelegt und den neuen Hint in das Package kopiert.
Durch den Aufruf des Scriptes wurde das neue Package dann installiert.

Statement: INSERT INTO stats$seg_stat

Wie sieht nun das zweite Insert aus?

Diesmal gibt es keine MOS Note.

Auch hier zeige ich wieder den sql code, mit den hervorgehobenen Hints:

SELECT /*+  ordered use_nl(s1.gv$segstat.X$KSOLSFTS) */
            :b3,
            :b2,
...
            SUM(decode(s1.statistic_name, 'row lock waits', value, 0))
        FROM
            v$segstat s1
        WHERE
            ( s1.dataobj#,
              s1.obj#,
              s1.ts# ) IN (
                SELECT /*+ unnest */
                    s2.dataobj#,
                    s2.obj#,
                    s2.ts#
                FROM
                    v$segstat s2
                WHERE
                    s2.obj# > 0
                    AND s2.obj# < 4254950912
                    AND ( decode(s2.statistic_name, 'logical reads', s2.value, 0) > :b10
                          OR decode(s2.statistic_name, 'physical reads', s2.value, 0) > :b9
                          OR decode(s2.statistic_name, 'buffer busy waits', s2.value, 0) > :b8
                          OR decode(s2.statistic_name, 'row lock waits', s2.value, 0) > :b7
                          OR decode(s2.statistic_name, 'ITL waits', s2.value, 0) > :b6
                          OR decode(s2.statistic_name, 'gc cr blocks received', s2.value, 0) > :b5
                          OR decode(s2.statistic_name, 'gc current blocks received', s2.value, 0) > :b4 )
            )
        GROUP BY
            s1.ts#,
            s1.obj#,
            s1.dataobj#
;

Wieder sieht man hier ein sehr ungewöhnliches Hint Format.

Wieder meldet aber die Report Funktion in 19c keinen Fehler und in unseren Test hat der Optimizer den Hint befolgt.

Jedoch bin ich diesmal mit der Wirkung des Hints nicht einverstanden.

Sehen wir uns dazu Laufzeitstatitiken an:

------------------------------------------------------------------------------
Id  | Operation                | Name          | Starts | A-Rows |   A-Time   
------------------------------------------------------------------------------
  0 | INSERT STATEMENT         |               |      1 |      0 |00:01:16.83 
  1 |  LOAD TABLE CONVENTIONAL | STATS$SEG_STAT|      1 |      0 |00:01:16.83 
  2 |   HASH GROUP BY          |               |      1 |    746 |00:01:16.83 
  3 |    NESTED LOOPS          |               |      1 |  19396 |00:01:16.81 
  4 |     VIEW                 | VW_NSO_1      |      1 |    747 |00:00:00.19 
  5 |      SORT UNIQUE         |               |      1 |    747 |00:00:00.19 
  6 |       FIXED TABLE FULL   | X$KSOLSFTS    |      1 |    814 |00:00:00.19 
  7 |     FIXED TABLE FULL     | X$KSOLSFTS    |    747 |  19396 |00:01:16.62 
------------------------------------------------------------------------------

Immerhin läuft auch dieser Befehl noch über eine Minute.

Die Zeit fällt fast zu 100% in der Zeile 7 an und zwar deshalb, weil die Zeile 747 Mal wiederholt wird.

Ein Hash join wäre hier wesentlich Laufzeit stabiler.
Der Nested Loop join hat Vorteile bei kleinen Datenmengen.

Allerdings spielt es ohnehin keine Rolle, welchen Join man nimmt, bei kleinen Datenmengen sind alle schnell.

Ich habe also den use_nl hint gegen einen use_hash hint getauscht.

Das ist das Resultat:

-------------------------------------------------------------------------------
Id  | Operation                | Name           | Starts | A-Rows |   A-Time   
-------------------------------------------------------------------------------
  0 | INSERT STATEMENT         |                |      1 |      0 |00:00:00.39 
  1 |  LOAD TABLE CONVENTIONAL | STATS$SEG_STAT |      1 |      0 |00:00:00.39 
  2 |   HASH GROUP BY          |                |      1 |    748 |00:00:00.39 
  3 |    HASH JOIN             |                |      1 |  19448 |00:00:00.38 
  4 |     VIEW                 | VW_NSO_1       |      1 |    748 |00:00:00.19 
  5 |      SORT UNIQUE         |                |      1 |    748 |00:00:00.19 
  6 |       FIXED TABLE FULL   | X$KSOLSFTS     |      1 |    815 |00:00:00.19 
  7 |     FIXED TABLE FULL     | X$KSOLSFTS     |      1 |    390K|00:00:00.13 
-------------------------------------------------------------------------------

Insgesamt läuft der Snapshot jetzt in 6 Sekunden durch.

Long Parse aufgrund von Skew Detection in Hybrid Hash Distribution

Für einen detaillierten Überblick wie Skew Detection/Skew Handling in Hybrid Hash Distributionen funktioniert und welche Ziele es verfolgt, empfehle ich den sehr guten Blogeintrag von Randolf Geist zu lesen: https://oracle-randolf.blogspot.com/2014/05/12c-hybrid-hash-distribution-with-skew.html

Im Blog von Herrn Geist werden unter anderem sieben Vorbedingungen genannt, die nötig sind, damit dieses Feature wirksam wird.

Zumindest in Oracle 12.2 konnte ich auf Basis meiner Untersuchung eine weitere Vorbedingung finden:
Dynamic Sampling muss ebenfalls aktiviert sein (d.h auf einem Level > 0 konfiguriert sein; der Default ist 2).

Die rekursive Query die während der Optimierungsphase (=Hard Parse) ausgeführt wird, um einen Skew zu erkennen sieht nun auch ein wenig anders aus.

Zum Großteil wurden hier weitere Hints hinzugefügt.
Auf Oracle 12.2 hat sie nun folgende Form:

--kkopqSkewInfo: Query:SELECT /* DS_SKEW */ /*+ RESULT_CACHE no_parallel dynamic_sampling(0) no_sql_tune no_monitoring */ * FROM (SELECT SYS_OP_COMBINED_HASH("ID"), COUNT(*) CNT, TO_CHAR("ID") FROM "ASC_SKEW_PART" SAMPLE(99.900000) SEED(1) GROUP BY "ID" ORDER BY CNT DESC, "ID") WHERE ROWNUM <= 2;

Was hier sofort auffällt ist die Größe des gewählten Samples (99.9%).

Nach ein paar Tests mit unterschiedlichen Tabellengrößen, kam ich zum Schluss, dass das Sample so gewählt wird, um ca. 5.500 Rows aus der Tabelle zu selektieren, um auf dieser Datenbasis die Skew Erkennung durchzuführen.

Die Beschränkung auf 5.500 Rows wurde in älteren Releases schon vorgenommen, wenn Histogramme im Zuge der Statistikberechnung erzeugt wurden. Für Tabellen die weniger als 5.500 Rows haben, wird das Sample mit 99,9% gewählt.

Sehen wir uns nun an unter welchen Umständen das zu sehr langen Parse Zeiten führen kann:

--#######################################
--# Small lookup table                  #
--#######################################
drop table asc_pids;
create table asc_pids
as
select 45e6 + rownum id
  from dual
 connect by level <= 13;
 
--#######################################
--# Big, partitioned fact table         #
--#######################################
--5 mio records in each partition from P1 to P9
--only 4 records in partition P10
drop table asc_skew_part;
create table asc_skew_part
nologging
partition by range(id)
(
   partition P1 values less than (5e6),
   partition P2 values less than (10e6),
   partition P3 values less than (15e6),
   partition P4 values less than (20e6),
   partition P5 values less than (25e6),
   partition P6 values less than (30e6),
   partition P7 values less than (35e6),
   partition P8 values less than (40e6),
   partition P9 values less than (45e6),
   partition P10 values less than (50e6)
)
as
with a as
(
    select rownum id
      from dual
     connect by level <= 1e5
)
select /*+ parallel(4) */rownum id, 
       lpad('*', 255, '*') padding
  from a, a
 where rownum <= 45e6
union all
select 45e6 + mod(rownum, 2) + 1 id,
       lpad('*', 255, '*') padding
  from dual
 connect by level <= 4;
 
--#######################################
--# histograms on id on fact table      #
--####################################### 
exec dbms_stats.gather_table_stats(user, 'asc_skew_part', method_opt=>'for all columns size 1 for columns id size 255', granularity=>'ALL');


--#######################################
--# clear RC                            #
--#######################################
exec dbms_result_cache.flush();

Ich habe eine große, partitionierte Faktentabelle erstellt. Darin befinden sich jeweils 5 Millionen Rows in den Partitionen P1 bis P9 und nur 4 Rows in der letzten Partition P10 (2 Rows pro Value, das reicht um die notwendige Vorbedingung für die Skew Erkennung zu erfüllen).

Nachdem ich Histogramme auf der Faktentabelle erzeugt und den Result Cache (aufgrund des Hints in der rekursiven Query) geflushed habe, joine ich diese Tabelle nun zur kleinen Lookup-Tabelle.
Dabei schränke ich in der where Klausel aber so ein, dass nur Datensätze der kleinen Partition P10 selektiert werden.

--#######################################
--# note the long parsing time          #
--#######################################
explain plan for
select /*+ leading(b a) use_hash(a) parallel(8) pq_distribute(a HASH HASH)*/*
  from asc_skew_part a, asc_pids b
 where a.id = b.id
   and a.id between 45000000 and 50000000;

Die rekursive Query die im 10053 Event/Optimizer Trace ersichtlich ist, ist exakt jene die ich schon zu Beginn des Posts gezeigt habe:

--kkopqSkewInfo: Query:SELECT /* DS_SKEW */ /*+ RESULT_CACHE no_parallel dynamic_sampling(0) no_sql_tune no_monitoring */ * FROM (SELECT SYS_OP_COMBINED_HASH("ID"), COUNT(*) CNT, TO_CHAR("ID") FROM "ASC_SKEW_PART" SAMPLE(99.900000) SEED(1) GROUP BY "ID" ORDER BY CNT DESC, "ID") WHERE ROWNUM <= 2;

Hier wurde während des Parsens ein 99,9% Sample über alle Partitionen in einer Tabelle mit 45 Millionen Rows gelesen, eine Hash Funktion auf der Join Spalte angewandt und aggregiert.

Es sieht so aus als wäre die Größe des Samples nur von der kleinen Partition P10 (4 Rows) abgeleitet worden, die ich mit meiner where Klausel treffe.

Angewandt wurde das Sampling dann aber über die gesamte Tabelle, eine where Klausel um auf die entsprechende Partition zu filtern gibt es in der rekursiven Query nicht.

Im konkreten Fall ist das auf einer Produktions-DB mit Milliarden von Rows und hunderten Partitionen aufgetreten, woraufhin das bl0ße Parsen über Stunden gedauert hat.

Update: 

Offensichtlich wird die Anzahl der Zeilen, die der Optimizer nach Anwendung der Filter in der where Klausel schätzt, als Basis für die Größe des Samples herangezogen.

Wenn wir eine Tabelle mit 1 Mrd. Rows hätten, dabei ein Wert besonders oft vorkommen würde (≥30% defaultmäßig) und die anderen eindeutig wären, würde ein Filter auf einen der eindeutigen Werte zu einem Sample von 99,9% in der rekursiven Query führen (da der CBO 1 Row nach dem Filter schätzen würde; 1 ≤ 5,500 deswegen → 99,9%).

Natürlich müssen alle anderen Vorbedingungen (Histogramme, Hash Join, etc.) natürlich weiterhin erfüllt sein.

Um eine Zeile aus einer Tabelle von 1 Mrd. Rows zu lesen, möchte man es wahrscheinlich vermeiden 99,9% aller Rows während des Parsens zu lesen und zu aggregieren 😉

Beispiel:

create table asc_skew
nologging
as
with generator as
(
   select *
     from dual
    connect by level <= 1e4
)
select (case when mod(rownum, 2) = 0 then -1 else rownum end)            
       id, 
       rpad('*', 255, '*') padding
  from generator, generator
 where rownum <= 50e6;

create table asc_pids
as
select rownum id
  from dual
 connect by level <= 13;

exec dbms_stats.gather_table_stats(user, 'asc_skew', method_opt=>'for all columns size 1 for columns id size 255', granularity=>'ALL');

--this will parse very long
explain plan for
select /*+ leading(b a) use_hash(a) parallel(8) pq_distribute(a HASH HASH)  */*
  from asc_skew a, asc_pids b
 where a.id = b.id
   and a.id = 42;

Fazit:

Meiner Meinung nach sollte die Auswahl des Samples angepasst werden auf die Größe des Segments vor der Anwendung der where Klausel.

Weiters sollte auf partitionierte Tabellen besonders Rücksicht genommen werden.

Eventuell wäre es auch sinnvoll bei 99,9% komplett auf die sample Klausel zu verzichten, da diese z.B auch Smart Scans auf Exadata verhindert.

 

Cloud Control installation in silent mode made EASY

Cloud Control installation in silent mode made EASY

Step-by-Step guide how to install Oracle Cloud Control in silent mode the EASY way

Step1. Prepare machine configuration

Machine configuration (small setup of CC)
operation system: oracle linux 7
RAM 15GB
CPU 4
Storage 150GB  (OMS 125g+ archive destination 25g)

Stage the software somewhere on server.

[root@server1 software]#
[root@server1 software]# ls -ltr
total 4840584
-rw-r--r-- 1 root root 3059705302 Mar 10 14:46 LINUX.X64_193000_db_home.zip
-rw-r--r-- 1 root root  115653541 Mar 10 14:46 p6880880_190000_Linux-x86-64.zip
-rw-r--r-- 1 root root  773493643 Mar 10 14:50 p30125133_190000_Linux-x86-64.zip
-rw-r--r-- 1 root root  993959385 Mar 10 14:56 p30557433_190000_Linux-x86-64.zip
[root@server1 software]# pwd
/admin/software

Kernel parameter settings can be done via installting rpm from yum repository. (Kernelparameter with 19c + oracle base parameters by oracle-preinstall*19c )

As prerequisites preinstall package for oracle 19c was installaed from yum public repository.

Step 2. Now install database software and create the database

mkdir -p /u01/app/oracle/product/19.0.0/dbhome_1
                mkdir -p /u01/oradata
                chown -R oracle:oinstall /u01
                chmod -R 775 /u01

cd $ORACLE_HOME
unzip -oq /path/to/software/LINUX.X64_193000_db_home.zip

export ORACLE_HOME=/u01/app/oracle/product/19.0.0/dbhome_1
export PATH=$ORACLE_HOME/bin:$PATH:$ORACLE_HOME/OPatch

Here I installed the database software in silent mode without responsefile.

Reference doc is (Doc ID 885643.1) from oracle support.

Once it is completed then database was created in silent mode using dbca

————————————————————————————————————

Here are the steps

Prepare CCREPO database for cloud control installation (Here CCREPO is my database used for cloud control repository )

dbca -silent -responsefile NO_VALUE -createDatabase -gdbname 'CCREPO' -templateName General_Purpose.dbc  -databaseConfigType 'SI' -sysPassword 'XYZPQR' -systemPassword 'XYZPQR' -sampleSchema false -emConfiguration NONE  -storageType FS  -datafileDestination /u01/oradata -recoveryAreaDestination /u01/fra  -characterSet 'AL32UTF8'  -nationalCharacterSet 'AL16UTF16' -createAsContainerDatabase 'FALSE'  -initParams 'db_unique_name=CCREPO'

Logs

[root@server1 ]$ ./dbca -silent -responsefile NO_VALUE -createDatabase -gdbname 'CCREPO' -templateName General_Purpose.dbc  -databaseConfigType 'SI' -sysPassword 'XYZPQR' -systemPassword 'XYZPQR' -sampleSchema false -emConfiguration NONE  -storageType FS  -datafileDestination /u01/oradata -recoveryAreaDestination /u01/fra  -characterSet 'AL32UTF8'  -nationalCharacterSet 'AL16UTF16' -createAsContainerDatabase 'FALSE'  -initParams 'db_unique_name=CCREPO'
[WARNING] [DBT-06208] The 'SYS' password entered does not conform to the Oracle recommended standards.
  CAUSE:
Oracle recommends that the password entered should be at least 8 characters in length, contain at least 1 uppercase character, 1 lower case character and 1 digit [0-9].b.The password entered is a keyword that Oracle does not recommend to be used as password
  ACTION: Specify a strong password. If required refer Oracle documentation for guidelines.
[WARNING] [DBT-06208] The 'SYSTEM' password entered does not conform to the Oracle recommended standards.
   CAUSE:
Oracle recommends that the password entered should be at least 8 characters in length, contain at least 1 uppercase character, 1 lower case character and 1 digit [0-9].
b.The password entered is a keyword that Oracle does not recommend to be used as password
  ACTION: Specify a strong password. If required refer Oracle documentation for guidelines.
Prepare for db operation
10% complete
Copying database files
40% complete
Creating and starting Oracle instance
42% complete
46% complete
50% complete
54% complete
60% complete
Completing Database Creation
66% complete
69% complete
70% complete
Executing Post Configuration Actions
100% complete
Database creation complete. For details check the logfiles at:
 /u01/app/oracle/cfgtoollogs/dbca/CCREPO.
Database Information:
Global Database Name:CCREPO
System Identifier(SID):CCREPO
Look at the log file "/u01/app/oracle/cfgtoollogs/dbca/CCREPO/CCREPO.log" for further details.
[root@server1 ]$

Step3. Prepare the database CCREPO for Cloud control reposity

 

[root@server1 ]$ ps -ef|grep pmon
oracle   22723     1  0 Mar13 ?        00:00:10 ora_pmon_CCREPO
oracle   88030  4260  0 09:23 pts/0    00:00:00 grep --color=auto pmon
[root@server1 ]$ . oraenv
ORACLE_SID = [oracle] ? CCREPO
The Oracle base has been set to /u01/app/oracle
[root@server1 ]$ echo $ORACLE_SID
CCREPO
[root@server1 ]$ sqlplus / as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Mon Mar 16 09:23:29 2020
Version 19.6.0.0.0
Copyright (c) 1982, 2019, Oracle.  All rights reserved.

Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.6.0.0.0

SQL> select name,open_mode from v$database;

NAME      OPEN_MODE
--------- --------------------
CCREPO    READ WRITE

SQL> show parameter optimizer_adaptive_features;
SQL> show parameter adaptive; (All adaptive features parameters should be unset for improved SQL performance)

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
optimizer_adaptive_plans             boolean     TRUE
optimizer_adaptive_reporting_only    boolean     FALSE
optimizer_adaptive_statistics        boolean     FALSE
parallel_adaptive_multi_user         boolean     FALSE
SQL> alter system set optimizer_adaptive_plans=FALSE scope=both;
System altered.
SQL> show parameter adaptive;

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
optimizer_adaptive_plans             boolean     FALSE
optimizer_adaptive_reporting_only    boolean     FALSE
optimizer_adaptive_statistics        boolean     FALSE
parallel_adaptive_multi_user         boolean     FALSE
SQL>

SQL> show parameter _allow_insert_with_update_check;
SQL> alter system set "_allow_insert_with_update_check"=true scope=both;
System altered.

SQL> show parameter _allow_insert_with_update_check;

NAME                                 TYPE        VALUE
----------------------------------- ----------- ------------------------------
_allow_insert_with_update_check      boolean     TRUE

SQL> ALTER  DATABASE tempfile 1 resize 5G;
Database altered.

SQL> select file_id,file_name,bytes/1024/1024/1024,maxbytes/1024/1024/1024,autoextensible from dba_temp_files where tablespace_name='TEMP' order by file_name;

   FILE_ID FILE_NAME                                          BYTES/1024/1024/1024 MAXBYTES/1024/1024/1024 AUT
---------- -------------------------------------------------- -------------------- ----------------------- ---
         1 /u01/oradata/CCREPO/temp01.dbf                                        5              31.9999847 YES

SQL> ALTER system SET session_cached_cursors = 400 scope = spfile;
SQL> ALTER system SET shared_pool_size = 1G scope = BOTH ;
SQL> ALTER system reset streams_pool_size scope = BOTH;
SQL> show parameter sga;

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
allow_group_access_to_sga            boolean     FALSE
lock_sga                             boolean     FALSE
pre_page_sga                         boolean     TRUE
sga_max_size                         big integer 9632M
sga_min_size                         big integer 0
sga_target                           big integer 9632M
unified_audit_sga_queue_size         integer     1048576
SQL> show parameter pga

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
pga_aggregate_limit                  big integer 6402M
pga_aggregate_target                 big integer 3201M

SQL> show parameter process; (process should be at least 600 recommended)

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
aq_tm_processes                      integer     1
cell_offload_processing              boolean     TRUE
db_writer_processes                  integer     1
gcs_server_processes                 integer     0
global_txn_processes                 integer     1
job_queue_processes                  integer     160
log_archive_max_processes            integer     4
processes                            integer     640
processor_group_name                 string

alter system set "_optimizer_nlj_hj_adaptive_join"= FALSE scope=both sid='';
alter system set "_optimizer_strans_adaptive_pruning" = FALSE scope=both sid='';
alter system set "_px_adaptive_dist_method" = OFF scope=both sid='';
alter system set "_sql_plan_directive_mgmt_control" = 0 scope=both sid='';
alter system set "_optimizer_dsdir_usage_control" = 0 scope=both sid='';
alter system set "_optimizer_use_feedback" = FALSE scope=both sid='';
alter system set "_optimizer_gather_feedback" = FALSE scope=both sid='';
alter system set "_optimizer_performance_feedback" = OFF scope=both sid='';

SQL> shut immediate;
Database closed.
Database dismounted.
ORACLE instance shut down.
SQL> startup;
ORACLE instance started.

Total System Global Area 1.0100E+10 bytes
Fixed Size                 12445880 bytes
Variable Size            1543503872 bytes
Database Buffers         8522825728 bytes
Redo Buffers               21106688 bytes
Database mounted.
Database opened.

SQL> select name,open_mode from v$database;
NAME      OPEN_MODE
--------- --------------------
CCREPO    READ WRITE
SQL> exit

Disconnected from Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.6.0.0.0
[root@server1 ]$

Note: It is important to restart the CCREPO database after setting all these parameeters. After restart our database is ready to be a Cloud control repository now.

Step4. Cloud control Installation

create the directories
cd /u01/app/oracle
mkdir -p /u01/app/oracle/mw13             > ( This will be our OMS_HOME )
mkdir -p /u01/app/oracle/agent13         > ( This will be our AGENT_HOME )
mkdir -p /u01/app/oracle/gc_inst           > ( This will be our OMS instance )
mkdir -p /u01/app/oracle/swlib                               > ( This will be our Software library )
total 0

drwxrwxr-x  3 oracle oinstall  20 Mar 10 16:03 product
drwx------  2 oracle oinstall   6 Mar 10 16:55 checkpoints
drwxrwxr-x 23 oracle oinstall 280 Mar 10 16:55 diag
drwxr-x---  4 oracle oinstall  34 Mar 12 12:55 cfgtoollogs
drwxr-x---  4 oracle oinstall  33 Mar 13 15:50 admin
drwxr-x---  4 oracle oinstall  33 Mar 13 15:53 audit
drwx------  2 oracle oinstall   6 Mar 16 10:05 mw13
drwx------  2 oracle oinstall   6 Mar 16 10:05 agent13
drwx------  2 oracle oinstall   6 Mar 16 10:05 gc_inst
drwx------  2 oracle oinstall   6 Mar 16 10:05 swlib
[root@server1 ]$

Main steps — Enterprise manage cloud control 13.3 installation in silent mode

 

Reference Documentation link: https://docs.oracle.com/en/enterprise-manager/cloud-control/enterprise-manager-cloud-control/13.4/embsc/introduction.html#GUID-1F83B94D-EC8F-4ED7-B632-A3C1C593E6CD

Here is the list of rpms needed as per OMS,agent and middleware.

rpm required for OEM 13.4
-------------------------------
binutils-2.23.52.0.1
compat-libcap1-1.10
compat-libstdc++-33-3.2.3.x86_64
compat-libstdc++-33-3.2.3.i686
gcc-4.8.2
gcc-c++-4.8.2
glibc-2.17.x86_64
glibc-2.17.i686
glibc-devel-2.17.x86_64
libaio-0.3.109.x86_64
libaio-devel-0.3.109.x86_64
libgcc-4.8.2.x86_64
libgcc-4.8.2.i686
libstdc++-4.8.2.x86_64
libstdc++-4.8.2.i686
libstdc++-devel-4.8.2.x86_64
dejavu-serif-fonts
ksh
make-3.82
sysstat-10.1.5
numactl-2.0.9 for x86_641
numactl-devel-2.0.9 for x86_642
motif-2.3.4-7 for x86_643
motif-devel-2.3.4-7 for x86_644
redhat-lsb-4.1-27.0.1.el7 for x86_64
redhat-lsb-core-4.1-27.0.1.el7 for x86_64
OpenSSL 1.0.1e
make-3.82-21
binutils-2.23
gcc-4.8.2-16
libaio-0.3.109-12
glibc-common-2.17-55
libstdc++-4.8.2-16
sysstat-10.1.5-4

rpm -qa rpm_name (to check if installed or not)

Download and stage the software EM 13.4 to the destination server.

https://www.oracle.com/enterprise-manager/downloads/linux-x86-64-13c-rel4-downloads.html

Directions

Download all the files including .bin in same directory. No need to unzip the downloaded software.

As the install user who will be installing the product, set the execute permission for the .bin file. Example: chmod +x em13400_linux64.bin

Chanage permissions and scp the software on destination server.

chmod 755 em13400_linux64.bin em13400_linux64*

ls -ltr
total 17300064
-rwxr-xr-x 1 oracle oinstall 1623577684 Mar 16 12:22 em13400_linux64.bin
-rwxr-xr-x 1 oracle oinstall 2109004737 Mar 16 12:25 em13400_linux64-2.zip
-rwxr-xr-x 1 oracle oinstall 2047749474 Mar 16 12:28 em13400_linux64-3.zip
-rwxr-xr-x 1 oracle oinstall 2117312528 Mar 16 12:32 em13400_linux64-4.zip
-rwxr-xr-x 1 oracle oinstall  808014516 Mar 16 12:33 em13400_linux64-5.zip
-rwxr-xr-x 1 oracle oinstall 2143125187 Mar 16 12:36 em13400_linux64-6.zip
-rwxr-xr-x 1 oracle oinstall 1879163226 Mar 16 12:39 em13400_linux64-7.zip

Step4.1. Prepare responsefile for OEM

pwd
/u01/software
             mkdir responsefiles
             cd responsefiles

           pwd
                /u01/software/responsefiles

cd ..
pwd
/u01/software
[root@server1 ]$ ls -ltr
total 16501492

drwxr-xr-x 5 oracle oinstall         81 Jan  7 04:07 30557433
-rw-rw-r-- 1 oracle oinstall     237583 Jan 15 14:30 PatchSearch.xml
-rwxrwxr-x 1 oracle oinstall 3059705302 Mar 10 15:26 LINUX.X64_193000_db_home.zip
-rwxrwxr-x 1 oracle oinstall  993959385 Mar 10 15:27 p30557433_190000_Linux-x86-64.zip
-rwxrwxr-x 1 oracle oinstall  115653541 Mar 10 15:27 p6880880_190000_Linux-x86-64.zip
-rwxr-xr-x 1 oracle oinstall 2109004737 Mar 16 12:48 em13400_linux64-2.zip
-rwxr-xr-x 1 oracle oinstall 2047749474 Mar 16 12:48 em13400_linux64-3.zip
-rwxr-xr-x 1 oracle oinstall 2117312528 Mar 16 12:49 em13400_linux64-4.zip
-rwxr-xr-x 1 oracle oinstall  808014516 Mar 16 12:49 em13400_linux64-5.zip
-rwxr-xr-x 1 oracle oinstall 2143125187 Mar 16 12:49 em13400_linux64-6.zip
-rwxr-xr-x 1 oracle oinstall 1879163226 Mar 16 12:49 em13400_linux64-7.zip
-rwxr-xr-x 1 oracle oinstall 1623577684 Mar 16 12:50 em13400_linux64.bin
drwx------ 2 oracle oinstall          6 Mar 16 12:57 responsefiles

./em13400_linux64.bin -getResponseFileTemplates -outputLoc /u01/software/responsefiles

My responsefile creation command got failed here becauseIi had very less space in /temp

ERROR Details :
----------------
[root@server1 ]$ ./em13400_linux64.bin -getResponseFileTemplates -outputLoc /u01/software/responsefiles
ERROR: Temporary directory /tmp does not have enough free space. At least 12289 MB of free space are required.
Please input another directory or [Exit]: exit
ERROR: Cannot setup the extract directory /tmp/sfx_DRYjy1 (-1).

[root@server1 ]$ df -h /tmp
Filesystem                 Size  Used Avail Use% Mounted on
/dev/mapper/rootvg-tmp_lv  5.5G   44M  5.5G   1% /tmp
[root@server1 ]$

Note: Here I have very less space available in my temp file system. As per oracle support we need at least 14gb of temp.

https://docs.oracle.com/en/enterprise-manager/cloud-control/enterprise-manager-cloud-control/13.4/embsc/oracle-enterprise-manager-deployment-prerequisites.html#GUID-C72126D0-C99F-4348-8CCB-7C49FC2E4ED2

So here we have 2 options.

Either increase temp file system or use another file system for temp where you have enough recommended space. Here I use another file system for /tmp

In order to fix this I have used a workaround here by using my own created space for temp because

 

cd u01
mkdir temp
cd temp
pwd
/u01/temp

export TMP=/u01/temp
export TMPDIR=/u01/temp
export TEMP=/u01/temp

./em13400_linux64.bin -J-Djava.io.tmpdir=/u01/temp -getResponseFileTemplates -outputLoc /u01/software/responsefiles  (Reference oracle documentation (Doc ID 2308484.1) )
logs from above command.

--------------------------------------
[root@server1 ]$ export TMP=/u01/temp
[root@server1 ]$ export TMPDIR=/u01/temp
[root@server1 ]$ export TEMP=/u01/temp
[root@server1 ]$ echo $TMP
/u01/temp
[root@server1 ]$ echo $TMPDIR
/u01/temp
[root@server1 ]$ echo $TEMP
/u01/temp

[root@server1 ]$ ./em13400_linux64.bin -J-Djava.io.tmpdir=/u01/temp -getResponseFileTemplates -outputLoc /u01/software/responsefiles
Launcher log file is /u01/temp/OraInstall2020-03-19_11-10-25AM/launcher2020-03-19_11-10-25AM.log.
Extracting the installer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Done
Copying response file template(s)
to /u01/software/responsefiles
  Copying response file template upgrade.rsp
  Copying response file template new_install.rsp
  Copying response file template software_only.rsp
Finished copying response file template(s)

The log(s) can be found here: /u01/temp/OraInstall2020-03-19_11-10-25AM.
[root@server1 ]$

Prepare the responsefile

cd responsefiles
ls -ltr
cp new_install.rsp myoem_install.rsp

Now edit this file myoem_install.rsp as below.

I removed all empty space and edit as per your requirement .

new edited responsefile (file location is /u01/software/responsefiles)

cat myoem_install_updated.rsp

INVENTORY_LOCATION="/u01/app/oraInventory"
SECURITY_UPDATES_VIA_MYORACLESUPPORT=false
DECLINE_SECURITY_UPDATES=true
INSTALL_UPDATES_SELECTION=skip
ORACLE_MIDDLEWARE_HOME_LOCATION="/u01/app/oracle/mw13"
AGENT_BASE_DIR="/u01/app/oracle/agent13"
ORACLE_HOSTNAME="server1.com"
WLS_ADMIN_SERVER_USERNAME="weblogic"
WLS_ADMIN_SERVER_PASSWORD="XYZPQR"
WLS_ADMIN_SERVER_CONFIRM_PASSWORD="XYZPQR"
NODE_MANAGER_PASSWORD="XYZPQR"
NODE_MANAGER_CONFIRM_PASSWORD="XYZPQR"
ORACLE_INSTANCE_HOME_LOCATION="/u01/app/oracle/gc_inst"
CONFIGURE_ORACLE_SOFTWARE_LIBRARY=true
SOFTWARE_LIBRARY_LOCATION="/u01/app/oracle/swlib"
DATABASE_HOSTNAME="server1.com"
LISTENER_PORT=1521
SERVICENAME_OR_SID="CCREPO"
SYS_PASSWORD="XYZPQR"
SYSMAN_PASSWORD="XYZPQR"
SYSMAN_CONFIRM_PASSWORD="XYZPQR"
DEPLOYMENT_SIZE=MEDIUM
MANAGEMENT_TABLESPACE_LOCATION="/u01/oradata/CCREPO/mgmt.dbf"
CONFIGURATION_DATA_TABLESPACE_LOCATION="/u01/oradata/CCREPO/mgmt_ecm_depot1.dbf"
JVM_DIAGNOSTICS_TABLESPACE_LOCATION="/u01/oradata/CCREPO/mgmt_deepdive.dbf"
AGENT_REGISTRATION_PASSWORD="XYZPQR"
AGENT_REGISTRATION_CONFIRM_PASSWORD="XYZPQR"
CONFIGURATION_TYPE=ADVANCED
CONFIGURE_SHARED_LOCATION_BIP=false
EM_INSTALL_TYPE=NOSEED

Step5: Install Cloud control in silent mode

cd software location for oem

cd /u01/software

./em13400_linux64.bin -silent -responseFile /u01/software/responsefiles/myoem_install_updated.rsp -J-Djava.io.tmpdir=/u01/temp

or

nohup ./em13400_linux64.bin -silent -responseFile /u01/software/responsefiles/myoem_install_updated.rsp -J-Djava.io.tmpdir=/u01/temp &

Logs

[root@server1 ]$ ./em13400_linux64.bin -silent -responseFile /u01/software/responsefiles/myoem_install_updated.rsp -J-Djava.io.tmpdir=/u01/temp
Launcher log file is /u01/temp/OraInstall2020-03-19_01-26-16PM/launcher2020-03-19_01-26-16PM.log.
Extracting the installer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Done
Checking swap space: must be greater than 512 MB.   Actual 19455 MB    Passed
Checking if this platform requires a 64-bit JVM.   Actual 64    Passed (64-bit not required)
Preparing to launch the Oracle Universal Installer from /u01/temp/OraInstall2020-03-19_01-26-16PM

====Prereq Config Location main===
/u01/temp/OraInstall2020-03-19_01-26-16PM/stage/prereq
EMGCInstaller args -scratchPath
EMGCInstaller args /u01/temp/OraInstall2020-03-19_01-26-16PM
EMGCInstaller args -sourceType
EMGCInstaller args network
EMGCInstaller args -timestamp
EMGCInstaller args 2020-03-19_01-26-16PM
EMGCInstaller args -paramFile
EMGCInstaller args /u01/temp/sfx_dJv119/Disk1/install/linux64/oraparam.ini
EMGCInstaller args -silent
EMGCInstaller args -responseFile
EMGCInstaller args /u01/software/responsefiles/myoem_install_updated.rsp
EMGCInstaller args -nocleanUpOnExit
DiskLoc inside SourceLoc/u01/temp
EMFileLoc:/u01/temp/OraInstall2020-03-19_01-26-16PM/oui/em/
ScratchPathValue :/u01/temp/OraInstall2020-03-19_01-26-16PM
EMGCInstallUpdatesInfoOnNext:: calling actionOnClickofNext
Now in EMGCInstallUpdatesInfoOnNext.actionsOnClickofNext
EMGCInstallUpdatesInfoOnNext:: End of actionOnClickofNext
Oracle strongly recommends using the AL32UTF8 character set for EM repository. AL32UTF8 is Oracle's name for the standard Unicode encoding UTF-8, which enables universal support of virtually all languages of the world.

 The following prerequisite check failed because the Oracle Database, where the Management Repository will be configured, does not meet the configuration requirements. These failures do not impact the installation, but you may encounter performance issues with the product later. You can either click Cancel, fix the issue manually now, based on the recommendation offered for this prerequisite, and click Next, or you can click OK to fix it after the installation ends. For more details check the logs: /u01/temp/OraInstall2020-03-19_01-26-16PM/emdbprereqs
Prereq Name             Recommendation
Check the redo log size.                Redo Log file size should be 600000000 bytes or greater.  Generally, there should be 3 or more redo logs available of this size.
Session log file is /u01/temp/OraInstall2020-03-19_01-26-16PM/install2020-03-19_01-26-16PM.log
Installation in progress
Install successful
Linking in progress
Link successful
Setup in progress
Setup successful
Session log file is /u01/temp/OraInstall2020-03-19_01-26-16PM/install2020-03-19_01-26-16PM.log
Installation in progress
Install successful
Linking in progress
Link successful
Setup in progress
Setup successful
Agent OracleHome :/u01/app/oracle/agent13/agent_13.4.0.0.0
Session log file is /u01/temp/OraInstall2020-03-19_01-26-16PM/install2020-03-19_01-26-16PM.log
..................................................................................................
Installation in progress (Thursday, March 19, 2020 1:32:41 PM CET)
                              98% Done.
Install successful
Linking in progress (Thursday, March 19, 2020 1:32:42 PM CET)
Link successful
Setup in progress (Thursday, March 19, 2020 1:32:42 PM CET)
Setup successful
Saving inventory (Thursday, March 19, 2020 1:32:42 PM CET)
Saving inventory complete
End of install phases.(Thursday, March 19, 2020 1:32:47 PM CET)
Session log file is /u01/temp/OraInstall2020-03-19_01-26-16PM/install2020-03-19_01-26-16PM.log
...............................................................  18% Done.
...............................................................  37% Done.
...............................................................  56% Done.
...............................................................  75% Done.
...............................................................  94% Done.
...............

Installation in progress (Thursday, March 19, 2020 1:33:14 PM CET)
.
.
.
.
at the end
---------------
Use the following URL to access:
Enterprise Manager Cloud Control URL: https://server1.com:7803/em
Admin Server URL: https://server1.com:7102/console
BI Publisher URL: https://server1.com:9803/xmlpserver/servlet/home

The following details need to be provided while installing an additional OMS:
Admin Server Host Name: server1.com
Admin Server Port: 7102

You can find the details on ports used by this deployment at : /u01/app/oracle/mw13/install/portlist.ini
 NOTE:
 An encryption key has been generated to encrypt sensitive data in the Management Repository. If this key is lost, all encrypted data in the Repository becomes unusable.
 A backup of the OMS configuration is available in /u01/app/oracle/gc_inst/em/EMGC_OMS1/sysman/backup on host server1.com. See Cloud Control Administrators Guide for details on how to back up and recover an OMS.

 NOTE: This backup is valid only for the initial OMS configuration. For example, it will not reflect plug-ins installed later, topology changes like the addition of a load balancer, or changes to other properties made using emctl or emcli. Backups should be created on a regular basis to ensure they capture the current OMS configuration. Use the following command to backup the OMS configuration:
/u01/app/oracle/mw13/bin/emctl exportconfig oms -dir <backup dir>

Prompt for the allroot.sh

Warning: You must run the following configuration scripts as the "root" user.
  /u01/app/oracle/mw13/allroot.sh
To execute the configuration scripts:
Open a new terminal window.
Login in as "root".
Run the scripts.
Successfully installed Enterprise Manager Cloud Control.
Logs successfully copied to /u01/app/oraInventory/logs.
[root@server1 ]$

Step 7. Check the status of OEM

Status of OMS

[root@server1 ]$ /u01/app/oracle/mw13/bin/emctl status oms
Oracle Enterprise Manager Cloud Control 13c Release 4
Copyright (c) 1996, 2020 Oracle Corporation.  All rights reserved.
WebTier is Up
Oracle Management Server is Up
JVMD Engine is Up
BI Publisher Server is Up

Status of agent

[root@server1 ]$ pwd
/u01/app/oracle/agent13/agent_13.4.0.0.0/bin
[root@server1 ]$ ./emctl status agent

Oracle Enterprise Manager Cloud Control 13c Release 4
Copyright (c) 1996, 2020 Oracle Corporation.  All rights reserved.
---------------------------------------------------------------
Agent Version          : 13.4.0.0.0
OMS Version            : 13.4.0.0.0
Protocol Version       : 12.1.0.1.0
Agent Home             : /u01/app/oracle/agent13/agent_inst
Agent Log Directory    : /u01/app/oracle/agent13/agent_inst/sysman/log
Agent Binaries         : /u01/app/oracle/agent13/agent_13.4.0.0.0
Core JAR Location      : /u01/app/oracle/agent13/agent_13.4.0.0.0/jlib
Agent Process ID       : 1492
Parent Process ID      : 1425
Agent URL              : https://server1.com:3872/emd/main/
Local Agent URL in NAT : https://server1.com:3872/emd/main/
Repository URL         : https://server1.com:4903/empbs/upload
Started at             : 2020-03-19 15:04:41
Started by user        : oracle
Operating System       : Linux version 3.10.0-1062.9.1.el7.x86_64 (amd64)
Number of Targets      : 53
Last Reload            : (none)
Last successful upload                       : 2020-03-19 15:49:43
Last attempted upload                        : 2020-03-19 15:49:43
Total Megabytes of XML files uploaded so far : 2.31
Number of XML files pending upload           : 0
Size of XML files pending upload(MB)         : 0
Available disk space on upload filesystem    : 67.07%
Collection Status                            : Collections enabled
Heartbeat Status                             : Ok
Last attempted heartbeat to OMS              : 2020-03-19 15:51:46
Last successful heartbeat to OMS             : 2020-03-19 15:51:46
Next scheduled heartbeat to OMS              : 2020-03-19 15:52:46
---------------------------------------------------------------
Agent is Running and Ready
[root@server1 ]$

 

Oracle Session Killer

Oracle Standard Edition – Simple Session/SQL Killer

In diesem kurzen Blog Post zeigen wir, wie man mittels einer einfachen Prozedur „long running Sessions“ killen kann.

Der Nachteil dieser Lösung ist, dass die komplette Session gekillt wird.

Damit diese Lösung aber so generisch wie möglich ist, werden nur jene Spalten selektiert die ausreichen, um eine Identifikation des USER/SQL sicherzustellen.

Es ist natürlich möglich, auch weitere Spalten hinzuzugeben und die Tabellen Definition dafür anzupassen.

Diese Killer Prozedur eignet sich für Oracle Standard Edition Datenbanken, wo kein Resource Manager funktioniert.

Ab Version 18c hat Oracle eine „CANCEL SQL“ eingeführt, siehe https://oracle-base.com/articles/18c/alter-system-cancel-sql-18c

Wenn Sie eine höhere Version verwenden, kann diese Prozedur einfach umgeschrieben werden, in dem Sie folgendes Statement …

stmt := 'alter system kill session '''|| d.sid ||','||d.serial#||',@'||d.inst_id||''' immediate';

auf…

stmt := 'alter system cancel sql '''|| d.sid ||','||d.serial#||',@'||d.inst_id||''' ';

…ändern.

PLSQL Code

drop table sys.dbmonitor_tab purge ;
create table sys.dbmonitor_tab (datum timestamp, inst_id number, sid number, serial# number, username varchar2(50), sql_id varchar2(30), sql_text varchar2(50)) tablespace users ;

create or replace procedure sys.dbmonitor(timeout IN number) IS 
-- query everything what is NOT a background process, bg processes dont have a username :)
cursor get_data is select s.inst_id, s.sid, s.serial#, s.username, s.sql_id, substr(sql.sql_text,1,50) as txt from gv$session s, gv$sqlarea sql where s.sql_id=sql.sql_id and s.inst_id=sql.inst_id and 
status='ACTIVE' and round(last_call_et / 60,2) > timeout and username is not null ;
d get_data%rowtype;
stmt varchar2(4000) ;
begin
for d in get_data
loop 
-- audit the query
insert into sys.dbmonitor_tab values (systimestamp, d.inst_id, d.sid, d.serial#, d.username, d.sql_id, d.txt) ;
commit ;

stmt := 'alter system kill session '''|| d.sid ||','||d.serial#||',@'||d.inst_id||''' immediate';
-- kill it!
execute immediate stmt ;
end loop;

exception
when others then
NULL ;
end; 
/

Test Szenario

Session 1: (läuft normalerweise sehr lange):

SQL> select count(*) from dba_objects,dba_objects,dba_objects ;

Session 2: (kill session die länger als 1 Minute laufen):

SQL> exec dbmonitor(1)

PL/SQL procedure successfully completed.

SQL> select * from sys.dbmonitor_tab ;

DATUM INST_ID SID SERIAL# USERNAME SQL_ID SQL_TEXT
---------------------------------------- ---------- ---------- ---------- ---------- ------------------------------ --------------------------------------------------
15-MAY-20 01.39.37.429586 PM 1 85 60201 SYS d3s7r4a3xa0su select count(*) from dba_objects,dba_objects,dba_o
15-MAY-20 01.43.00.013776 PM 1 85 59874 SYS d3s7r4a3xa0su select count(*) from dba_objects,dba_objects,dba_o

Session 1 bekommt folgende Meldung angezeigt:

SQL> select count(*) from dba_objects,dba_objects,dba_objects ;
select count(*) from dba_objects,dba_objects,dba_objects
*
ERROR at line 1:
ORA-00028: your session has been killed

Wenn Oracle 18c oder eine höhere Versionen implementiert ist, bekommt die Session folgenden Error angezeigt und wird nicht gekillt.

Der Benutzer kann in diesem Fall weiterarbeiten.

SQL> select count(*) from dba_objects,dba_objects,dba_objects ;
select count(*) from dba_objects,dba_objects,dba_objects
*
ERROR at line 1:
ORA-01013: user requested cancel of current operation

Um diese Prozedur autonom zu machen, können wir in der Datenbank einen Scheduler Job erstellen, der alle 5 Minute gestartet wird und jene Sessions killt, die länger als 5 Minuten laufen:

begin
dbms_scheduler.create_job(
job_name=>'SYS.SESSION_KILLER',
job_type=>'PLSQL_BLOCK',
job_action=>'begin dbmonitor(5);end;',
repeat_interval=>'FREQ=MINUTELY;BYMINUTE=0,5,10,15,20,25,30,35,40,45,50,55',
enabled=>true);
end;
/
Oracle Text Troubleshooting

Oracle Text Index – Concept and troubleshooting its related issues

With Oracle text indexes (or Domain index), we can index text documents and search it based on contents using text patterns with specialized text query operators.

Oracle Text index is different from the traditional B-Tree or Bitmap indexes. They have several components communicates internally.

In a query application, the table must contain the text or pointers to where the text is stored. Text is usually a collection of documents but can also be small text.

Oracle Text index differs from the traditional B-Tree or Bitmap. In an Oracle Text index, the text data is not directly indexed rather, the text data is split into a set of tokens (these splits stored in database internal tables) and tokens are indexed.

Oracle Text Index objects

Oracle Text index has four tables: $I, $K, $N and $R tables.

The $I table contains the data which is being indexed, all the tokens (words) generated from the text document is stored in this table. The tokens in this table are indexed by a B-Tree index with name format DR${index_name}$X.

The $K table maps the internal DOCID values to external ROWID values (fetching a DOCID when we know the ROWID value) .

The $R table maps the ROWID values to DOCID values, (fetching a ROWID when we know the DOCID value). The entries from this table are indexed by a B-Tree index with name format DRC${index_name}$R.

The $N table contains a list of deleted DOCID values, which are cleaned up by the index optimization process.

Oracle Text Health Check

 Oracle Text Status and Version:

  1. A: Status of all CTXSYS objects status :
SELECT * FROM dba_objects
WHERE status !='VALID' AND OWNER = 'CTXSYS' 
ORDER BY object_type,     object_name;

B: The query for health check of the index.
The idx_docid_count is Number of documents indexed. The number of idx_docid_count should be the same or close to the number of rows of base table. The domidx_status  is domain index status.

SELECT c.idx_owner,c.idx_name,c.idx_text_name,c.idx_type,
c.idx_docid_count, i.status,i.domidx_status
FROM ctxsys.ctx_indexes c, dba_indexes i
WHERE c.idx_owner = ‘OWNER’
AND c.idx_name = ‘INDEX_NAME' and c.idx_name=i.index_name
ORDER BY 2,3;

C: Compilation errors of invalid Text-related objects:

SELECT owner, name, type, line, position, text
  FROM dba_errors
 WHERE owner = 'CTXSYS'
      OR (owner = 'SYS' AND (name like 'CTX_%' or name like 'DRI%'))
  ORDER BY owner, name, sequence;

  SELECT * FROM ctxsys.ctx_index_errors
   ORDER BY err_timestamp DESC, err_index_owner, err_index_name;

D: Extract the DDL of the existing index :

SELECT CTX_REPORT.CREATE_INDEX_SCRIPT('SCHEMA.INDEX_NAME') FROM DUAL;
  1. Validating the Index integrity:

A: Validate $K against the base table(there should be no rows selected for a valid INDEX) :

select  *
from dr$INDEX_NAME$k k
where not exists (select 1
from TABLE_NAME t
where k.textkey = t.rowid);

The keys on $K  table should be match with the base table rowids.

B: Validate $R against $K(there should be no rows selected for a valid INDEX) :

select  *
from table(ctx_diag.decode_r('dr$INDEX_NAME$R')) r
where not exists (select 1
from dr$INDEX_NAME$k k
where r.textkey = k.textkey);

C: validate $R (find duplicates) (there should be no rows selected for a valid INDEX) :

column docids for a40

select  textkey, listagg(docid, ', ') within group (order by docid) docids
from table(ctx_diag.decode_r('dr$INDEX_NAME$R'))
group by textkey
having count(*) > 1;

The above queries for validating the TEXT index should have no return values therefore the index would be consistence.

Types of Oracle Text Indexes

CONTEXT

Use this index to build a text retrieval application when your text consists of large coherent documents.

You can index documents of different formats such as MS Word, HTML or plain text.

You can customize the index in a variety of ways.

This index type requires CTX_DDL.SYNC_INDEX after DML on base table.

Note! Transactional CONTEXT Indexes: The new TRANSACTIONAL parameter to CREATE INDEX and ALTER INDEX enables changes to a base table to be immediately queryable.

CTXCAT

Use this index type for better mixed query performance.

Typically, with this index type, you index small documents or text fragments.
Other columns in the base table, such as item names, prices, and descriptions can be included in the index to improve mixed query performance.

This index is larger and takes longer to build than a CONTEXT index.

The size of a CTXCAT index is related to the total amount of text to be indexed, the number of indexes in the index set, and the number of columns indexed.
Consider your queries and your resources before adding indexes to the index set.

This index type is transactional, automatically updating itself after DML to base table.

No CTX_DDL.SYNC_INDEX is necessary.

CTXRULE

Use CTXRULE index to build a document classification or routing application.

This index is created on a table of queries, where the queries define the classification or routing criteria. 

ALTER INDEX Sync Methods

 MANUAL:

No automatic synchronization. This is the default. You must manually synchronize the index with CTX_DDL.SYNC_INDEX.

EVERY:

Automatically synchronize the index at a regular interval specified by the value of interval-string.

ON COMMIT:

Synchronize the index immediately after a commit.   

TRANSACTIONAL:

Specify that documents can be searched immediately after they are inserted or updated.

If a text index is created with TRANSACTIONAL enabled, then, in addition to processing the synchronized rowids already in the index, the CONTAINS operator will process unsynchronized rowids as well.

To turn on TRANSACTIONAL index property:

ALTER INDEX myidx REBUILD PARAMETERS('replace metadata transactional');
                To turn off TRANSACTIONAL index property:
ALTER INDEX myidx REBUILD PARAMETERS('replace metadata nontransactional');

Oracle Text Index preferences

DATASTORE:

  1.  DIRECT_DATASTORE: Indicates that the data is stored internally in text columns of a database table.
  2. MULTI_COLUMN_DATASTORE: Indicates that the data is stored in text table in more than one column. Columns are concatenated (joined) to create a virtual document and each concatenated row is indexed as a single document
  3. DETAIL_DATASTORE: Indicates that the data is stored internally in a text column.
  4. NESTED_DATASTORE: Indicates that the data is stored in nested tables
  5. FILE_DATASTORE: Indicates that the data is stored in Operating System files. This type of data source is supported only for CONTEXT index.
  6. URL_DATASTORE: Indicates that the data is stored over Internet.
  7. USER_DATASTORE: Indicates that the documents would be synthesized at index time by a user defined stored procedure

FILTER:

In this phase the text stream can be converted to format that is recognized by the Oracle text processing engine. 

SECTIONER:

 The task of the sectioner is to divide the incoming text stream into multiple sections based on the internal document structures (HTML or XML).

 LEXER:

This property determines the language associated with the incoming document.

 How to SYNC_INDEX after DML on base table

The following script is useful to synchronize the index with the base table when using context type of text index:

export ORACLE_SID=SID_NAME
export LD_LIBRARY_PATH=$ORACLE_HOME/lib:$ORACLE_HOME/ctx/lib:$ORACLE_HOME/lib32:/usr/lib
sqlplus "/as sysdba" << EOF
exec ctx_ddl.sync_index(idx_name =>'SCHEMA.INXEX’);
exit;
EOF

Oracle 19c DBMS_JOB Konvertierung mit Tücken

Unlängst machte ich bei einer Datenbank-Migration Bekanntschaft mit einer der vielen Verhaltensänderungen, wie sie bei Versionswechsel immer wieder vorkommen.

Der Kunde hatte sich entschieden seine bereits ans Herz gewachsene Oracle 11.2.0.4er Datenbank auf aktuellen Stand zu bringen und sich folgerichtig für die Oracle 19c Version entschieden, welche ja eine „Long Term Support“ Version ist.

Da die Applikationslogik bereits zuvor in einzelne Schemas aufgeteilt war, wählte ich als Migrationsmethode Datapump Export/Import.

DBMS_JOB konvertiert

Nach dem ersten Test-Import und der für Export/Import obligatorischen Analyse der Importfehler stellte ich mit Überraschung fest, dass die vorhandenen Datenbank Jobs, welche zuvor als klassische DBMS_JOB angelegt waren, nun automatisch in neue Scheduler-Jobs „konvertiert“ wurden.

Dieses Verhalten der automatischen Konvertierung kannte ich bisher nur bei einem klassischen Upgrade der Datenbank und es wird auch erst seit Version 19c so gehandhabt.

Das nun auch automatisch neue Scheduler-Jobs bei einem Datapump Import angelegt wurden, überraschte mich erstmal, war aber bei näherer Betrachtung durchaus logisch.

Wie inzwischen allgemein bekannt sein sollte, ist das bereits in die Jahre gekommene DBMS_JOB Package zur Verwaltung von klassischen Datenbank Jobs bereits seit Version 12.2.0.1 „deprecated“, was in der Praxis bedeutet, dass es zwar noch existiert, jedoch in zukünftigen Versionen jederzeit „desupported“ bzw. auch entfernt werden könnte.

Oracle empfiehlt daher komplett auf den „Scheduler“ (DBMS_SCHEDULER) umzusteigen, was in jedem Fall sinnvoll ist, bietet dieser doch wesentlich mehr Funktionalität.

Mit der Version 19c geht Oracle noch einen Schritt weiter und baut eine automatische Konvertierung für klassischen Jobs ein. Das bedeutet, dass das alte DBMS_JOB API noch wie gewohnt verwendet werden kann, Oracle jedoch im Hintergrund automatisch aus jedem neuen DBMS_JOB in Wirklichkeit einen Scheduler Job erzeugt. Das Package DBMS_JOB dient dabei nur mehr als Wrapper um die Rückwärtskompatibilität zu gewährleisten.

Unter der Haube werkt somit nur mehr der Scheduler (das Mapping zwischen „alten“ und „neuen“ Jobs findet man übrigens in der neuen Dictionary Tabelle: SCHEDULER$_DBMSJOB_MAP).

Damit ist auch das Verhalten während meines Datapump Imports erklärt: Da hier die Jobs ebenfalls neu angelegt wurden, erhalte ich auch sofort die dazugehörenden Scheduler Jobs.

SQL> select job,schema_user,what from dba_jobs where job=844;

JOB SCHEMA_USER WHAT
------------- ----------- -------------------------------------
844 DWH dbms_mview.refresh('"DWH"."MVIEW1"');


SQL> select job_name,owner,job_action from dba_scheduler_jobs 
where job_name like 'DBMS_JOB$_844';

JOB_NAME OWNER JOB_ACTION
------------- ---------- -------------------------------------
DBMS_JOB$_844 DWH dbms_mview.refresh('"DWH"."MVIEW1"');

Das Mapping zwischen „alten“ und „neuen“ Jobs findet man übrigens in der neuen Dictionary Tabelle: SCHEDULER$_DBMSJOB_MAP):

SQL> select * from SCHEDULER$_DBMSJOB_MAP where dbms_job_number=844;

DBMS_JOB_NUMBER JOB_OWNER JOB_NAME
--------------- --------- --------------
844 DWH DBMS_JOB$_844

Allgemeine DBMS_JOB Verhaltensänderungen

Trotz Automatismus dürfen folgende Änderungen nicht unerwähnt bleiben:

Berechtigungen

Da es sich nun immer um Scheduler Jobs handelt (auch wenn diese mittels DBMS_JOB angelegt werden) und damit um Datenbankobjekte, muss der User zwingend die Berechtigung zum Anlegen dieser Objekte über das „CREATE JOB“ Privileg erhalten, um weiterhin Jobs über DBMS_JOB anzulegen und zu verwalten.

Transaktionsverhalten

Verwendet man in einer früheren Version als 19c DBMS_JOB zur Verwaltung der Jobs, muss immer explizit ein „commit“ verwendet werden, um die jeweilige Transaktion abzuschließen.

Ab Oracle 19c ist dies nun nicht mehr notwendig – der Scheduler Job wird implizit angelegt.

Trotzdem bleibt die Transaktionsfunktionalität bei der Verwendung von DBMS_JOB erhalten.

Stolperstein NLS-Einstellungen

Nachdem die Jobs während meines Datapump Imports automatisch in Scheduler Jobs konvertiert wurden, war hier für mich als DBA eigentlich nichts weiter zu tun.

Die Freude währte allerdings nur kurz, denn der Kunde machte mich umgehend auf ein seltsames Phänomen aufmerksam: Einige Spalten in seinen Materialized Views hätten plötzlich seltsame Werte bekommen!

Zahlen, die in VARCHAR2 Feldern als Strings gespeichert waren, hatten nun wie von Geisterhand 6 Nullen als Nachkommastelle bekommen.

Meine erste Reaktion war natürlich sofort das Konstrukt „Zahlen in Strings zu speichern“ in Frage zu stellen, allerdings hatte dieser Umstand leider wie so oft eine historische Berechtigung und mit dem eigentlichen Problem gar nichts zu tun.

Lediglich die Auswirkungen eines ganz anderen Problems kamen aber genau an dieser Stelle glücklicherweise an die Oberfläche: Die Jobs, welche den Refresh dieser Materialized Views durchführten, waren genau jene Jobs welche beim Import automatisch konvertiert wurden.

Analyse

Nach einer kurzen Analyse war klar: Bei der Neuanlage der ursprünglichen Jobs während des Imports wurden offensichtlich geänderte NLS-Settings verwendet!

Durch die besondere Konstellation von mehreren verschachtelten Views, MViews und DB-Links führten diese NLS-Settings (konkret die Einstellung des Parameters NLS_NUMERIC_CHARACTERS) dazu, dass einige Zahlen nun in den MViews falsch dargestellt wurden.

Damit war das Problem erkannt und nach einigen Tests schnell behoben.
Das NLS-Environment für einen bestehenden Scheduler-Job lässt sich übrigens auch im Nachhinein einfach ändern (man muss also nicht den kompletten Job neu erstellen):

begin
sys.dbms_scheduler.set_attribute('"USER1"."DBMS_JOB$_1185"','NLS_ENV',
'NLS_LANGUAGE=''GERMAN'' NLS_TERRITORY=''GERMANY'' NLS_CURRENCY=''€'
'NLS_ISO_CURRENCY=''GERMANY'' NLS_NUMERIC_CHARACTERS='',.'
' NLS_DATE_FORMAT=''DD.MM.RR'' NLS_DATE_LANGUAGE=''GERMAN'
' NLS_SORT=''GERMAN''');
end;
/

Fazit:

  • DBMS_JOBs werden ab Oracle 19c automatisch in Scheduler Jobs „konvertiert“ (sowohl beim Upgrade wie auch beim Import) – das alte DBMS_JOB Interface bleibt vorerst vollständig erhalten.
  • Besser vor einem Upgrade alle DBMS_JOBs überprüfen und kontrolliert durch DBMS_SCHEDULER Jobs ersetzen.
  • Besonders beim Import vorab die NLS-Settings von Jobs überprüfen.

Mein persönlicher Tipp an dieser Stelle:

Auch wenn gute Automatismen vorhanden sind, sollte man sich immer schon im Vorfeld eines geplanten Versionswechsels mit bekannten Verhaltensänderungen auseinandersetzen und diese auch bereits vorab gelöst haben.

Dann gibt es auch keine unliebsamen Überraschungen mit unbekannten „Side-Effects“.