Firebird 5 demnächst RC

Produkt- und Serviceankündigungen zu Firebird.

Moderator: martin.koeditz

bfuerchau
Beiträge: 490
Registriert: Mo 7. Mai 2018, 18:09
Kontaktdaten:

Also bei mir ist der Default mit 1 gesetzt.
Da ich den Index autonom erstelle und inaktiviere und nach dem Script der Index aktiv ist, gehe ich erst mal davon aus, dass es funktioniert;-).
Ich kann das aber auch noch mal gezielt mit 1 und 0 probieren.
bfuerchau
Beiträge: 490
Registriert: Mo 7. Mai 2018, 18:09
Kontaktdaten:

Das wäre dann tatsächlich ein Kompatibilitätsbruch.
Beim Wechsel auf V4 müsste man dann halt den Default anpassen, wenn man denn solche Transaktionen macht.
Wie oben gesagt, meine Scripte laufen problemlos, d.h., der erstellte Index in der autonomen Transaktion ist anschließend für die Aktivierung und die Indexaktualisierung sichtbar.
Wenn es sich vermeiden lässt, sollte man geschachtelte Transaktionen sowieso vermeiden. Ich habe ehrlicherweise auch noch keine Anwendung dafür gefunden.
Dasselbe gilt auch für Savepoints. Für mich sind das eh nur Krücken für Designfehler.
Ich verwende auch die autonome Transaktion nur weil es da ist. In der FB1.5 habe ich halt 2 Transaktionen und Aufrufe gehabt.
Beim SQL-Server ist das sogar noch chaotischer, da dort Commits/Rollbacks in einer Prozedur erlaubt sind. Solche Konzepte gehören verboten;-).
Aber Microsoft weigert sich auch auch immer noch gegen Before-Trigger, die das Leben wirklich einfacher als summarische After-Trigger machen.
Benutzeravatar
martin.koeditz
Beiträge: 446
Registriert: Sa 31. Mär 2018, 14:35

bfuerchau hat geschrieben: Do 27. Jul 2023, 16:43 Aber Microsoft weigert sich auch auch immer noch gegen Before-Trigger, die das Leben wirklich einfacher als summarische After-Trigger machen.
Stimmt! :D
Martin Köditz
it & synergy GmbH
vr2
Beiträge: 219
Registriert: Fr 13. Apr 2018, 00:13

bfuerchau, ich hab Deinen Indexerzeuger mal etwas lesbarer formatiert, so dass man besser erkennt, was der da macht, paar VB-Konstrukte sind noch drin:

Code: Alles auswählen

execute block returns (xCount integer) 
as 
  declare xCmd varchar(" & Len(pSQL) + 1 & ");
  declare xValue integer; 
begin 
  begin 
    in autonomous transaction Do 
    begin 
      xCmd = pSQL;  
      for select first 1 a.lockvalue from zgloballock a with lock into :xvalue Do  
      begin
        execute statement :xcmd;
        xCmd='alter index " & xName & " inactive';
        execute statement :xcmd;
        when any do 
        begin 
          exception; 
        end
      End 
    End 
  End
  
  xCmd = 'alter index " & xName & " active';           
  begin 
    in autonomous transaction do 
      execute statement :xcmd; 
  end
  
  for select round(coalesce(1/nullif(a.rdb$statistics, 0), 0), 0) xcount 
      from rdb$indices a 
      where a.rdb$index_name = '" & xName & "'"
  into :xCount do 
  begin 
    suspend; 
  end
End
Im ersten Tei erzeugst Du den Index und deaktivierst ihn. Beide Aktionen nur, nachdem Du das lock gekriegt hast. Nur im ersten Teil eine exception-Behandlung.
Im zweiten Teil aktivierst Du den Index,
im dritten ermittelst Du die Satzanzahl.

Die exception ist anonym, keine custom exception. Dann kannst Du sie auch weglassen und when any auch, weil wenn es kracht, gibt es eh die default exception.

