Entwickler-Ecke

Datenbanken - Delphi/Interbase -> Transaktionen


stiff - So 01.05.05 18:36
Titel: Delphi/Interbase -> Transaktionen
Hallo,
ich habe folgende Beispiel-Transaktion:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
...
if not m_update.Transaction.InTransaction then
  m_update.Transaction.StartTransaction;

  if not m_update.ExecuteSQL('alter table abnehmer add mitglied char(1)'then goto L_ERROR;
  if not m_update.ExecuteSQL('update abnehmer set mitglied = ''N'''then goto L_ERROR;
  ...
  m_update.Transaction.Commit;

L_ERROR:
  m_Update.Transaction.Rollback;


Das Feld 'mitglied' wird in die Tabelle eingefuegt, aber beim Updaten des Feldes kommt eine Fehlermeldung (conversion error from string 'N')!
Muss man wirklich nach jedem "alter table ..." ein 'commit' machen oder übersehe ich da was?
Die Transaktion sollte normalerweise ja dafür da sein, dass man mehrere Anweisungen entweder ganz oder gar nicht ausführt.

Bitte um Ratschläge

Gruß
stiff

Moderiert von user profile iconChristian S.: Code- durch Delphi-Tags ersetzt.


Lemmy - So 01.05.05 20:08

Hi,

ich würde Änderungen an der Struktur nicht mit DML-Anweisungen mischen. Ich empfehle Dir zudem, auf goto zu verzichten. Der Fehler hat meiner Meinung nach aber nichts mit der Transaktionssteuerung zu tun.... Versuch mal folgendes:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
if not m_update.Transaction.InTransaction then
  m_update.Transaction.StartTransaction;

try
  m_update.ExecuteSQL('alter table abnehmer add mitglied char(1)');
  m_update.ExecuteSQL('update abnehmer set mitglied = '#39'N'#39);
  ...
  m_update.Transaction.Commit;
except
  m_Update.Transaction.Rollback;
end;


Lemmy


stiff - Mo 02.05.05 16:30

Hallo Lemmy,

habe deinen Vorschlag implementiert, es kommt aber trotzdem immer noch dieselbe Fehlermeldung.
Das Vermischen von DDL- und DML-Anweisungen wäre für mich eines der wichtigsten Punkte bei Transaktionen,
da dies bei anderen Datenbanken (z.B. PostgreSQL) ja auch funktioniert.

Ich verwende eine TIBQuery für die SQL-Anweisung. Wenn ich der Query die Parameter manuell zuweise (also z.B. m_IBQuery.Params[0] := 'N'), dann kommt keine Fehlermeldung, jedoch bleibt die erhoffte Wirkung der SQL-Anweisung aus. (im obigen Beispiel wird Feld 'mitglied' nicht gesetzt!)


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
function TUpdate.ExecuteSQL(sql:string): boolean;
  begin
    m_IBQuery.Close;
    m_IBQuery.SQL.Clear;
    m_IBQuery.SQL.Text := sql;
    try
      m_IBQuery.ExecSQL;
      ExecuteSQL:=True
    except
      on E:Exception do
      begin
       MessageDlg(E.Message,mtError,[mbOK],E.HelpContext);
       ExecuteSQL:=False
      end
    end
  end;


Ist TIBQuery die ideale Komponente zum Ausführen von SQL-Statements oder gibt es da Komponenten, welche geeigneter wären?


Stefan.Buchholtz - Mo 02.05.05 16:41

user profile iconstiff hat folgendes geschrieben:
Hallo Lemmy,

habe deinen Vorschlag implementiert, es kommt aber trotzdem immer noch dieselbe Fehlermeldung.
Das Vermischen von DDL- und DML-Anweisungen wäre für mich eines der wichtigsten Punkte bei Transaktionen,
da dies bei anderen Datenbanken (z.B. PostgreSQL) ja auch funktioniert.


DIe meisten Datenbanken, sogar Oracle und soweit ich weiss auch Interbase untestützen keine Transaktionen bei DDL-Anweisungen. Jede DDL-Anweisung wirkt als implizites Commit für vorherige DML-Anweisungen.

Stefan


stiff - Mo 02.05.05 18:20

Dann werde ich in diesem Fall wohl oder übel ohne Transaktionen auskommen müssen ... :(

Danke trotzdem für die Hinweise!

stiff


Lemmy - Di 03.05.05 07:35

Guten Morgen,

ja, es gibt wesentlich bessere Komponenten als TIBTable und TIBQuery. Diese wurden nur zu Umstellungszwecken (von BDE auf IBX) eingeführt und haben eigentlich sonst keinen Wert. Verwende entweder eine TIBDataSet oder eine TIBSQL (die gibt aber keine Werte zurück).


Lemmy


Delete - Di 03.05.05 09:14

user profile iconstiff hat folgendes geschrieben:

Dann werde ich in diesem Fall wohl oder übel ohne Transaktionen auskommen müssen ... :(


Das geht beim Interbase/Firebird wegen der MGA gar nicht. Sogar zum Lesen von Daten musst du eine Transaktion starten!


Amiga-Fan - Di 03.05.05 09:36

Zitat:
Das geht beim Interbase/Firebird wegen der MGA gar nicht. Sogar zum Lesen von Daten musst du eine Transaktion starten!


wie meinst du das? Muss man auch bei Select-Statements eine Transaktion machen? Bei mir hat es auch so funktioniert.


Delete - Di 03.05.05 10:18

user profile iconAmiga-Fan hat folgendes geschrieben:
Zitat:
Das geht beim Interbase/Firebird wegen der MGA gar nicht. Sogar zum Lesen von Daten musst du eine Transaktion starten!


wie meinst du das? Muss man auch bei Select-Statements eine Transaktion machen? Bei mir hat es auch so funktioniert.


Dann wird die Transaktion bei automatisch gestartet worden sein. Bei den IBX - Komponenten gibts die Option "AutoStartTransaction" (oder so ähnlich :gruebel:).


Amiga-Fan - Sa 25.11.06 00:12

warum muß man zum lesen eine Transaktion starten? habe wieder PRobleme damit...


Delete - Sa 25.11.06 14:21

user profile iconAmiga-Fan hat folgendes geschrieben:
warum muß man zum lesen eine Transaktion starten?


Wegen der Multigenerationsarchitektur des Interbase/Firebird muss immer eine Transaktion gestartet werden, egal ob lesen oder etwas schreiben.


Amiga-Fan - Sa 25.11.06 17:06

im Moment habe ich wieder das Problem, das er mir anzeigt das eine Transaktion schon aktiv ist. Das kann aber einfach nicht sein, ich habe absolut jedes starttransaction bereits schon mit commit abgeschlossen. Ich versteh das nicht, ob das vielleicht damit zusammenhängt das ich die tibquery und ibx allgemein mit Firebird 1.5.3 benutze? Wie würde das beim lesen denn aussehen sollen?

Lesen:
starttransaction

open

commitretaining bzw. rollback

----------
Schreiben:

starttransaction

execsql

commit bzw rollback

Muß das so aussehen?


UGrohne - Sa 25.11.06 18:23

Ein CommitRetaining bedeutet, dass zwar committed wird, aber die Transaktion immer noch offen bleibt. Schau mal nach, ob Du bei den IBTransactions als DefaultAction CommitRetaining angegeben hast. Das kann diese Meldung hervorrufen.


Amiga-Fan - Sa 25.11.06 18:51

nein, commitretaining steht nicht drin, sondern commit.

wenn ich fürs lesen ein einfaches commit mache (statt commitretaining) hatte ich das Problem das anschließend keine Daten in der Query mehr drin waren.


Lemmy - So 26.11.06 13:51

Hi,

wenn Delphi sich beschwert, dass schon eine Transaktion gestartet ist, warum überprüfst Du das nicht vorher?


Delphi-Quelltext
1:
2:
3:
if Transaction.InTransaction then
  Transaction.Commit;
Transaction.StartTransaction;


Lemmy


Amiga-Fan - So 26.11.06 16:25

nach jedem starttransaction mache ich bereits ein commit, wenn ich dann wieder ein starttransaction mache ist somit die Transaktion theorethisch bereits abgeschlossen.

das was du gezeigt hast, habe ich bereits versucht. Bei execsql erhielt ich dann eine Fehlermeldung


Lemmy - So 26.11.06 19:51

user profile iconAmiga-Fan hat folgendes geschrieben:
... die Transaktion theorethisch bereits abgeschlossen.


eben... theoretisch, aber nicht praktisch!


user profile iconAmiga-Fan hat folgendes geschrieben:

das was du gezeigt hast, habe ich bereits versucht. Bei execsql erhielt ich dann eine Fehlermeldung


Und....??? OutOfMemory? Äpfel nicht gefunden? Birnen faul? oder soll ich weiterraten? Du kannst auch stopp rufen sobald ich die richtige Fehlermeldung geraten habe.... ;-)


Amiga-Fan - So 26.11.06 20:07

Zitat:
eben... theoretisch, aber nicht praktisch!

wieviele commits muß ich denn machen, um da sicherzugehen? oder würdest du eher sowas wie
while transaction.intransaction do begin
end;
machen? Vor dem starttransaction oder nach dem commit?

im moment erhalte ich immer die meldung "Transaktion ist nicht aktiv", und das immer beim 2. Durchlaufen dieser Routine, beim commit:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
    try
      DBQuery.database.close;
      DBQuery.database.open;

      if not DBQuery.database.Connected then begin
        DBQuery.database.open;
        if not DBQuery.database.Connected then begin
          DBQuery.database.open;
          if not DBQuery.database.Connected then begin
            result:=false;
            exit;
          end;
        end;
      end;

      if assigned(dbtransaction) then
        DBQuery.Transaction:=DBTransaction;
    except
      result:=false;
      exit;
    end;

    if assigned(dbtransaction) then
      DBTransaction.StartTransaction;
    try
      with DBQuery do begin
        close;
        if trim(DBQuery.SQL.text)<>trim(SQLStr) then begin
          sql.clear;
          sql.add(SQLStr);
        end;
        if pos('INSERT',ansiuppercase(SQLStr))>0 then begin
        [..]
        end;
        execsql;
      end;
      if assigned(dbtransaction) then
        DBTransaction.Commit;


hansa - So 26.11.06 20:54

Was ist denn das für eine Funktion ? :shock:

Jetzt mal der Reihe nach :

user profile iconAmiga-Fan hat folgendes geschrieben:
..wieviele commits muß ich denn machen, um da sicherzugehen?


Lediglich eines. Geht das schief, dann liegt sonstwo ein Fehler vor und auch while etc. nützt dann nichts mehr. Ich nehme mal an, du benutzt ein Form, um Daten einzugeben.


Delphi-Quelltext
1:
2:
3:
4:
5:
  if not DM.Transaction.Active then // FormShow
    DM.Transaction.StartTransaction;
...
  if DM.Transaction.Active then // FormClose
    DM.Transaction.Commit;


Mehr ist das nicht. Du kannst das genausogut in einen "Speichern"-Button verfrachten. Dann müßte zusätzlich noch in das OnClick :


Delphi-Quelltext
1:
2:
3:
4:
...
  if DM.Transaction.Active then begin // überflüsssig, sofern FormShow wie oben !
    DM.Transaction.Commit;            // speichern  
    DM.Transaction.StartTransaction;  // für nächste Eingaben Transaktion wieder starten


Amiga-Fan - So 26.11.06 21:17

nein ich benutze ein datamodul, in dem diese Funktion ist, und dieser Funktion übergebe ich den sql-befehl. Der Funktion übergebe ich die Query und die Transaktion (da mein Programm 2 austauschbare Datenbanken verwaltet).

wie man sieht, mache ich in dieser Funktion ein starttransaction und commit. Wenn ich übrigens if assigned(dbtransaction) then weglasse, bleibt das problem vorhanden...

naja ich weiß nicht, woran es liegt. Imho ist dieser code richtig...


hansa - So 26.11.06 21:37

user profile iconAmiga-Fan hat folgendes geschrieben:
...Der Funktion übergebe ich die Query und die Transaktion (da mein Programm 2 austauschbare Datenbanken verwaltet).


Inwiefern austauschbar ? Wann steht fest, welche DB gebraucht wird ? Muss die während des Programmlaufes gewechselt werden oder wie ? "Austauschen" heißt für mich, dass zu einer Zeit nur eine DB gebraucht wird. Du musst schon etwas mehr sagen. Ich vermute nämlich stark, dass sich die Problematik viel einfacher lösen läßt.


Amiga-Fan - So 26.11.06 21:53

"muß" gar nicht. Die aktive DB wird über einen Menüpunkt umgeschaltet. Man kann aber auch nur eine DB festlegen.

wenn ich die DB wechsle, lege ich über variablen die aktuelle db, die queries und die transaktion fest. Nachher greife ich auf diese Variablen zu und übergebe sie dieser prozedur. Grundsätzlich ist das nicht schwer, ich kann aber natürlich nicht den ganzen quellcode posten...

Bitte beachten, diese funktion im datamodul rufe ich mehrere male mit denselben parametern hintereinander auf (bis auf sql-befehl natürlich), und die Fehlermeldung tritt erst beim 2. aufruf auf...


hansa - Mo 27.11.06 00:56

user profile iconAmiga-Fan hat folgendes geschrieben:
...wenn ich die DB wechsle, lege ich über variablen die aktuelle db, die queries und die transaktion fest. Nachher greife ich auf diese Variablen zu und übergebe sie dieser prozedur....


So ungefähr war es zu erwarten. :mrgreen:

Ein Denkfehler produziert wohl einige Folgefehler. Es ist überhaupt kein Problem eine Datenbank zu benutzen und irgendwann eine andere. Ich habe hier bestimmt 10 verschiedene DBs. Test-DB, welche mit Originaldaten usw. Dabei wird gar nichts übergeben, außer dem ConnectionString zur DB. In der Praxis sieht es so aus, dass der DB-Name in einer INI im Programm-Verzeichnis liegt und die wird notfalls eben manuell geändert.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
Ini := TIniFile.Create (ExtractFilePath (ParamStr (0)) + 'DB.INI');
    DatenBankName := Ini.ReadString('Datenbank','Name',ExtractFilePath (ParamStr (0))+'db\XXX.fdb')
  DM.DataBase.Close;
  DM.DataBase.DatabaseName := DatenBankName;
  DM.DataBase.Open;
  DM.Transaction.Active := true;
  Ini.free;


Sollte Bedarf bestehen, die DB auch während des Programmlaufes zu wechseln, dann wäre der Code, der die INI betrifft auch leicht durch eine ComboBox etc. zu ersetzen. Es geht lediglich um : Database.Close -> genauen Datenbanknamen angeben -> Database.Open. Alles andere bleibt gleich. Würdest Du bei 10 DBs allen eine extra TDataBase, TTransaction usw. spendieren ? :shock:


Amiga-Fan - Mo 27.11.06 01:03

das programm ist sowieso auf 2 datenbanken festgelegt, das ist schon ok. Wenn es noch mehr wären, hätte ich wohl auch eine andere Lösung ausgesucht, aber so... außerdem will ich über eine funktion eine db in die andere kopieren können, dazu brauche ich zwei gleichzeitig gültige db-verbindungen.

die db-verbindungen schreibe ich auch in eine ini-datei, aber natürlich im anwendungsdaten-verzeichnis.

wie auch immer, es läuft jetzt wieder. Ich hatte beim dynamischen Generieren von Queries nicht die Transaktion zugewiesen, danach mußte ich noch in meiner Schreibfunktion ein db.close und anschließend db.open durchführen, und dann gings wieder.


hansa - Mo 27.11.06 01:16

user profile iconAmiga-Fan hat folgendes geschrieben:
...danach mußte ich noch in meiner Schreibfunktion ein db.close und anschließend db.open durchführen, und dann gings wieder.


Überlege trotzdem, was ich geschrieben habe. Oder eben auch nicht. "Operation gelungen, Patient tot" das sollte keine Dauerlösung werden. :mrgreen:


Lemmy - Mo 27.11.06 08:01

Guten Morgen,

user profile iconAmiga-Fan hat folgendes geschrieben:

wie auch immer, es läuft jetzt wieder. Ich hatte beim dynamischen Generieren von Queries nicht die Transaktion zugewiesen, danach mußte ich noch in meiner Schreibfunktion ein db.close und anschließend db.open durchführen, und dann gings wieder.


genau das habe ich gemeint: Der Fehler lag nicht an der Transaktion sondern wo anders und hat bis an diese Stelle einfach durchgeschlagen! Gut wenn es jetzt läuft...

Grüße
Lemmy


Amiga-Fan - Di 12.06.07 20:44

Ich weiß jetzt, woran es lag. Zum einen war AutoCommit aktiviert...
Zum anderen startete ich immer eine neue Transaktion, ohne zu achten ob bereits eine aktiv war.