[ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ] [ 8 ] [ 9 ] SQL Server Isolation LevelsDie alles entscheidende Frage ist nun: "Wenn ein Lesevorgang innerhalb einer Transaktion einen "Read" Lock absetzt, wie lange soll dieser Lock bestehen bleiben?" Je länger ein "Read" Lock bestehen bleibt, desto eher ist meine eigene Transaktion "isoliert" von anderen. Als Nachteil erweist sich natürlich einmal mehr die Verfügbarkeit des Systems für andere Benutzer. Genau aus dem Grund ist es möglich, selber zu beeinflussen, welchen "Isolation" Level man bzgl. der Locks wünscht:
Das Setzen des Transaction Isolation Levels erfolgt mit folgender TSQL Anweisung, welche pro Connection via SQL Pass Through gesetzt werden kann: SQL Server Transaktionen aus MTSFür diejenigen, welche in die n-Tier Entwicklung unter MTS einsteigen, ist es wichtig zu verstehen, dass für das Sicherstellen von Transaktionen aus dem MTS heraus der DTS (Distributed Transaction Coordinator) verwendet wird. DTS wird auf den Maschinen, auf welchen der entsprechende SQL Server läuft, als Service betrieben. Zusammen mit der oben gewonnenen Erkenntnis, dass der Isolation Level "Serializable" der Standard für alle Transaktionen aus MTS heraus ist, ist sofort klar, dass Performance Aspekte im MTS Umfeld sehr wohl ein Thema sind, ist doch der Isolation Level "Serializable" derjenige der am "verschwenderischsten" mit der Verfügbarkeit umgeht oder anders gesagt, auf der vorsichtigen Seite operiert, was die eigene Transaktionssicherheit angeht, den anderen Benutzern und Transaktionen aber entsprechend weniger Spielraum lässt. VFP spezifisches C/S WissenSQL Server Transaktionen aus VFP heraus steuernNun haben Sie bereits ein sehr solides Fundament erhalten, um wagemutig Ihre ersten SQL Server basierten Datenbank Transaktionen aus VFP heraus ansteuern zu können. Sie werden sehen, das geht ganz einfach. Bevor wir beginnen, halten wir uns aber nochmals folgendes vor Augen:
Um eine Verbindung zum SQL Server herzustellen, können wir aus VFP heraus folgendermassen vorgehen: Wenn die Verbindung erfolgreich war, dann erhalten wir eine gültige Connection Nummer zurück. Diese müssen wir uns natürlich abspeichern, damit wir bequem damit arbeiten können. Dies erfolgt in obigem Beispiel in der Variablen lncon. Der Paramerer "fibu" ist der Name des ODBC Data Source Names (DSN). Um nun eine Transaction zu beginnen, muss folgende Anweisung eingegeben werden Diese Anweisung stellt sicher, dass die Transaktionsverarbeitung manuell gesteuert werden kann. Erst wenn mit der Anweisung SQLCOMMIT(nConnectionHandle) die Transaktion bestätigt, bzw. mit SQLROLLBACK(nConnectionHandle) verworfen wird, ist die Transaktion beendet. In unserem Beispiel können wir das folgendermassen zeigen: Wenn nun ein anderer Benutzer denselben Datensatz lesen möchte, dann kann er das, entsprechend dem, was wir eingangs dieser Session gelernt haben, NICHT, da unsere Transaktion den betroffenen Datensatz gesperrt hat. Man kann das selber ganz einfach nachvollziehen, indem man eine neue Connection eröffnet (nicht vergessen: alles läuft pro connection!) und versucht, auf diesen Datensatz zuzugreifen. Natürlich ohne Erfolg! Erst wenn die erste Connection die Transaktion beendet, entweder durch SQLCOMMIT oder SQLROLLBACK, ist die Tabelle wieder frei und kann von einer anderen Connection bezogen werden. Quizfrage: Was passiert, wenn ich oben statt einer "update" Anweisung ein "select from" abgesetzt hätte? Wenn Sie diese Frage nicht beantworten können, habe ich meine Aufgabe nicht erfüllt oder Sie waren mit Ihren Gedanken anderswo. Ich hoffe natürlich das zweite. Ganz im Ernst: Der Isolation Level "Read Committed" stellt sicher, dass die "Read" locks (auch "shared" Locks genannt) gleich nach Beendigung des SQL Statements wieder frei gegeben werden. Zudem würden sich zwei "Read" Locks sowieso nicht gegenseitig behindern. Nur die "Write" Locks, (auch "exclusive" Locks genannt) behindern sich gegenseitig. Ich betone hier diese Begriffe ganz bewusst nochmals, insbesondere auch die Begriffe "Shared" Locks und "Exclusive" Locks, da diese sehr häufig verwendet werden und man sehr gut daran tut, sich deren Bedeutung immer wieder vor Augen zu führen. Die alles steuernde ConnectionWir haben nun gelernt, dass die Connection das Mass aller Dinge ist. Dieser Sachverhalt kann gar nicht genug hervorgehoben werden. In der Praxis bedeutet dies, dass man sich über jede Connection, unter welcher man Transaktionen betreiben will, klar werden muss und man das Verwenden von "irgendwelchen" Connection Nummern auf gar keinen Fall dem Zufall überlassen kann. Wenn man nun alle Connctions von Hand durch explizite SQLCONNECT() Anweisungen herstellen würde, könnte man selbst steuern, welche Connections verwendet werden und was damit gemacht wird. In VFX haben wir für diesen Sachverhalt eigens einen Connection Manager geschrieben, der das Erstellen und Verwalten von Connections auf Applikationsebene vereinfacht. Will man nun aber mit den sehr praktischen VFP remote views operieren, so muss man auf folgendes achten: Die Connection Definition im DBCWird eine remote view erstellt, so muss man zunächst eine Connection aus dem DBC auswählen. Doch Achtung: Diese DBC Connection ist nur die Definition, wie auf den SQL Server zugegriffen werden muss und hat mit den effektiven Connections, welche später durch das Öffnen von verschiedenen remote views verwendet werden, nichts zu tun. Die Connection Definition wird im DBC abgespeichert. Die im DBC zu definierende Connection beinhaltet folgende Informationen:
Um zur Laufzeit die Connection den aktuellen Bedingungen beim Kunden am besten anpassen zu können empfiehlt es sich, mit der Option "Connections string" zu arbeiten. Ein ausgewachsener Connection String beinhaltet folgendes: Wobei die einzelnen Optionen im Wesentlichen denjenigen Einstellungen entsprechen, welche auch beim Definieren eines ODBC-DSN Eintrages vorkommen. TIP:Um obigen Connection String zu erhalten, kann man auf "Verify Connection" klicken. WICHTIG: Um beim Einsatz Ihres Programmes beim Kunden keine Probleme zu haben, müssen Sie unbedingt sicherstellen, dass keine umgebungsspezifischen Einstellungen im Connection String vorkommen. Am sichersten ist es, wenn Sie hierzu nur den DSN=DsnName Eintrag, wie in obigem Bild dargestellt, verwenden. Das gibt Ihnen die Möglichkeit, beim Kunden vor Ort die Einstellungen in der DSN Definition, d.h. beim Definieren der ODBC Verbindung, vorzunehmen. Um zur Laufzeit den Connection String zu manipulieren, z.B. weil der Kunde mit einem anderen DSN Eintrag arbeiten muss, oder diesen zur Laufzeit ändern will, bietet VFP folgende Möglichkeit: In VFX bietet sich z.B. die Methode onpostlogin() an, um die gewünschten Einstellungen vorzunehmen. Hier ein Beispiel: PROCEDURE onpostlogin dodefault() *--dsn ggfl. setzen if !empty(m.gs_dsn) dbsetprop("conFibu", "CONNECTION", "ConnectString", "dsn=" + alltrim(m.gs_dsn)) else dbsetprop("conFibu", "CONNECTION", "ConnectString", "dsn=fibu") endif … endproc In obigem Beispiel wird der Connection String entsprechend dem Eintrag in der System Variablem m.gs_dsn gesetzt sowie weitere, applikationsspezifische Prüfungen vorgenommen. Nachdem wir die Connection in unserem DBC richtig eingestellt haben und auch wissen, wie wir diese zur Laufzeit manipulieren können, sind wir bereit für den nächsten Schritt: HINWEIS: Für das Betreiben Ihrer Anwendung ohne DSN Eintrag siehe weiter unten! Connections und Remote ViewsBeim Erstellen einer Remote View muss immer zuerst eine im DBC vorhandene Connection ausgewählt werden.
Wird die Remote View anschliessend erstellt, ist in der View Definition die in obigem Dialog ausgewählte Connection Definition fest abgespeichert. Wird die Remote View erstellt, muss im Menu "Query"
die Option "Advanced Options…" angewählt werden, um folgende Einstellungen im "Advanced Options" Dialog vornehmen zu können:
Die Checkbox "Share Connection" ist mitunter eine der wichtigsten überhaupt. Nur wenn diese angeklickt ist, kann sichergestellt werden, dass eine neu geöffnete Remote View versucht, eine bereits vorhandene Connection einer zuvor durch eine andere Remote View geöffnete Connection, zu nutzen. ACHTUNG: Eine manuell zuvor geöffnete Connection wird nie hierfür in Betracht gezogen! Das ist auch gut so, denn VFP kann ja nicht wissen, was mit dieser "fremden" Connection alles bewerkstelligt wurde oder noch wird. Vielleicht wurde damit ja bereits ein SQL Prepare String an den Server gesendet oder es läuft bereits eine Transaktion darauf. Was VFP aber kann, ist das Verwenden einer Connection, welche VFP selbst für das Erstellen einer anderen Remote View bereits eröffnet hat. Das und nur das bedeutet "Share Connection". Transaktion mit verschiedenen Remote Views und SPTWill ich nun eine Transaktionale Verarbeitung mit verschiedenen vorhandenen, updatable Remote Views, sowie evtl. eigener SQL Pass Through (SPT) Routinen verwenden, dann muss als Voraussetzung folgendes erfüllt sein: Alle Remote Views müssen mit ein und derselben Connection arbeiten. Andernfalls ist es unmöglich, diese in ein und denselben Transaktionsprozess einzubinden. Diese wichtige Voraussetzung ist nur dann erfüllbar, wenn alle beteiligten Remote Views die Option "Share connection", so wie weiter oben beschrieben, angewählt haben. Um mit der weiter oben beschriebenen Methode SQLSETPROP(nConnectionhandle, "TRANSACTIONS", 2) Anweisung eine manuelle Transaktion zu starten, ist es erforderlich, die aktuelle Connection der beteiligten Remote Views zu kennen. VFP bietet uns hierzu folgende Möglichkeit an: Durch obige Anweisung wird vom aktuellen Arbeitsbereich (optional könnte auch noch der Alias übergeben werden) die Connection Nummer ermittelt und zurückgegeben. Ist dies einmal geschehen, habe ich eigentlich Tür und Tor offen, um selbst zu bestimmen wie es weiter gehen soll. Zunächst einmal kann ich die Transaktion starten: Und meine benötigte transaktionale Business Logik durchlaufen um am Schluss wieder mit die Transaktion im Erfolgsfall permanent zu beenden, oder aber mit die Transaktion zurückzufahren, und alle in der Transaktion vollzogenen Änderungen auf der Datenbank wieder rückgängig zu machen. In VFX kann dies z.B. durch das Überschreiben der onsave() Methode in einem Form folgendermassen aussehen:
local laError[7], lViewMessage, lSaved, lValid, lCanSave, oMatEingang
if thisForm.lUseHook and !thisForm.OnEventHook("OnSave",this,thisForm) select (thisform.cWorkAlias)
if !empty(tlFromChild)
lSaved = .f.
if thisform.nFormStatus == ID_EDIT_MODE this.lcanedit = this.lactcanedit lValid = thisForm.Valid() this.SetObjectFocus() this.OnWriteBuffer()
if lCanSAve and lValid
if type("goProgram")=="O"
do case endcase lnconnection = CURSORGETPROP("ConnectHandle") sqlsetprop(lnconnection, "Transactions", 2) ** for remote views do the same in VFP BEGIN TRANSACTION
* Applikationsspezifische Business Logik…
local lcUpdateSQL, lcTempCursor
"Falls Sie durch Anpassen Ihrer Eingabe den Fehler nicht verhindern
können, " + ; "nehmen Sie bitte mit Ihrem Systemadministrator Kontakt auf!",0+16,"Fehler bei Eingang/rvEingang!") else * Busineslogik oMatEingang = createobject("cMatEingang") lntransok = oMatEingang.Eintragen(lnconnection,"rveingang","rveingangpos") oMatEingang.Release() oMatEingang = .NULL. if !lntransok aerror(laerror) =messagebox("Vom Datenbank Server ist folgender Fehler zurückgemeldet worden:" + CHR(13) + CHR(13) + ; laerror[1,3] + CHR(13) + CHR(13) + ; "Falls Sie durch Anpassen Ihrer Eingabe den Fehler nicht verhindern können, " + ; "nehmen Sie bitte mit Ihrem Systemadministrator Kontakt auf!",0+16,"Fehler bei Eingang/busineslogic!") endif endif endif endif if lntransok sqlcommit(lnconnection) ** for remote views do the same in VFP END TRANSACTION
lsaved = .t.
lsaved = .f.
if !lSaved =aerror(laError)
if laError[1] != 0
if lViewMessage and lValid
if tlAllTable
select (thisform.cWorkAlias)
this.RefreshToolBar(.t.)
unlock all In obigem Beispiel wird schön ersichtlich, dass in der gesamten Speicherlogik, sowohl SPT Anweisungen, welche UPDATE oder INSERT Befehle absetzen neben ganz normalen TableUpdate Anweisungen verwendet werden. Der Vorteil eines solchen Ansatzes liegt auf der Hand: Man kann die sehr praktischen Remote Views ohne weiters in eine Transaktionsverarbeitung integrieren, selbst wenn sich erst nach Fertigstellung eines Forms der Bedarf ergeben sollte, dass das ganze Abspeichern transaktional zu erfolgen hat. Aufgepasst mit busy connectionsAuf ein Risiko beim Arbeiten mit ein und derselben Connection bei mehreren Remote Views sei noch hingewiesen. Nämlich das Problem, dass eine Connection auch busy sein kann. Die häufigste Ursache liegt darin, dass nicht alle selektierten Datensätze bezogen wurden, da die Fetchsize nicht auf -1 eingestellt wurde. Es ist sehr wichtig, dass man die Views so parametrisiert, dass es immer Sinn macht, alle selektierten Datensätze sofort zu beziehen, und nicht mit dem Datenbezug zuzuwarten. Nur so kann verhindert werden, dass eine View "busy" bleibt. ConnectionsUm es gleich vorweg zu nehmen. Ein sehr grosses Optimierungspotential liegt darin, seine Client/Server Anwendung bzgl. der Verwendung von Connections zum Datenbankserver, zu optimieren. Ein allzu sorgloses Verwenden von Connections kann sich bitter rächen. Es drängt sich hierbei der Ansatz auf, dass man im Wesentlichen pro Benutzer und Applikation nur eine einzige Connection vom Client auf den Datenbankserver zulässt. Nur dann, wenn es wirklch nicht anders geht, dürfen zusätzliche Connections erstellt werden. Wenn man sorglos mit Remote Views und manuell erstellten Connections für SPT Anweisungen umgeht, ist es mit der Performance früher oder später nicht weit her. Man stelle sich einmal vor, wieviele Connections geöffnet werden, wenn man ein Form lädt, das bereits in der Datenumgebung eine Reihe von Remote Views besitzt und zusätzlich noch eine nicht zu unterschätzende Anzahl Comboboxen oder Listboxen, alle natürlich wieder mit eigenen Remote Views als Row Sources und folglich wieder Connections. Es wird relativ schnell klar, das das sicher nicht die resourcenschonendste Art und Weise der Client/Server Programmierung darstellt. Neben der Performance sind bei der Verwendung von Connections vom Client zum Server natürlich auch lizenzrechtliche Aspekte zu berücksichtigen. [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ] [ 8 ] [ 9 ]
|