Nur die ersten beiden Teile laufen in autonomen Transaktionen. Bei ReadConsistency = 1 dürftest Du im dritten Teil die aktualisierte Selektivität aus rdb$statistics in dem execute block noch nicht sehen. Und stattdessen eine veraltete Satzzahl.

Es kommt noch drauf an, wie die aufrufende Transaktion konfiguriert ist.
bfuerchau
Beiträge: 490
Registriert: Mo 7. Mai 2018, 18:09
Kontaktdaten:

Meine aufrufenden Transaktionen sind grundsätzlich ReadCommited. Da via ODBC-Treiber oder NetProvider immer nit RecVersion.
Wenn ich nach dem Execute mit IBExpert (der läuft auch noch) reinsehe, ist der Index aktiv und die Selekitivity passt.

Nachtrag:
In der Verbindung wird mir WaitLock und Locktimeout von 15 Sekunden gearbeitet.
Die obigen Routinen werden ausschließlich für DDL (Create/Alter/Drop) angewendet.
Da diese sehr schnell sind, besteht die Sperre eben nur äußerst kurz.
Und der getrennte Commit für den Indexaufbau stört dann keine anderen DDL-Anweisungen mehr, wie es eine gemeinsame Transaktion für Create Table und/oder Create Index bedeuten würde.
vr2
Beiträge: 219
Registriert: Fr 13. Apr 2018, 00:13

bfuerchau hat geschrieben: So 30. Jul 2023, 14:16 Meine aufrufenden Transaktionen sind grundsätzlich ReadCommited. Da via ODBC-Treiber oder NetProvider immer nit RecVersion.
Wenn ich nach dem Execute mit IBExpert (der läuft auch noch) reinsehe, ist der Index aktiv und die Selekitivity passt.
Danach ja, aber dann ist die TX des execute blocks bereits committet und IBE benutzt eine neue TX. Dann natürlich.
Aber es geht um die Sichtbarkeit innerhalb einer Transaktion und innerhalb einer stored routine oder eines execute blocks bei ReadConsistency. Der execute block gibt Dir nicht die korrekte Satzzahl zurück, wenn bei ReadCommitted das sub-level ReadConsistency = 1 konfiguriert ist, durch den Schalter ReadConsistency = 1 in firebird.conf. Dann wird Dein sub-level record_version ignoriert. Und eine stored routine oder ein execute block sieht bei ReadCommitted dann nur noch die eigenen Änderungen, aber nicht mehr die anderer Transaktionen, selbst wenn die nach seinem Start committet wurden, und bevor er die Änderungen abfragt, alles noch in der eigenen Transaktion. Das ist grundlegend anders als bisher. Bisher (ReadConsistency = 0, egal ob record_version oder no record_version) hat eine stored routine oder ein execute block, der in einer ReadCommited TX ausgeführt wurde, zwischenzeitlich commitete Änderungen anderer Transaktionen innerhalb seines bodies gesehen. Das ist ja genau der Zweck und die Definition von ReadCommitted, maximale Sichtbarkeit und Nebenläufigkeit von Datenänderungen, ohne dafür die Konsistenz zu opfern wie etwa bei Dirty Read (was es bei Firebird nicht gibt).

Ich habe Dein Beispiel eben noch mal getestet, ziemlich vereinfacht, und das zeigt es ganz klar. Vorbereitung eine Testtabelle tbl mit einer Spalte und zwei Datensätzen, das langt:

Code: Alles auswählen

create table tbl (num integer);

Code: Alles auswählen

insert into tbl values (1);
insert into tbl values (2);
Dann der execute block:

Code: Alles auswählen

execute block returns (xCount integer) as 
begin
  in autonomous transaction do 
    execute statement 'create index TBL_X2 on TBL (NUM)';

  -- in autonomous transaction do 
    select round(coalesce(1 / nullif(rdb$statistics, 0), 0), 0)
    from rdb$indices 
    where rdb$index_name = 'TBL_X2'
    into :xCount; 
  
  suspend; 
