Hallo alle,
in https://firebirdsql.org/en/news/new-fea ... d-history/ wurde angesagt, dass Firebird 5 demnächst RC wird.
In dem Zusammenhang eine Bitte an Martin für das Hinzufügen eines wichtigen Hinweises in den Release Docs von Firebird 5:
Das neue Feature SKIP LOCKED funktioniert nur dann wie beschrieben, wenn in der firebird.conf der Parameter ReadConsistency = 1 (default ab Firebrid 4) steht. Hat man den wegen Altanwendungen oder weil man defensiv umstellen wollte, auf ReadConsistency = 0, funktioniert SKIP LOCKED nicht. Das ist laut Adriano technisch bedingt, siehe https://github.com/FirebirdSQL/firebird/pull/7350.
Die default-Änderung zu ReadConsistency = 1 bei Firebird 4 betrifft u.a. ewig lang offene read-only-read committed TXen, siehe https://groups.google.com/g/firebird-su ... msWD6FzHbY. Vor Firebird 4 bzw durch ReadConsistency = 0 wurden diese "pre-committet", siehe https://www.firebirdsql.org/file/commun ... stency.pdf. Das war eher ein Workaround für schlecht geschriebene Anwendungen.
Grüße, Volker
Firebird 5 demnächst RC
Moderator: martin.koeditz
- martin.koeditz
- Beiträge: 478
- Registriert: Sa 31. Mär 2018, 14:35
Hi Volker,
ich werde versuchen, das bei Gelegenheit einzuarbeiten. Danke für den Hinweis.
Gruß
Martin
ich werde versuchen, das bei Gelegenheit einzuarbeiten. Danke für den Hinweis.
Gruß
Martin
Martin Köditz
it & synergy GmbH
it & synergy GmbH
Transaktionen bei DB's scheint eh ein vernachlässigtes Thema zu sein. Das lese, sehe und höre ich immer wieder. Meine erste Begegnung dazu war bereits Ende der 70er des letzten Jahrhunderts.
Das Thema "ewig lang offene read-only-read committed" kann ichdaher ebenso wenig nachvollziehen, da generell gilt:
Nach Abschluss jeder SQL-Aktion gehört ein Commit. Dies gilt eben auch für reine Read-Operationen. Denn wenn diese Art der Transaktion lange offen bleibt, sah sich die FB eben gezwungen, viele Satzversionen aufzuheben, die ja von dieser Transaktion vielleicht noch benötigt werden.
Vielleicht sollte man sich doch noch mal die Wichtigkeit von Transaktionen und deren Abschluss bei der Datenverarbeitung zu Gemüte führen.
Das Thema "ewig lang offene read-only-read committed" kann ichdaher ebenso wenig nachvollziehen, da generell gilt:
Nach Abschluss jeder SQL-Aktion gehört ein Commit. Dies gilt eben auch für reine Read-Operationen. Denn wenn diese Art der Transaktion lange offen bleibt, sah sich die FB eben gezwungen, viele Satzversionen aufzuheben, die ja von dieser Transaktion vielleicht noch benötigt werden.
Vielleicht sollte man sich doch noch mal die Wichtigkeit von Transaktionen und deren Abschluss bei der Datenverarbeitung zu Gemüte führen.
Naja, von den Firebird-Leuten nicht, siehe zb https://ib-aid.com/en/transactions-in-f ... esolution/ und https://firebirdsql.org/en/firebird-201 ... nsactions/. Was dabei aber wenig klar wird, ist, was das für die Anwendung bedeutet und wieviel Arbeit man sich sparen kann, und wieviel besser die Performance ist, wenn man Transaktionen richtig einsetzt. Erschwerend kommt hinzu, dass connectivity-Libs Transaktionen unterschiedlich gut unterstützen und dokumentieren. Wenn dort CommitRetaining beim Close der default ist, sind die Probleme vorprogrammiert - im wahrsten Sinne des Wortes.
Der Schalter ReadConsistency hat übrigens auch Auswirkungen auf autonome TXen, die innerhalb einer ReadCommitted TX ausgeführt werden, die Ergebnisse der ATXen werden dann nämlich nicht mehr von der ReadCommitted TX gesehen, da die ReadCommitted TXen sich bei ReadConsistency ähnlich wie Snapshot-TXen verhalten. Ich kann mich noch nicht entscheiden, ob das ein Bug oder ein Feature ist
Grüße, Volker
Wie in einem anderen Thread bereits geschrieben, verwende ich z.B. die Anweisung im execute Block "begin autonomous transaction" um in dieser einen Index zu erstellen, zu deaktivieren und dann zu committen.
Nach dieser Transaktion kann ich jedoch auf den Index mitz activate zugreifen.
Irgendwo habe ich aber gelesen, dass diese autonomous transaktion innerhalb der aktuellen Transaktion immer sichtbar ist.
Sonst wäre ich ja gar nicht darauf gekommen, dieses Verfahren anzuwenden.
Der Hintergrund hierzu war, dass bei Create-Befehlen (Table, Index, ..) Metatabellen (RDB$) verändert werden, was bei einer parallelen Verbindung einer 2. Anwendung, die gerade das selbe versucht, zu einem concurrent update Abbruch geführt hatte.
Der alter index ist da wohl nicht so kritisch, da nur 1 Zeile geändert wird und der Indexaufbau ggf. nach dem Commit durchgeführt wird.
Das betrifft wohl auch generell geschachtelte Transaktionen (savepoints), die nur der aktuellen Transaktion sichtbar sind.
https://firebirdsql.org/refdocs/langref ... point.html
Nach dieser Transaktion kann ich jedoch auf den Index mitz activate zugreifen.
Irgendwo habe ich aber gelesen, dass diese autonomous transaktion innerhalb der aktuellen Transaktion immer sichtbar ist.
Sonst wäre ich ja gar nicht darauf gekommen, dieses Verfahren anzuwenden.
Der Hintergrund hierzu war, dass bei Create-Befehlen (Table, Index, ..) Metatabellen (RDB$) verändert werden, was bei einer parallelen Verbindung einer 2. Anwendung, die gerade das selbe versucht, zu einem concurrent update Abbruch geführt hatte.
Der alter index ist da wohl nicht so kritisch, da nur 1 Zeile geändert wird und der Indexaufbau ggf. nach dem Commit durchgeführt wird.
Das betrifft wohl auch generell geschachtelte Transaktionen (savepoints), die nur der aktuellen Transaktion sichtbar sind.
https://firebirdsql.org/refdocs/langref ... point.html
Bisher war das auch so, aber mit ReadConsistency = 1 teilweise nicht mehr. Zumindest bei Daten (also inserts innerhalb einer autonomen TX) konnte ich den Effekt beobachten, dass diese Daten für die umgebende TX nicht mehr sichtbar waren. Ob das bei DDL wie create table, create index auch so ist, muss ich noch testen. Denke nicht, denn die Änderung bei ReadConsistency ersetzt hauptsächlich Record_Version/No_Record_Version in der Konfiguration einer Read Committed TX. Das Thema war Datenkonsistenz.
Ja, weil zwei TXen dann auf dem selben Satz einer Systemtabelle schrauben. Das ließe sich durch wait-TXen lösen, oder eine schluckt die Exception.Der Hintergrund hierzu war, dass bei Create-Befehlen (Table, Index, ..) Metatabellen (RDB$) verändert werden, was bei einer parallelen Verbindung einer 2. Anwendung, die gerade das selbe versucht, zu einem concurrent update Abbruch geführt hatte.
Hast Du auch die Möglichkeit, Deine Logik auf Firebird 4 zu testen, mit ReadConsistency = 1? Ich hab den Eindruck, dass man dann einiges anders umsetzen muss, es wäre gut, das mal zusammenzutragen. Denn da das der neue engine-default bei ReadCommitted-TXen ist, und zumindest bereits ein neues Feature in Firebird 5 darauf basiert, muss klar sein, was man dann anders umsetzen muss.
Da die ReadConsistency laut Doku eine ReadOnly-Transaktion betrifft, könnte sich das aufheben, wenn man einen Insert/Update/Delete verwendet hat, da es dann ja nicht mehr ReadOnly ist.
Auch eine DDL-Anweisung wäre dann keine ReadOnly-Transaktion.
Den Test mit FB4 kann ich derzeit noch nicht machen. Mal sehen, wann ich dazu Zeit habe. Wir haben noch Legacy VB6-Anwendungen (die aber auf aktuellen Systemen noch fiunktionieren).
Aber ich stelle dir gerne den SQL für Create Table/Index demnächst mal zur Verfügung.
Auch eine DDL-Anweisung wäre dann keine ReadOnly-Transaktion.
Den Test mit FB4 kann ich derzeit noch nicht machen. Mal sehen, wann ich dazu Zeit habe. Wir haben noch Legacy VB6-Anwendungen (die aber auf aktuellen Systemen noch fiunktionieren).
Aber ich stelle dir gerne den SQL für Create Table/Index demnächst mal zur Verfügung.
So, Tests erfolgreich abgeschlossen, nach dem ich Legacy_Auth für Server und Client aktiviert habe.
Zum Nachvollziehen:
Zentrale Sperr-Tabelle gegen parallele Metadatenänderung
CREATE TABLE ZGLOBALLOCK (
LOCKVALUE INTEGER
);
Datensatz für Sperraktionen:
Insert into ZGLOBALLOCK (lockvalue) values(1);
VB6-Funktion zum Erstellen des CreateBefehls:
Ausführung für Create Table mit Primary Key (kompatibel zu 2.5):
Für einen Index ist es nur unwesentlich komplizierter:
VBA-Funktion für den "create index":
Übrigens: Die Abfrage zum Schluss des rdb$statistics liefert mir die Anzahl Schlüssel für diesen Index, da der Kehrwert der Selektivität genau dieses wiedergibt.
Beim Identity-Index liefert dieser mir schnell und relativ zuverlässig die Anzahl Zeilen der Tabelle und erspart mir den langen Count(*). Dazu muss allerdings durchaus auch mal die Indexstatistik erneuert werden. Klar ist das nicht genau, da ja kurz danach Zeilen entfernt/hinzugefügt werden können. Aber der Count(*) ist ja auch nicht viel genauer, da nach der Ausführung das ja auch schon längst passiert sein kann.
Ob der Lock nun auch tatsächlich wirkt, kann ich nicht beurteilen, ich glaube aber fest daran, da ich sonst viel mehr Schwierigkeiten hätte. Im Multiuser-Universum kann es viel häufiger zu Konflikten kommen als man glaubt. Zumal, wenn man viele temporäre Tabellen erstellen und löschen muss.
Zum Nachvollziehen:
Zentrale Sperr-Tabelle gegen parallele Metadatenänderung
CREATE TABLE ZGLOBALLOCK (
LOCKVALUE INTEGER
);
Datensatz für Sperraktionen:
Insert into ZGLOBALLOCK (lockvalue) values(1);
VB6-Funktion zum Erstellen des CreateBefehls:
Code: Alles auswählen
Public Function GetCreateSQL(ByVal pSQL As String) As String
GetCreateSQL = "execute block as " & _
"declare xCreate varchar(" & Len(pSQL) + 1 & "); " & _
"declare xValue integer; " & _
"begin " & _
"begin in autonomous transaction Do " & _
"xCreate = '" & Replace$(pSQL, "'", "''") & "'; " & _
"for select first 1 a.lockvalue from zgloballock a with lock into :xvalue " & _
"Do execute statement :xcreate; " & _
"when any do begin exception; end " & _
"end " & _
"End"
End Function
Code: Alles auswählen
connection.BeginTransaction
connection.Execute GetCreateSQL("create table ....")
connection.Execute GetCreateSQL("alter table ... add constraint ....")
connection.CommitTransaction
VBA-Funktion für den "create index":
Code: Alles auswählen
Public Function GetCreateIndexSQL(ByVal pSQL As String) As String
Dim xName As String
Dim xPos As Long
Dim xEnd As Long
xPos = InStr(1, pSQL, "index ", vbTextCompare) + 6
xEnd = InStr(xPos, pSQL, " on", vbTextCompare)
xName = Mid$(pSQL, xPos, xEnd - xPos)
GetCreateIndexSQL = "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 "
End Function
Beim Identity-Index liefert dieser mir schnell und relativ zuverlässig die Anzahl Zeilen der Tabelle und erspart mir den langen Count(*). Dazu muss allerdings durchaus auch mal die Indexstatistik erneuert werden. Klar ist das nicht genau, da ja kurz danach Zeilen entfernt/hinzugefügt werden können. Aber der Count(*) ist ja auch nicht viel genauer, da nach der Ausführung das ja auch schon längst passiert sein kann.
Ob der Lock nun auch tatsächlich wirkt, kann ich nicht beurteilen, ich glaube aber fest daran, da ich sonst viel mehr Schwierigkeiten hätte. Im Multiuser-Universum kann es viel häufiger zu Konflikten kommen als man glaubt. Zumal, wenn man viele temporäre Tabellen erstellen und löschen muss.
- martin.koeditz
- Beiträge: 478
- Registriert: Sa 31. Mär 2018, 14:35
Danke für den Test.
Martin Köditz
it & synergy GmbH
it & synergy GmbH
Nein, etwas allgemeiner, ReadConsistency betrifft das Lesen innerhalb einer Read Committed-TX, auch von ReadWrite-Transaktionen. Hier noch zwei Tests.
Vorbereitung:
Code: Alles auswählen
CREATE TABLE TBL (NUM integer);
Code: Alles auswählen
execute block returns (cnt integer)
as
begin
delete from tbl;
insert into tbl values (444);
select count(*) from tbl into :cnt;
suspend; -- cnt = 1
in autonomous transaction do
insert into tbl values (555);
select count(*) from tbl into :cnt; -- cnt = 1 bei ReadConsistency 1 und cnt = 2 bei ReadConsistency 0
suspend;
end;
cnt
1
1
ReadConsistency = 0:
cnt
1
2
In beiden Fällen sind nach commit der originalen TX 2 Sätze in tbl.
========================================================
DDL ausführen:
Code: Alles auswählen
execute block returns (cnt integer)
as
begin
select count(*) from rdb$indices
where rdb$index_name = 'TBL_X1' into :cnt;
suspend;
if (:cnt = 1) then
in autonomous transaction do
execute statement 'drop index tbl_x1';
select count(*) from rdb$indices
where rdb$index_name = 'TBL_X1' into :cnt;
suspend;
in autonomous transaction do
execute statement 'create index tbl_x1 on tbl (num)';
select count(*) from rdb$indices
where rdb$index_name = 'TBL_X1' into :cnt;
suspend;
end;
ReadConsistency = 0: 1 0 1 oder 0 0 1
Das DDL (und auch das DML oben) wird in den autonomen TXen auch bei ReadConsistency = 1 ausgeführt, die originale TX sieht das dann aber nicht, in beiden Fällen. Sie kann es nur sehen, wenn ReadConsistency = 0 ist oder wenn die Abfragen auch in den autonomen TXen gemacht werden.