{"id":252,"date":"2015-12-30T11:04:26","date_gmt":"2015-12-30T09:04:26","guid":{"rendered":"http:\/\/blog.sqlora.com\/de\/?p=252"},"modified":"2018-08-19T22:29:32","modified_gmt":"2018-08-19T20:29:32","slug":"merge-and-ora-30926-teil-2-oder-unterschiede-bei-der-schreibkonsistenz-zwischen-merge-und-update","status":"publish","type":"post","link":"https:\/\/blog.sqlora.com\/de\/merge-and-ora-30926-teil-2-oder-unterschiede-bei-der-schreibkonsistenz-zwischen-merge-und-update\/","title":{"rendered":"MERGE and ORA-30926 &#8211; Teil 2 oder Unterschiede bei der Schreibkonsistenz zwischen Merge und Update"},"content":{"rendered":"<p>Im <a title=\"MERGE and ORA-30926\" href=\"https:\/\/blog.sqlora.com\/de\/merge-and-ora-30926\/\" target=\"_blank\">ersten Teil<\/a> haben wir gesehen, wie Oracle ein Merge-Statement drei Mal ausf\u00fchrt, wenn es zu einem ORA-30926-Fehler kommt. Um zu verstehen, was dabei eigentlich passiert, sollten wir das Verst\u00e4ndnis f\u00fcr das Konzept hinter &#8222;update restarts&#8220; oder auch manchmal &#8222;mini rollbacks&#8220; genannt, auffrischen. Das Konzept ist sehr gut von Tom Kyte beschrieben: <a title=\"Teil I\" href=\"http:\/\/tkyte.blogspot.de\/2005\/08\/something-different-part-i-of-iii.html\" target=\"_blank\">Teil I<\/a>, <a title=\"Teil II\" href=\"http:\/\/tkyte.blogspot.de\/2005\/08\/part-ii-seeing-restart.html\" target=\"_blank\">Teil II<\/a> and <a title=\"Teil III\" href=\"http:\/\/tkyte.blogspot.de\/2005\/09\/part-iii-why-is-restart-important-to.html\" target=\"_blank\">Teil III<\/a>. Wenn der Begriff &#8222;write consistency&#8220; f\u00fcr Sie unbekannt ist, empfehle ich sehr, ertsmal diesen Links zu folgen.<!--more--><\/p>\n<p>Vielen Dank auch an Sergey Markelenkov f\u00fcr seinen ausgezeichneten Artikel \u00fcber write consistency und statement restarts, leider nur in <a title=\"Write Consistency\" href=\"http:\/\/www.sql.ru\/forum\/actualfile.aspx?id=10688937\" target=\"_blank\">russisch<\/a> verf\u00fcgbar. Der ist auf jeden Fall eine \u00dcbersetzung Wert, ist aber 40 Seiten lang, sodass ich hier nur auf einige wichtige Erkenntnisse und Schl\u00fcsse zur\u00fcckgreifen werde.<\/p>\n<p><strong>UPDATE restart<\/strong><\/p>\n<p>Zuerst k\u00f6nnen wir zeigen, wie ein statement restart mit einem UPDATE stattfindet. Ich bin davon ausgegangen, dass es dann genauso auch mit dem UPDATE-Teil vom\u00a0 MERGE funktioniert. Tut es aber nicht!<\/p>\n<p>Wir legen zurst eine Testtabelle mit einem Zeilentrigger an. Dieser Trigger referenziert keine :NEW oder :OLD &#8211; Werte und ist nur daf\u00fcr da, um zu sehen, wie oft er &#8222;z\u00fcndet&#8220;. Dann aktualisieren wir den Datensatz mit\u00a0 ID=2 auf ID=3 ohne die \u00c4nderung mit einem COMMIT festzuschreiben.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">\r\n--Session\u00a01\r\nDROP\u00a0TABLE\u00a0t_target;\r\n\r\nCREATE\u00a0TABLE\u00a0t_target\u00a0\u00a0(id\u00a0NUMBER,\u00a0val\u00a0NUMBER);\r\n\r\nINSERT\u00a0INTO\u00a0t_target\u00a0(id,\u00a0val)\u00a0VALUES\u00a0\u00a0(1,1);\r\nINSERT\u00a0INTO\u00a0t_target\u00a0(id,\u00a0val)\u00a0VALUES\u00a0\u00a0(2,1);\r\n\r\nCOMMIT;\r\n\r\nCREATE\u00a0OR\u00a0REPLACE\u00a0TRIGGER\u00a0test_trg\r\nBEFORE\u00a0UPDATE\u00a0ON\u00a0t_target\r\nFOR\u00a0EACH\u00a0ROW\r\nBEGIN\r\n\u00a0\u00a0DBMS_OUTPUT.PUT_LINE('Fired');\r\nEND;\r\n\/\r\n\r\nUPDATE\u00a0t_target\u00a0SET\u00a0id\u00a0=\u00a03\u00a0WHERE\u00a0id\u00a0=\u00a02;\r\n\r\n<\/pre>\n<p>In einer anderen Session versuchen wir Datens\u00e4tze mit ID < 3 zu aktualisieren. Nat\u00fcrlich wird diese Session blockiert und muss warten, bis die Session 1 entweder einen Commit oder Rollback macht.\n\n<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">\r\n\r\n--Session\u00a02\r\n\r\nUPDATE\u00a0t_target\u00a0t\u00a0SET\u00a0\u00a0t.val\u00a0=\u00a0t.val\u00a0+\u00a010\u00a0WHERE\u00a0t.id\u00a0&lt;\u00a03;\r\n&amp;#91;\/code&amp;#93;\r\n\r\nAber was f\u00fcr ein Ergebnis k\u00f6nnen wir vom\u00a0 UPDATE in der Session 2 erwarten, wenn in der 1. Session ein Commit ausgef\u00fchrt wird? Wird nur ein Datensatz auktualisert oder zwei? Es gab zwei Datens\u00e4tze mit ID &lt; 3 zum Zeitpunkt als das UPDATE in der Session 2 gestartet wurde. Aber nach dem Festschreiben in der Session 1 hat der zweite Datensatz die WHERE-Bedingung nicht mehr erf\u00fcllt.\r\n\r\n&lt;br&gt;\r\n&#x5B;code language=&quot;sql&quot;]\r\n\r\nUPDATE\u00a0t_target\u00a0t\r\nSET\u00a0\u00a0\u00a0\u00a0t.val\u00a0=\u00a0t.val\u00a0+\u00a010\u00a0\r\nWHERE\u00a0\u00a0t.id\u00a0&lt;\u00a03;\r\n\r\nFired\r\nFired\r\nFired\r\n\r\n1\u00a0row\u00a0updated.\r\n...\r\n-------------------------------------------------------------------\r\n|\u00a0Id\u00a0\u00a0|\u00a0Operation\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0Name\u00a0\u00a0\u00a0\u00a0\u00a0|\u00a0Starts\u00a0|\u00a0E-Rows\u00a0|\u00a0A-Rows\u00a0|\r\n-------------------------------------------------------------------\r\n|\u00a0\u00a0\u00a00\u00a0|\u00a0UPDATE\u00a0STATEMENT\u00a0\u00a0\u00a0|\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0|\u00a0\u00a0\u00a0\u00a0\u00a0\u00a03\u00a0|\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0|\u00a0\u00a0\u00a0\u00a0\u00a0\u00a00\u00a0|\r\n|\u00a0\u00a0\u00a01\u00a0|\u00a0\u00a0UPDATE\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0|\u00a0T_TARGET\u00a0|\u00a0\u00a0\u00a0\u00a0\u00a0\u00a03\u00a0|\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0|\u00a0\u00a0\u00a0\u00a0\u00a0\u00a00\u00a0|\r\n|*\u00a0\u00a02\u00a0|\u00a0\u00a0\u00a0TABLE\u00a0ACCESS\u00a0FULL|\u00a0T_TARGET\u00a0|\u00a0\u00a0\u00a0\u00a0\u00a0\u00a03\u00a0|\u00a0\u00a0\u00a0\u00a0\u00a0\u00a02\u00a0|\u00a0\u00a0\u00a0\u00a0\u00a0\u00a04\u00a0|\r\n-------------------------------------------------------------------\r\n&amp;#91;\/code&amp;#93;\r\n\r\nWir k\u00f6nnen hier das Restart von unserem UPDATE beobachten. Das hat erst die zu aktualisierende Datens\u00e4tze im konsistenten Modus zum Startzeitpunkt des Statements\u00a0 identifiziert. Dann versucht Oracle, einen Lock auf diese Datens\u00e4tze zu plazieren und diese zu aktualisieren. Der Datenbank-Block wird noch mal im &quot;current&quot; Modus geholt. Es macht Sinn, denn wir wollen die aktuellste Kopie der Daten aktualisieren und nicht die m\u00f6glicherweise veraltete. Hier wird dann gepr\u00fcft, ob die Daten, die in der Suchbedingung partizipieren, unver\u00e4ndert blieben. Falls die WHERE-Bedingung nicht mehr zutreffend ist, wird das ganze Statement neu gestartet. Alle \u00c4nderungen, die das UPDATE zu diesem Zeitpunkt gemacht hat, werden zur\u00fcckgerollt. Wir fragen eine neue SCN an und selektieren alle zu aktualisierenden Datens\u00e4tze im konsitenten Modus respektive dieser neuen SCN. Dabei werden diese Datens\u00e4tze blockiert (aka SELECT FOR UPDATE). Soll das nicht m\u00f6glich sein, wird neu versucht, insgesamt bis 5000 Mal. Nach dem alle Datens\u00e4tze blockiert wurden, kann Oracle die alle sicher aktualsieren.\r\n\r\nZur\u00fcck zu unserem Beispiel: das\u00a0 UPDATE in der Session 2 hat den ersten Datensatz mit ID=1 aktualisiert, der Trigger wird zum ersten Mal ausgel\u00f6st. Dann muss diese zweite Session auf die erste warten, weil unm\u00f6glich ist, einen Lock auf den Datensatz mit ID=2 zu platzieren. Nach dem Commit in der Session 1 wird es dann m\u00f6glich sein. Der Trigger wird zum ersten Mal ausgel\u00f6st. Aber dann sagt die Pr\u00fcfung, dass der Datensatz der WHERE-Bedingung nicht mehr entspricht, denn die Session 1 hat die ID auf 3 aktualisiert. Die bereits gemacht \u00c4nderung am ersten Datensatz wird r\u00fcckg\u00e4ngig gemacht. Ein neuer Versuch wird gestartet, mit einer neuen SCN und kann die \u00c4nderungen der Seesion 1im konsistenten Modus sehen. Demzufolge wird nur ein Datensatz mit ID=1 selektiert und dann aktualisiert. Die dritte Ausl\u00f6sung vom Trigger.\r\n\r\nWir k\u00f6nnen sehen, wie Oracle uns 3 Starts in Explain-Plan-Ausgabe zeigt. Auch A-Rows ist 4, obwohl wir nur einen Datensatz aktualisiert haben. Ich denke, es wurde beim ersten Versuch auf zwei hochgez\u00e4hlt, dann auf drei beim SELECT FOR UPDATE und dann auf vier bei der eigentlichen Aktualisierung vom Datensatz.\r\n\r\nWe k\u00f6nnen auch eine Best\u00e4tigung von dem Restart sehen, indem wir die Session-Statistiken vor und nach dem UPDATE abfragen:\r\n&lt;ul&gt;\r\n\t&lt;li&gt;&lt;em&gt;calls to kcmgas&lt;\/em&gt; - we k\u00f6nnen den Request der neuen SCN sehen&lt;\/li&gt;\r\n\t&lt;li&gt;&lt;em&gt;rollback changes - undo records applied&lt;\/em&gt; - we sehen ein Rollback ohne ihn explizit auszuf\u00fchren&lt;\/li&gt;\r\n\t&lt;li&gt;&lt;em&gt;table scan rows gotten&lt;\/em&gt; - wird gr\u00f6\u00dfer als erwartet sein&lt;\/li&gt;\r\n&lt;\/ul&gt;\r\nF\u00fcr mich ist es eine Art von &quot;optimistic locking&quot;,\u00a0 implementiert direkt in der Datenbank. \u00c4hnlich wie eine Applikation gegen verlorene Updates vorgehen sollte, indem z.B. die abgefragten Daten mit den aktuellen Daten in der Datenbank unmittelbar vor der Aktualisierung abgeglichen werden, stellt Oracle hier sicher, dass wirklich nur die Datens\u00e4tze\u00a0 aktualisiert werden, die zur Startzeit identifiziert wurden. Und wenn das nicht klappt, wird diese Startzeit eben neu definiert\u00a0;-)\u00a0\u00a0 Spa\u00df beiseite - ein ziemlich sinnvolles Feature.\r\n\r\nEinige generelle Anmerkungen noch: nicht nur bei UPDATE Statements, sondern auch bei DELETE, SELECT FOR UPDATE und MERGE k\u00f6nnen Restarts auftreten. Das kann nur passieren, wenn die Transaktion in der Isolationsebene READ COMMITTED arbeitet. Bei\u00a0 SERIALIZABLE bekommt man in entsprechender Situation den Fehler ORA-08177 can&#039;t serialize access.\r\n\r\nEs gibt einen anderen Fall, der zum Restart f\u00fchrt: wenn wir einen BEFORE Row Trigger haben, der die :OLD Werte referenziert, und diese sich seit Statement Beginn ge\u00e4ndert haben. Wir betrachten aber diesen Fall nicht in dem Post.\r\n\r\n&lt;strong&gt;Und was ist mit MERGE?&lt;\/strong&gt;\r\n\r\nAber warten Sie! Wollten wir nicht eigentlich, das Verhalten von einem MERGE Statement untersuchen? Verh\u00e4lt sich der UPDATE-Teil von einem MERGE genauso? I w\u00fcrde sagen, ja, sollte definitiv so sein, aber...\r\n\r\nWir werden in der Session 1 exakt dasselbe tun. In der Session 2 werden wir das UPDATE mit einem MERGE ersetzen und sehen, was nach dem Commit in der Session 1 passiert:\r\n\r\n&#x5B;code language=&quot;sql&quot;]\r\nMERGE\u00a0INTO\u00a0t_target\u00a0t\r\nUSING\u00a0(select\u00a01\u00a0from\u00a0dual)\u00a0q\r\nON\u00a0\u00a0\u00a0\u00a0(t.id &lt; 3)\u00a0\r\nWHEN\u00a0MATCHED\u00a0THEN\u00a0UPDATE\u00a0SET\u00a0\u00a0t.val\u00a0=\u00a00;\u00a0\r\n\r\nFired\u00a0\r\nFired\u00a0\r\n\r\n2\u00a0rows\u00a0merged.\u00a0\r\n\r\n--------------------------------------------------------------------\r\n|\u00a0Id\u00a0\u00a0|\u00a0Operation \u00a0\u00a0\u00a0\u00a0      \u00a0|\u00a0Name     |\u00a0Starts\u00a0|\u00a0E-Rows\u00a0|\u00a0A-Rows\u00a0|\r\n--------------------------------------------------------------------\r\n|\u00a0\u00a0\u00a00\u00a0|\u00a0MERGE\u00a0STATEMENT\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0|          |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a01\u00a0| \u00a0\u00a0     |     \u00a00\u00a0|\r\n|\u00a0\u00a0\u00a01\u00a0|\u00a0\u00a0MERGE \u00a0\u00a0\u00a0\u00a0         \u00a0|\u00a0T_TARGET\u00a0|\u00a0\u00a0\u00a0\u00a0\u00a0\u00a01\u00a0| \u00a0\u00a0     | \u00a0    0\u00a0|\r\n|\u00a0\u00a0\u00a02\u00a0|\u00a0\u00a0\u00a0VIEW \u00a0\u00a0\u00a0\u00a0\u00a0         |          |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a01\u00a0| \u00a0\u00a0     | \u00a0    2\u00a0|\r\n|\u00a0\u00a0\u00a03\u00a0|\u00a0\u00a0\u00a0\u00a0NESTED\u00a0LOOPS\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0|          |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a01\u00a0|      2\u00a0|     \u00a02\u00a0|\r\n|\u00a0\u00a0\u00a04\u00a0|\u00a0\u00a0\u00a0\u00a0\u00a0FAST\u00a0DUAL \u00a0\u00a0\u00a0\u00a0\u00a0  |          |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a01\u00a0|      1\u00a0| \u00a0    1\u00a0|\r\n|*\u00a0\u00a05\u00a0|\u00a0\u00a0\u00a0\u00a0\u00a0TABLE\u00a0ACCESS\u00a0FULL|\u00a0T_TARGET\u00a0|\u00a0\u00a0\u00a0\u00a0\u00a0\u00a01\u00a0|      2\u00a0|     \u00a02\u00a0|\r\n--------------------------------------------------------------------\r\n\r\nSELECT * FROM t_target;\r\n\r\n ID \u00a0\u00a0VAL\r\n----------\u00a0----------\r\n \u00a01 \u00a0\u00a0\u00a0\u00a00\r\n \u00a03 \u00a0\u00a0\u00a0\u00a00\r\n&amp;#91;\/code&amp;#93;\r\n\r\nOffensichtlich kein Restart hier! Unser MERGE hat gerade die Spalte VAL zu 0 aktualisiert an einem Datensatz, der dem Suchkriterium ID &lt; 3 gar nicht mehr entspricht!\r\n\r\nAber was passiert, wenn wir das Beispiel so ab\u00e4ndern, dass die Session 1 die Spalte VAL statt ID aktualisiert?\r\n&lt;br&gt;\r\n&#x5B;code language=&quot;sql&quot;]\r\n--Session\u00a01\r\n\r\nUPDATE\u00a0t_target\u00a0SET\u00a0val\u00a0=\u00a03\u00a0WHERE\u00a0id\u00a0=\u00a02;\r\n\r\n--Session\u00a02\r\n\r\nMERGE\u00a0\/*+\u00a0gather_plan_statistics\u00a0*\/\u00a0INTO\u00a0t_target\u00a0t\r\nUSING\u00a0(SELECT\u00a010\u00a0val\u00a0FROM\u00a0dual)\u00a0q\r\nON\u00a0\u00a0\u00a0\u00a0(t.id &lt; 3)\u00a0\u00a0\r\nWHEN\u00a0MATCHED\u00a0THEN\u00a0UPDATE\u00a0SET\u00a0\u00a0t.val\u00a0=\u00a0t.val+q.val;\u00a0\r\n\r\nFired\u00a0\r\nFired\u00a0\r\nFired\u00a0\r\nFired\u00a0\r\n\r\n2\u00a0rows\u00a0merged.\u00a0\r\n...\r\n--------------------------------------------------------------------\r\n|\u00a0Id\u00a0\u00a0|\u00a0Operation \u00a0\u00a0\u00a0\u00a0\u00a0      |\u00a0Name     |\u00a0Starts\u00a0|\u00a0E-Rows\u00a0|\u00a0A-Rows\u00a0|\r\n--------------------------------------------------------------------\r\n|\u00a0\u00a0\u00a00\u00a0|\u00a0MERGE\u00a0STATEMENT\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0|          |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a03\u00a0|      \u00a0 |      0\u00a0|\r\n|\u00a0\u00a0\u00a01\u00a0|\u00a0\u00a0MERGE \u00a0\u00a0\u00a0\u00a0\u00a0         |\u00a0T_TARGET\u00a0|\u00a0\u00a0\u00a0\u00a0\u00a0\u00a03\u00a0| \u00a0\u00a0     |     \u00a00\u00a0|\r\n|\u00a0\u00a0\u00a02\u00a0|\u00a0\u00a0\u00a0VIEW \u00a0\u00a0\u00a0\u00a0\u00a0         |          |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a03\u00a0|      \u00a0 |     \u00a06\u00a0|\r\n|\u00a0\u00a0\u00a03\u00a0|\u00a0\u00a0\u00a0\u00a0NESTED\u00a0LOOPS\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0|          |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a03\u00a0|      2\u00a0|      6\u00a0|\r\n|\u00a0\u00a0\u00a04\u00a0|\u00a0\u00a0\u00a0\u00a0\u00a0FAST\u00a0DUAL \u00a0\u00a0\u00a0\u00a0\u00a0  |          |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a03\u00a0|      1\u00a0| \u00a0    3\u00a0|\r\n|*\u00a0\u00a05\u00a0|\u00a0\u00a0\u00a0\u00a0\u00a0TABLE\u00a0ACCESS\u00a0FULL|\u00a0T_TARGET\u00a0|\u00a0\u00a0\u00a0\u00a0\u00a0\u00a03\u00a0|      2\u00a0|     \u00a06\u00a0|\r\n--------------------------------------------------------------------\r\n\r\nSELECT\u00a0*\u00a0FROM\u00a0t_target;\r\n\r\n\u00a0ID\u00a0VAL\r\n---\u00a0---\r\n\u00a0\u00a01\u00a0\u00a011\r\n\u00a0\u00a02\u00a0\u00a013\r\n&amp;#91;\/code&amp;#93;\r\nMan kann klar sehen, dass ein Restrat stattgefunden hat.\u00a0 Unser MERGE hat die Spalte VAL aktualisiert und 10 zu dem Wert 3 addiert. Dieser Wert 3 war wiederum das Ergebnis von dem Update aus der Session 1, das wir erst NACH (!) dem MERGE gestartet und committet hatten.\r\n\r\nUnd was ist, wenn unsere Session denselben Datensatz mehrfach zu aktualisieren versucht? Kein UPDATE in der Session 1 diesmal, einfach ein MERGE in der Session 2\r\n\r\n&lt;br&gt;\r\n&#x5B;code language=&quot;sql&quot;]\r\nMERGE\u00a0\/*+\u00a0gather_plan_statistics\u00a0*\/\u00a0INTO\u00a0t_target\u00a0t\r\nUSING\u00a0(SELECT\u00a02\u00a0id,\u00a010\u00a0val\u00a0FROM\u00a0dual\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0UNION\u00a0ALL\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0SELECT\u00a02\u00a0id,\u00a010\u00a0val\u00a0FROM\u00a0dual)\u00a0q\r\nON\u00a0(t.id\u00a0=\u00a0q.id)\r\nWHEN\u00a0MATCHED\u00a0THEN\u00a0UPDATE\u00a0SET\u00a0\u00a0t.val\u00a0=\u00a0t.val\u00a0+\u00a0q.val;\r\nFired\r\nFired\r\nFired\r\n\r\nMERGE\u00a0\/*+\u00a0gather_plan_statistics\u00a0*\/\u00a0INTO\u00a0t_target\u00a0t\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0*\r\nERROR\u00a0at\u00a0line\u00a01:\r\nORA-30926:\u00a0unable\u00a0to\u00a0get\u00a0a\u00a0stable\u00a0set\u00a0of\u00a0rows\u00a0in\u00a0the\u00a0source\u00a0tables\r\n\r\n--------------------------------------------------------------------\r\n|\u00a0Id\u00a0\u00a0|\u00a0Operation \u00a0\u00a0\u00a0\u00a0\u00a0      |\u00a0Name     |\u00a0Starts\u00a0|\u00a0E-Rows\u00a0|\u00a0A-Rows\u00a0|\r\n--------------------------------------------------------------------\r\n|\u00a0\u00a0\u00a00\u00a0|\u00a0MERGE\u00a0STATEMENT\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0|          |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a03\u00a0|      \u00a0 |     \u00a00\u00a0|\r\n|\u00a0\u00a0\u00a01\u00a0|\u00a0\u00a0MERGE \u00a0\u00a0\u00a0\u00a0        \u00a0 |\u00a0T_TARGET\u00a0|\u00a0\u00a0\u00a0\u00a0\u00a0\u00a03\u00a0| \u00a0\u00a0     |     \u00a00\u00a0|\r\n|\u00a0\u00a0\u00a02\u00a0|\u00a0\u00a0\u00a0VIEW \u00a0\u00a0\u00a0\u00a0         \u00a0|          |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a03\u00a0| \u00a0     \u00a0| \u00a0    5\u00a0|\r\n|*\u00a0\u00a03\u00a0|\u00a0\u00a0\u00a0\u00a0HASH\u00a0JOIN \u00a0\u00a0\u00a0\u00a0   \u00a0|          |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a03\u00a0|      2\u00a0| \u00a0    5\u00a0|\r\n|\u00a0\u00a0\u00a04\u00a0|\u00a0\u00a0\u00a0\u00a0\u00a0VIEW \u00a0\u00a0\u00a0\u00a0       \u00a0|          |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a03\u00a0|      2\u00a0| \u00a0    6\u00a0|\r\n|\u00a0\u00a0\u00a05\u00a0|\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0UNION-ALL \u00a0\u00a0\u00a0\u00a0 \u00a0|          |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a03\u00a0| \u00a0     \u00a0| \u00a0    6\u00a0|\r\n|\u00a0\u00a0\u00a06\u00a0|\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0FAST\u00a0DUAL\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0|          |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a03\u00a0|      1\u00a0|     \u00a03\u00a0|\r\n|\u00a0\u00a0\u00a07\u00a0|\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0FAST\u00a0DUAL\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0|          |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a03\u00a0|      1\u00a0|     \u00a03\u00a0|\r\n|\u00a0\u00a0\u00a08\u00a0|\u00a0\u00a0\u00a0\u00a0\u00a0TABLE\u00a0ACCESS\u00a0FULL|\u00a0T_TARGET\u00a0|\u00a0\u00a0\u00a0\u00a0\u00a0\u00a03\u00a0|      2\u00a0| \u00a0    6\u00a0|\r\n--------------------------------------------------------------------\r\n<\/pre>\n<p>Erstmal, sieht man, dass das MERGE restartet wurde. Danach kommt aber der Fehler ORA-30926. Eine Trace-Datei wird dabei erzeugt, in der wir sehen k\u00f6nnen, was dem Oracle nicht gef\u00e4llt:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nCompare\u00a0value:\r\n\u00a0\u00a0cmpp\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0:\u00a00x6da03ff1\r\n\u00a0\u00a0cmpl\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0:\u00a02\r\n\u00a0\u00a0cmpf\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0:\u00a00\r\n\u00a0\u00a0Data: &lt; c1\u00a002 &gt;\r\nCurrent\u00a0value:\r\n\u00a0\u00a0u-&gt;kduudcol&#x5B;(piececol)]\u00a0\u00a0\u00a0\u00a0:\u00a00x70c9bff1\r\n\u00a0\u00a0u-&gt;kduudlen&#x5B;(piececol)]\u00a0\u00a0\u00a0\u00a0:\u00a02\r\n\u00a0\u00a0u-&gt;kduudflag&#x5B;(piececol)]\u00a0\u00a0\u00a0:\u00a00\r\n\u00a0\u00a0Data: &lt; c1\u00a00c &gt;\r\n<\/pre>\n<p>Die erste Aktualisierung \u00e4ndert den Wert der Spalte VAL auf 11, das ist &#8222;C1 0C&#8220; im internen Speicherformat. Der zweite Versuch stellt fest, dass der aktuelle Wert der Spalte &#8222;C1 0C&#8220; nun anders ist, als der vom konsistenten Lesemodus &#8222;C1 02&#8220; (interne Darstellung von 1). Mein Verst\u00e4ndnis ist es, an der Stelle ist klar, dass der Fehler ORA-30926 zur\u00fcckgegeben wird. Warum versucht Oracle trotzdem dieselbe Vorgehensweise mit dem Restart? Es kann doch an der Situation nichts \u00e4ndern&#8230;<\/p>\n<p><strong>Fazit<\/strong><\/p>\n<p>Wenn wir Datens\u00e4tze einer Tabelle aktualisieren, k\u00f6nnen wir hier zwei Aspekte betrachten, unabh\u00e4ngig davon, ob wir es mit einem UPDATE oder MERGE tun.\u00a0 Der erste: welche Datens\u00e4tze werden aktualisiert? Kann andere DML-Aktivit\u00e4t, die nach dem Starten von der Aktualisierung stattfindet, die Menge dieser Datens\u00e4tze beeinfl\u00fc\u00dfen? \u00a0 Und der zweite: kann es zu &#8222;verlorenen updates&#8220; kommen?<\/p>\n<p>Wenn meine Erkenntnisse korrekt sind, kann man sagen:<\/p>\n<ul>\n<li>Ein lang laufendes <strong>UPDATE<\/strong> kann die \u00c4nderungen sehen, die nach dem Start gemacht und festgeschrieben wurden, wenn diese die WHERE-Bedingung beeinfl\u00fc\u00dfen werden und somit ide Menge der zu aktualisierenden Datens\u00e4tze. Ein UPDATE wird nur Datens\u00e4tze aktualsieren, die im konsistenten Lesemodus zur SCN vom Startzeitpunkt oder zu einer neuen\u00a0 SCN, falls es zu einem Restart kam, identifiziert wurden. Es kann aber auch sein, dass das ganze Update fehlschalgen wird, falls es nicht m\u00f6glich war die Datens\u00e4tze nach dem Restart zu blockieren. Es gibt aber keinen Mechanismus, um vor verlorenen Updates zu sch\u00fctzen.<\/li>\n<li>Ein lang laufendes <strong>MERGE <\/strong>wird nur Datens\u00e4tze aktualsieren, die im konsistenten Lesemodus zur SCN vom Startzeitpunkt vom MERGE identifiziert wurden. Die Menge wird eindeutig an diesem Zeitpunkt definiert. Wenn andere Transaktion die Daten \u00e4ndert, die in der ON-Bedingung benutzt wird, und diese \u00c4nderung festschreibt, wird das keine Auswirkung haben &#8211; die Datens\u00e4tze werden trotzdem mit dem zuvor gestarteten MERGE aktualisiert. Auf der anderen Seite, ein MERGE wird restartet bei \u00c4nderungen in den\u00a0 Spalten\u00a0 der SET-Klausel. Somit gibt es quasi einen Schutz vor verlorenen Updates f\u00fcr Laufzeit des Statements<\/li>\n<\/ul>\n<p>Dieser Unterschied ist etwas verwirrend f\u00fcr mich. Meines Erachtens handelt es sich hier um einen Bug. Ich habe eine <a href=\"https:\/\/asktom.oracle.com\/pls\/apex\/f?p=100:11:::NO::P11_QUESTION_ID:61865893444475\" target=\"_blank\">Diskussion<\/a> auf AskTom gefunden, wo Tom Kyte damit auch einverstanden ist und mitteilt, dass er daf\u00fcr einen Bug eingetsellt hat (2006!) Das scheint aber ein nicht \u00f6ffentlicher Bug zu sein, denn man findet ihn nicht auf MOS-Seiten.<\/p>\n<pre>Hdr: 5214824 Abstract: READ CONSISTENCY NOT WORKING CORRECTLY ON MERGE STATEMENT<\/pre>\n<p>Nach dieser Diskussion auf Ask Tom wird es in dem Bug bem\u00e4ngelt, dass der Restart nicht stattfindet bei \u00c4nderungen in ON-Spalten. Nun, dass der Restart bei \u00c4nderungen in SET-Spalten\u00a0 doch stattfindet, ist genauso nicht dokumentiert. In der Dokumentation steht nur das:<\/p>\n<p><strong><em>MERGE is a deterministic statement. You cannot update the same row of the target table multiple times in the same MERGE statement.<\/em><\/strong><\/p>\n<p>Das erkl\u00e4rt aber das ganze Verhalten nicht wirklich. Das erkl\u00e4rt nur, dass der ORA-30926 durchaus berechtigt kommt. Wenn er kommt&#8230; Denn es gibt wohl gewisse Unregelm\u00e4\u00dfigkiten dabei, was ich in <a href=\"https:\/\/blog.sqlora.com\/de\/bye-bye-bypass_ujvc\/\" target=\"_blank\">einem anderen Post bereits erw\u00e4hnt habe.<\/a><\/p>\n<p>Ein anderer Bug mit MERGE ist es auch meiner Sicht, dass Oracle ein Restart bei Datendiskrepanz in SET-Spalten auch dann durchf\u00fchrt, wenn diese Diskrepanz durch vorherige Aktualisierung aus derselben Sitzung verursacht wird und es berechtigterweise zu ORA-30926 kommt. Aber davor wird die ganze Arbeit unn\u00f6tig dreifach wiederholt. Ich kann keine plausible Erkl\u00e4rung finden, warum das n\u00f6tig sein k\u00f6nnte. Sie vielleicht?<\/p>\n<p>Die ganze Forschung zeit nochmal, wie gef\u00e4hrlich es sein kann ein Massenupdate durchzuf\u00fchren, wenn andere DML-Aktivit\u00e4t auf der Tabelle stattfindet. Das Ergebnis variiert vom deutlich h\u00f6heren Resourcenverbrauch bis zu unvorhersagbaren und schlicht falschen Ergebnissen.<\/p>\n<p>Anzumerken ist es auch, dass das erw\u00e4hnte Feature von MERGE, wobei nach Feststellung der Datendiskrepanz in den SET-Spalten ein Restart durchgef\u00fchrt wird, keinesfalls als Ersatz f\u00fcr ein echtes Konzept vom Optimistc Locking in Ihrer\u00a0 Applikation verstanden werden darf. Hierbei geht es nur um die \u00c4nderungen, die nach dem Starten vom MERGE gemacht wurden und nicht in der Zeit zwischen Abfragen der Daten, Editieren in der Applikation und dann dem Ausf\u00fchren von dem MERGE-Befehl.<\/p>\n<p><strong>Weiterf\u00fchrende Links<\/strong><\/p>\n<p><a href=\"https:\/\/asktom.oracle.com\/pls\/asktom\/f?p=100:11:1986990476734034::::P11_QUESTION_ID:11504247549852\" target=\"_blank\">Diskussion on Asktom \u00fcber write consistency<\/a><\/p>\n<p><a href=\"https:\/\/asktom.oracle.com\/pls\/apex\/f?p=100:11:::NO::P11_QUESTION_ID:61865893444475\" target=\"_blank\">Weitere Diskussion \u00fcber Merge<\/a><\/p>\n<p><a href=\"https:\/\/docs.oracle.com\/database\/121\/SQLRF\/statements_9016.htm#SQLRF01606\" target=\"_blank\">Oracle Dokumentation &#8211; Merge Statement<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Im ersten Teil haben wir gesehen, wie Oracle ein Merge-Statement drei Mal ausf\u00fchrt, wenn es zu einem ORA-30926-Fehler kommt. Um zu verstehen, was dabei eigentlich passiert, sollten wir das Verst\u00e4ndnis f\u00fcr das Konzept hinter &#8222;update restarts&#8220; oder auch manchmal &#8222;mini rollbacks&#8220; genannt, auffrischen. Das Konzept ist sehr gut von Tom Kyte beschrieben: Teil I, Teil [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1,31,3,4,22],"tags":[10,32,34,23,36],"class_list":["post-252","post","type-post","status-publish","format-standard","hentry","category-allgemein","category-merge","category-oracle","category-sql","category-trivadis","tag-merge","tag-ora-30926","tag-statement-restart","tag-trivadiscontent","tag-write-consistency"],"_links":{"self":[{"href":"https:\/\/blog.sqlora.com\/de\/wp-json\/wp\/v2\/posts\/252","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.sqlora.com\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.sqlora.com\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.sqlora.com\/de\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.sqlora.com\/de\/wp-json\/wp\/v2\/comments?post=252"}],"version-history":[{"count":26,"href":"https:\/\/blog.sqlora.com\/de\/wp-json\/wp\/v2\/posts\/252\/revisions"}],"predecessor-version":[{"id":443,"href":"https:\/\/blog.sqlora.com\/de\/wp-json\/wp\/v2\/posts\/252\/revisions\/443"}],"wp:attachment":[{"href":"https:\/\/blog.sqlora.com\/de\/wp-json\/wp\/v2\/media?parent=252"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.sqlora.com\/de\/wp-json\/wp\/v2\/categories?post=252"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.sqlora.com\/de\/wp-json\/wp\/v2\/tags?post=252"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}