end;
Da kriegst Du <null> als xcount, weil die Änderung der rdb$indices durch die autonome Transaktion für den execute block nicht sichtbar ist, obwohl sie committet ist.

Dann lösch den gerade erzeugten Index tbl_x2:

Code: Alles auswählen

drop index tbl_x2;
und mach den Gegentest, indem Du im execute block die auskommentierte Zeile "-- in autonomous transaction do" aktivierst. Nur dann liefert der execute block das korrekte Ergebnis, nämlich 2.

Durch den Schalter ReadConsistency = 1 verhalten sich stored routines und execute blocks bei ReadCommitted intern, in ihrem body, wie bei Isolation Level Snapshot! Sie sehen nur commits, die vor ihrem Start committet waren, aber keine mehr seitdem.

Ich bin gerade mit Vlad da dran, weil das ganz grundlegende weitreichende Konsequenzen hat. Das ist mir leider erst vor kurzem aufgefallen, im Zusammenhang mit Firebird 5, vorher hatte ich ReadConsistency auf 0, bei Firebird 4, nach dem Motto, erstmal defensiv alle Schalter auf legacy, und dann nach und nach umstellen. Das ReadCommitted-Sublevel ReadConsistency gibt es schon einige Jahre, und wurde vor Jahren auch ausgiebig diskutiert, anscheinend nicht genug. Der Test oben betrifft Versionen ab Firebird 4. Betroffen sind alle, die PSQL-Code einsetzen, der ReadCommitted-Sichtbarkeit auch innerhalb des PSQL-Codes voraussetzt. Also in stored procedures, stored functions, trigger, execute blocks.

Grüße, Volker
Zuletzt geändert von vr2 am Fr 11. Aug 2023, 03:11, insgesamt 1-mal geändert.
bfuerchau
Beiträge: 490
Registriert: Mo 7. Mai 2018, 18:09
Kontaktdaten:

Vielen Dank für deinen Test.
Zumindest die DDL-Erstellung eines Index funktioniert soweit ja auch in der autonomen Transaction.
Den count werte ich im Endeffekt gar nicht aus.

Autonome Transactions innerhalb einer Transaktion sollten dort zumindest sichtbar werden. Denn ansonsten habe ich noch andere Probleme, die wohl nur mit mehreren Server-Calls gelöst werden können.

Ggf. kannst du ja auch mal testen, ob eine 2. autonome Transaction dann die Sichtbarkeit der 1. garantiert, denn die ist ja auf jeden Fall neuer.
Somit könnte man den 2. Teil in eine weitere autonome Transaction packen und die Änderung wäre eher marginal.
vr2
Beiträge: 219
Registriert: Fr 13. Apr 2018, 00:13

bfuerchau hat geschrieben: Do 10. Aug 2023, 13:25 Vielen Dank für deinen Test.
Zumindest die DDL-Erstellung eines Index funktioniert soweit ja auch in der autonomen Transaction.
Den count werte ich im Endeffekt gar nicht aus.
Gerne, das ist ein wichtiges Thema. Die Indexerzeugung funktioniert weiterhin ganz normal in der ATX, Du siehst es nur nicht mehr im EB. Wenn Du den count nicht auswertest, ist es in dem Fall natürlich egal.
Autonome Transactions innerhalb einer Transaktion sollten dort zumindest sichtbar werden. Denn ansonsten habe ich noch andere Probleme, die wohl nur mit mehreren Server-Calls gelöst werden können.
Deren committete Daten sind bei ReadConsistency nicht mehr sichtbar für die äußere TX, das ist ja genau eins der Probleme. Wie man das jetzt am besten löst, ist gerade Thema meiner Diskussionen mit Vlad. Er hat den Vorschlag gebracht, ein Statement in der Art "refresh my snapshot" einzuführen, das man an den Stellen im PSQL-Code einfügt, wo man seit PSQL-Start committete Daten sehen will/muss. Die Frage ist jetzt, sind das einige wenige Stellen oder tendenziell jedes zweite Statement im body einer stored routine? Es verlagert die Verantwortung für die Sichtbarkeit bei ReadCommitted-ReadConsistency in PSQL zum Anwender. Macht man nix, ist die Sichtbarkeit Snapshot.

Die Motivation für ReadConsistency war der Umstand, dass sowas wie select count(*) from tbl in einer ReadCommitted-TX auch zwischenzeitlich commitete Daten seit seinem Ausführungstart gesehen hat, und das ist falsch, gibt inkonsistente Ergebnisse. Also wurde mit ReadConsistency für top-level-Statements bei ReadCommitted ein Verhalten wie bei Isolation Level Snapshot eingeführt, so dass bspw ein select count(*) from tbl nur die Daten sieht, die vor seinem Start committet waren. Das Dumme ist nur, dass dann ein Aufruf einer stored routine ebenfalls als Ganzes wie im Isolation Level Snapshot läuft, und das ist für ReadCommitted uU zu grob. Jedes Statement innerhalb PSQL müsste sich ebenfalls einen aktuellen Snapshot holen, damit sich ein Stück PSQL bei ReadCommitted-ReadConsistency so verhält wie bisher bei Record_Version / No Record_Version. Das wurde diskutiert, ist aber anscheinend nur sehr schwer umzusetzen, bei unklarem Nutzen. Der Punkt ist jetzt rauszufinden, was bzgl PSQL wirklich nötig ist. Dass da noch was fehlt, ist immerhin schon mal Konsens.
Ggf. kannst du ja auch mal testen, ob eine 2. autonome Transaction dann die Sichtbarkeit der 1. garantiert, denn die ist ja auf jeden Fall neuer.
Somit könnte man den 2. Teil in eine weitere autonome Transaction packen und die Änderung wäre eher marginal.
Ja, genau das zeigt der Test oben ja auch. Wenn man die Abfrage der rdb$indices auch in eine ATX packt, kriegt man das korrekte Ergebnis, weil die ATX natürlich (als separate neu angefangene TX) die committeten Daten von TXen vor ihr sieht. Nur die äußere TX (die des execute blocks) sieht nix - weil durch ReadConsistency nun effektiv Snapshot.

Die Lösung kann natürlich nicht darin bestehen, dass jede Aktion in PSQL in eine ATX verpackt werden muss, um das bisherige Verhalten bei ReadCommitted bei Sub-Level ReadConsistency auch innerhalb von PSQL zu erreichen.

Grüße, Volker
bfuerchau
Beiträge: 490
Registriert: Mo 7. Mai 2018, 18:09
Kontaktdaten:

Sehr schön erklärt.
Was ich nicht verstehe ist, dass ein Count(*) neuere Daten sehen kann.
Denn das betrifft nicht nur einen Count sondern alle Resultsets, die mal ein wenig dauern.
Ins besonders alle Grouped-SQL's stehen vor demselben Problem.

Wenn es wie beim Count auch zu Mehrfachzählungen kommen kann (so habe ich das verstanden), stimmen die grouped Aggregate auch generell nicht. Denn das Leseverfahren ist ja unabhängig vom gewählten Aggregat.
Das wäre der absolute Tod der Firebird!

Und we siehts bei den OLAP-Funktionen aus? Da werden auch jede Menge Aggregate gebildet.
Simmt also ein
"select first 1 count(*) over() from mytable"
genauso wenig wie ein normaler "select count(*) from mytable"?

Bei ETL lade ich 100-tausende Zeilen in ein DWH, grundsätzlich mit ReadCommitted.
Inzwischen nutzen wir auch OLAP-Funktionen in der 3.0.
Wenn obige Probleme bestehen, kann ein Wechsel zu 4.0 nur mit Legacy und zu 5.0 überhaupt nicht erfolgen.

Und ehrlich gesagt verstehe ich das Problem überhaupt nicht:
Nach meinen Verständnis der Satzversionen und der entsprechenden Doku seit 2.5, sieht eine Transaktion mit Record-Version nur die Daten, deren Transaktionsnummer kleiner oder gleich der aktuellen Transaktion ist.
Ich verwende noch z.T. den alten ODBC-Treiber (funktioniert auch mit V4) und halt den neuen Netprovider (9.1.1). Bei diesen gibt es keine Einstellung (Transaction Isolation), die ohne Satzversion arbeitet, zumindest habe ich keine gefunden.

Sollte man nicht eher dieses Problem lösen als einfach automatisch eine andere Transaction Isolation zu wählen, die mehr Probleme als Lösungen bringt?

Ich muss noch mal einen Test in der 3.0 machen, denn wir gehen von der folgenden Prämisse aus:
Während des Ladens einer Tabelle bleiben für alle anderen neuen Connections die vorher gültigen Daten sichtbar. D.h., ein ETL macht innerhalb einer Transaktion einen "delete from table" sowie einen anschließenden Ladevorgang mit 1000den Inserts.
Erst wenn die Transaktion committed wird, sollen diese Daten anderen neuen Transaktionen sichtbar werden. Bis dahin sollen die alten Daten sichtbar sein.
Nun muss ich mal prüfen, ob das so noch passt.
vr2
Beiträge: 219
Registriert: Fr 13. Apr 2018, 00:13

bfuerchau hat geschrieben: Fr 11. Aug 2023, 10:28 Sehr schön erklärt.
Was ich nicht verstehe ist, dass ein Count(*) neuere Daten sehen kann.
Denn das betrifft nicht nur einen Count sondern alle Resultsets, die mal ein wenig dauern.
Danke! Das ging mir genauso. Ich mache seit Ewigkeiten heftig ETL und andere sportliche Geschichten mit Firebird, aber das war mir neu. Ich hab es bisher auch nicht geschafft, den Effekt zu reproduzieren. Die Beschreibung ist hier: https://www.firebirdsql.org/file/commun ... stency.pdf
Wenn obige Probleme bestehen, kann ein Wechsel zu 4.0 nur mit Legacy und zu 5.0 überhaupt nicht erfolgen.
Im Gegenteil, der Effekt betrifft alle Versionen, aber in 4.0 und 5.0 gibt es mit dem ReadConsistency-Schalter in der firebird.conf einen Fix dafür - wenn man ihn überhaupt braucht.
Sollte man nicht eher dieses Problem lösen als einfach automatisch eine andere Transaction Isolation zu wählen, die mehr Probleme als Lösungen bringt?
War auch mein erster Gedanke. Es ist aber ein Grenzfall. ReadCommitted bedeutet, dass eine TX1 die von anderen TXen committeten Daten sehen soll, ausdrücklich auch seit dem Start von TX1, denn sonst wäre es ja Snapshot. Ob die anderen TXen vor oder nach dem Start von TX1 gestartet wurden, ist dabei egal, wichtg ist, dass sie vor dem Ende von TX1 committet haben. Nur ist es zu weit getrieben, wenn diese hohe Sichtbarkeit sogar innerhalb eines lesenden Statements angewendet wird. Mittendrin. Dann ist es vermutlich implementierungsabhängig, welches Ergebnis kommt.
Erst wenn die Transaktion committed wird, sollen diese Daten anderen neuen Transaktionen sichtbar werden. Bis dahin sollen die alten Daten sichtbar sein.
Nun muss ich mal prüfen, ob das so noch passt.
Sie werden natürlich erst sichtbar, wenn sie committet sind. Wenn Du es schaffst, den Effekt zu reproduzieren, schreibs bitte hier rein. Das würde mich sehr interessieren, denn dann kann man beurteilen, unter welchen Umständen der Effekt auftritt. In dem pdf hatten sie ein Beispiel:

connection1/TX1: In einer Schleife werden jeweils 1000 Sätze eingefügt und dann committet
connection2/TX2: In der anderen Schleife werden die gezählt

Beide TXen ReadCommitted, und record_version oder no_record_version, das ist anscheinend egal.

Grüße, Volker
Antworten