locked
Limits bei Table Buffering? RRS feed

  • Frage

  • Ich bin grad über einen blöden Fehler gestolpert.

    In meinem Kassenprogramm werden zu jedem Verkaufsabschluss alle Teile in 2 verschiedenen Tabellen gebucht. Ich verwende optimistic Table-Buffering und schließe dann mit TableUpdate(1,.T.,...) ab. Das funktioniert alles auch seit Jahren überall gut.

    Nun hab ich aber einen Kunden, bei dem nicht nur 5 Kassen parallel laufen, sondern ab und zu Einkäufe von mehreren tausend Stück entstehen (von anderen Händlern). Und hier passiert es manchmal, dass eine der Tabellen später nur einen Teil der Verkäufe enthält. Es sieht so aus, als ob irgendwann einfach aufgehört wird, zu buchen. Eine Fehlermeldung gibt es nicht. Der nächste Verkauf ist auch wieder in Ordnung. Die Buchung wird in einer Schleife gemacht, in der Inserts zu den beiden Tabellen direkt hintereinander kommen. Die erste Tabelle ist immer korrekt. Die Unterschiede zwischen den Tabellen sind (a) Tabelle2 (in der unvollständig gespeichert wird) hat größere Datensätze als Tabelle1. (b) Tabelle2 wird im Netzwerk auch von den anderen Kassen benutzt (opportunistic locking ist aber abgeschaltet) und (c) für Tabelle1 wird INSERT INTO (..) VALUES(..) verwendet und für Tabelle2 INSERT INTO .. FROM MEMVAR.

    Jetzt ist eine meiner Ideen, ob vielleicht das Table-Buffering auf irgend ein internes Limit läuft. Ist da was bekannt? Oder hat jemand noch andere Ideen?

    Gruß,

    Winfried


    • Bearbeitet WiWo Montag, 23. September 2013 18:59
    Montag, 23. September 2013 18:56

Alle Antworten

  • Ich würde Dir raten, von INSERT FROM MEMVAR auf INSERT FROM NAME loRecord umzusteigen und vorab natürlich dann statt SCATTER MEMVAR lieber SCATTER NAME loRecord zu nutzen. Hier ist übrigens mal nicht ein fehlendes m. dafür zuständig, daß der Insert leere Daten erzeugt, denn INSERT FROM MEMVAR geht explizit davon aus, auf einem Arbeitsbereich zu stehen, in dem Feldnamen identisch zu Variablennamen sind, INSERT FROM MEMVAR füllt dann Felder mit Variablenwerten und läßt Felder frei, zu denen keine Variable existieren.

    Am Umstand, wie Du den Buffer füllst ist unklar, ob er überlaufen könnte. Ich wüßte nicht daß soetwas geht, ich denke da greift wie überall das Limit 2GB.

    Klar sollte sein, daß selbst INSERTs in den Buffer gehen, wenn eine Tabelle gepuffert wird und Schreiben in die DBF auf jeden Fall ein abschließendes TABLEUPDATE benötigt.

    Tschüß, Olaf.


    Olaf Doschke (Setmics)

    Montag, 23. September 2013 21:40
  • Hallo Olaf,

    den alten SCATTER MEMVAR / INSERT FROM MEMVAR -Code werde ich jetzt auf jeden Fall mal ersetzen; der ist eigentlich noch ein Überbleibsel aus der DOS-Zeit.

    Ich arbeite bei dem Kassenprogramm übrigens mit freien Tabellen; kann das hier negative Wirkung haben?

    TABLEUPDATEs setze ich ab, klar, allerdings prüfe ich bislang den Return-Wert nicht auf Fehler. Der Grund: Eigentlich weiß ich gar nicht, was mein Programm im Fehlerfalle dann machen soll. Ich werde aber jetzt mal eine Version erstellen, die mögliche Fehler dort in ein Log-File schreibt; vielleicht lässt sich daraus ja was ersehen.

    Gruß,

    Winfried


    • Bearbeitet WiWo Dienstag, 24. September 2013 12:09
    Dienstag, 24. September 2013 12:07
  • Moin, moin

    warum verwendest du in dieser Situation auf freien Tabellen Buffering ?

    Direktes Schreiben sollte nicht viel langsamer sein ?

    Grüße

    tom

    Dienstag, 24. September 2013 14:30
  • Hallo Tom,

    mein ursprüngliches Kassenprogramm war eine DOS-Version und bis vor 3 Jahren hab ich aus Kompatibilitätsgründen auch in der Windows-Version immer noch Tabellen im DOS-Format verwendet. Die Funktionalität der Windows-Kasse ist allerdings wesentlich größer und in einem einzigen Verkaufsvorgang können sehr viele Tabellen involviert sein (Umtausch, Kunden-Karten, Gutscheine, PLZ-Statistik, Auswahl-Teile etc.). Mit der Tabellen-Pufferung kann ich fast wie mit Transaktionen arbeiten, aber ohne DBC und den langen Sperrzeiten. Klar könnte man das auch noch mal ganz anders lösen, aber wenn das Geld dafür da wäre, würde ich sowieso auf eine SQL-Server-Basis wechseln.

    Jetzt hab ich erst mal ein Error-Logging in die TABLEUPDATE-Aufrufe eingebaut und werde das baldmöglichst beim Kunden einspielen, damit ich mehr Infos kriege.

    Gruß,

    Winfried

    Dienstag, 24. September 2013 16:13
  • Hallo WiWo,
    helfen kann ich Dir leider auch nicht  aber an freien Tabellen sollte das nicht liegen.
    Ich habe genau wie Du Altlasten und viele freie Tabellen und noch nie Ärger deswegen gehabt.

    Ich vermute auch das SCATTER MEMVAR usw hier nicht die Ursache ist weil es ja eigentlich funktioniert
    und nur manchmal nicht. Das sind immer die schönsten Fehler weil man sie zu Hause nicht in Ruhe debuggen kann.

    Den RETURN- Wert zu prüfen ist vollkommen richtig. Das Du nicht weiß was Du dann machen sollst finde ich fast lustig (obwohl ich kann's mir vorstellen). Zumindest aber eben Dir Infos wegschreiben und ich würde den User informieren das etwas schiefgelaufen ist bei Satz Nr , Artikelnr usw. Dann kann er zumindest seinen Bestand nachprüfen, Dich informieren oder sonst was veranlassen. Rechne aber damit das Dir da auch nichts bringt !

    Ich würde dann zumindest mal versuchen mir die Sätze zu merken ob irgendwas an den Keys oder sonstigen Feldern nicht  stimmt oder doppelt ist oder drückst Du Errormeldungen weg ? (on error tue garnix) oder irgend sowas Doofes halt.

    oder aber Tabelle 2 ist evtl. gesperrt ?

    jm2p

    Horst

    Mittwoch, 25. September 2013 07:23
  • Hallo Horst,

    heute morgen ist es mir erst mal gelungen, an 5 der insgesamt 10 Kassen (2 Filialen) per Fernwartung das geänderte Programm einzuschleusen, was mir Fehler der TABLEUPDATEs in Log-Dateien schreibt. Das musste in der kurzen Zeit zwischen Start des Rechners und des ersten Verkaufs geschehen. Vor allem darf ich da keine Verkäufer nervös machen.

    Speziell bei diesem Kunden hat zum Glück der Fehler keine besonders dramatischen Auswirkungen, da die Läden von deren eigenen Großhandel beliefert werden und die Bestandsführung dort geschieht. Trotzdem war es dem Kunden selber aufgefallen, dass da Zahlen nicht stimmen und mir bereitet es natürlich auch Sorge.

    Als erstes habe ich ein Analyse-Tool geschrieben und über den Datensatz des Kunden laufen lassen; dabei stellte sich heraus, dass diese Fehler in beiden Filialen mehrfach wöchentlich auftreten. Probeweise hab ich damit auch Datensätze anderer Kunden untersucht; dort gibt's die Fehler nicht.

    Testweise hab ich mir mal angeschaut, was passiert, wenn ich die DBF von anderer Stelle aus mit RLOCK("0",alias) gegen INSERts sperre; dann passiert genau das beobachtete Verhalten. Also es wird keine Exception geworfen, aber TABLEUPDATE läuft nicht. Die Datei wird aber nirgends im Programm explizit gesperrt. Ich vermute jetzt, dass es eine implizite Sperrung gibt, wenn eine andere Kasse ein TABLEUPDATE macht. Standardmäßig steht bei mir das REPROCESS auf 2 Sekunden; vielleicht reicht das einfach nicht bei diesem Kunden.

    Die Logs werden hoffentlich genauere Informationen liefern, vielleicht schon bis heute Abend.

    Gruß,

    Winfried

    Mittwoch, 25. September 2013 08:39
  •  Du könntest auf der richtigen Spur sein. Wahrscheinlich überflüssig Dir das zu sagen  aber Du müsstest auf jeden Fall die Hilfe zu tableupdate genau durchlesen. Unter bestimmten Umständen gibt es auch keine Fehlermeldung . Warum eigentlich auch wenn man TRUE oder False als Rückgabewert auswerten kann.
    Und beachte die Fehlerhilfe die Du evtl. durch AERROR() bekommen kannst da ON ERROR nicht durchlaufen wird.

    >>Vor allem darf ich da keine Verkäufer nervös machen.
    Sowas würde vor allem MICH nervös machen wenn ich an deiner Stelle wäre :-)))

    Gutes Gelingen

    Grüsse aus dem WBL
    Horst

    Mittwoch, 25. September 2013 11:51
  • >Unter bestimmten Umständen gibt es auch keine Fehlermeldung .

    Eigentlich nie. Genauso wie SQLExec (außer bei falscher parmetrisierung der Funktion selbst) nie SQL-Fehler wirft, den muß man sich schon im Detail mit AERROR abholen.

    Im einfachsten Fall wirst Du vielleicht das Tableupdate mit dem lForce Parameter auf .T. machen wollen, um Deine Bufferänderungen auf jeden Fall zu speichern. Evtl. besteht das Problem einfach darin, daß an zwei Kassen das gleiche Produkt im Buffer ist? Sowas in der Richtung?

    Eine Inventar-aktualisierung geht immer besser per UPDATE-SQL ohne den Weg über den Client mit einlesen, ändern rückschreiben (auch wenn bei VFP eh alles am client passiert), einem UPDATE inventar Set count=count-1 where productid=x ist es egal, ob es aus Sperrgründen per REPROCESS in der Reihenfolge mit anderen Updates getauscht wird. Ist es an der Reihe, zählt es den aktuellen Count runter. Solche Update-Notwendigkeiten kann man sich allerdings natürlich ersparen, wenn man eh nur eine Tabelle mit Buchungen fortschreibt, dann ist nur der aktuelle Inventarstand immer über eine Summation aller Buchungen zu errechnen, wenn es darum geht.

    Tschüß, Olaf.


    Olaf Doschke (Setmics)

    • Bearbeitet Olaf Doschke Donnerstag, 26. September 2013 10:39
    Donnerstag, 26. September 2013 10:28
  • Die ersten Ergebnisse meines Error-Logs sind da: tatsächlich hat es gestern 2 mal Fehler 109 beim Tableupdate an einer Kasse gegeben. Glücklicherweise wurden aber beide Vorgänge doch richtig gebucht, da (und das hab ich inzwischen durch Versuch auch bestätigen können) die Buchung automatisch mit dem nächsten Verkauf nachgeholt wurde. TABLEUPDATE verwirft also die nicht gespeicherten Records bei einem Fehler nicht, sondern man kann es dann auch nachholen.

    Mein Analysetool war übrigens auch noch fehlerhaft; nach Korrektur hab ich festgestellt, dass echte Fehler beim Kunden nicht wöchentlich sondern pro Kasse etwa 1 bis 2 mal im Jahr aufgetreten sind. Natürlich ist das auch nicht akzeptabel. Aus den gespeicherten Kassenabläufen kann ich erkennen, dass das tatsächlich immer im Zusammenhang mit einem Schließen des Verkaufsbildschirms nach dem Verkauf stattfand (Verkäuferwechsel etc.), also wenn die betroffene Datei geschlossen wurde.

    Hilfreich für mein weiteres Vorgehen wäre jetzt, genaueres über implizites Locking bei VFP zu erfahren. Also "Wann wird was für wie lange gesperrt?"

    Gruß,

    Winfried

    Donnerstag, 26. September 2013 10:46
  • http://msdn.microsoft.com/en-us/library/aa975797%28v=vs.71%29.aspx

    => Tableupdate() : Hängt vom Buffering ab, Buffermodus Table dürfte immer eine Tabellensperre machen, außer Du machst explizit nur ein Update des aktuellen Datensatzes, denn wohl auch im Buffermodus 5 nur eine Datensatzsperre.

    Wenn das wirklich beim Schließen des Formulares passiert, folgendes hilft einfach herauszufinden, ob noch etwas ungespeichert ist:

    If GETNEXTMODIFIED(0,"Alias")=0
       * kein Problem beim Schließen
    Else
       * tableupdate oder -revert muss noch gemacht werden
       * USE macht implizit ein Tableupdate, man müßte mit einer fingierten Situation probieren, was bei Konflikten genau passiert
    Endif

    Abgesehen davon sollte Reprocess das Wiederholen ja automatisieren, es kann also an den 2 Sekunden liegen, auf die Du das limitierst.

    Und ja, die Anleitung zu Tableupdate beschreibt auch, wann und wie der Buffer weiter abgearbeitet wird oder nicht und letztlich bleiben bei Resultat .F. alle ungespeicherten Änderungen im Buffer, dazu ist er ja auch noch da, nicht nur um das Speichern auf den Tableupdate zu verschieben.

    Tschüß, Olaf.


    Olaf Doschke (Setmics)

    • Bearbeitet Olaf Doschke Donnerstag, 26. September 2013 12:09
    Donnerstag, 26. September 2013 12:05
  • Hallo Olaf,

    ich hab inzwischen bei dem Kunden ein paar Änderungen eingespielt:

    - jeder Fehler in einem TABLEUPDATE() wird protokolliert

    - das TABLEUPDATE() wird im Fehlerfall (für jede Tabelle separat) mit etwas zeitlichem Abstand noch 2 mal wiederholt.

    Das Protokoll, das seit einigen Tagen geführt wird, zeigt mir erschreckend viele Fehler an; häufiger auch noch in der ersten Wiederholung des TABLEUPDATE. Die Timeout-Zeiten hab ich auf 2 Sekunden gelassen, was in diesen Fällen ja schon mindestens 4 Sekunden Wartezeit ausmacht. Fehler passieren immer nur bei einer Datei, zu der nur SQL-Inserts gemacht werden und niemals explizites Locking. In der von Dir zitierten Microsoft-Hilfe steht zwar sinngemäß "Use APPEND to append records", aber ich glaube kaum, dass damit wirklich gemeint ist, APPEND gegenüber INSERT vorzuziehen, oder etwa doch?

    Wenn 4 Sekunden Wartezeit schon nicht ausreichen, ist natürlich die Frage, wie viel es denn sein sollte; immerhin stehen dort fast immer einige wartende Kunden vor den Kassen. Ich befürchte einfach, hier stößt generell die Foxpro-Datenbanktechnik an ihre Grenzen.

    Gruß,

    Winfried

    Mittwoch, 2. Oktober 2013 10:16
  • In dem von mir verlinkten Hilfe hast Du erst einmaleine Übersicht über Befehle, die automatische Sperren bewirken und daran interessant ist erst einmal nur der Eintrag zu TableUpdate.

    Ich sehe nicht, daß das Hilfskapitel APPEND favorisiert, da mußt Du schon genauer zitieren, was Du meinst.

    Denkbar ist in Deinem Fall nur neuer Datensätze auch APPEND FROM mit einem gesonderten Cursor als Quelle der neuen Sätze, Ziel muß dann natürlich die ungepufferte DBF sein, sonst zieht das ja nochmal ein TABLEUPDATE() nach sich.

    Das hat aber gegenüber TABLEUPDATE eines Puffers mit nur neuen Sätzen keinen mir erkennbaren Unterschied. Mit der DBF-Datei passiert bem TABLEUPDATEi dasselbe, sofern der Puffer ein Tabellenpuffer mit optimistischer Sperrung ist, kommt die Sperrung erst zum Zeitpunkt des TABLEUPDATE zum Einsatz, genauso wie bei APPEND FROM und wegen Tabellenpufferung kommt eine Header Sperrung zum Einsatz, keine Einzelsperren, auch wie beim APPEND FROM.

    Welchen Buffermodus nimmst Du eigentlich? Wann setzt Du ihn, was setzt Du per CursorSetProp, per Buffermode Eigenschaft und per BuffermodeOverride Eigenschaft? All das wirkt sich auf CursorGetProp("Buffering","Tabelle") aus und letzteres ist auf jeden Fall der Modus, der am Werk ist.

    Prüfe doch mal was das ist, direkt vor dem TABLEUPDATE z.B. oder auch bei Formularstart im Activate, der Buffermode kann sich ja dann nicht ändern, spätestens sobald etwas im Puffer ist, und er bleibt auch nach dem Tableupdate() erhalten. Außerdem kann sich auswirken, das irgendwo ein CursorSetProp("Buffering",X,0) auf Arbeitsbereich 0 gemacht wird, was die Pufferung aller Tabellen oder Cursoren bestimmt, die ab dann geöffnet werden. Also, was sagt Dir CursorGetProp()?

    Tschüß, Olaf.


    Olaf Doschke (Setmics)

    Mittwoch, 2. Oktober 2013 13:28
  • Hallo WiWo,

    was macht denn der Fall ? hast Du inzwischen eine Lösung gefunden ?
    Ich kann mir irgendwie nicht vorstellen das das Unausgereiftes vom Fuchs sein soll.
    Aber da ich in Zukunft verstärkt auf den SQL Server gehen werde interessiert mich das natürlich

    Grüsse aus dem nassen WBL

    Horst


    • Bearbeitet Horst Kühn Mittwoch, 16. Oktober 2013 09:31
    Mittwoch, 16. Oktober 2013 08:23
  • Hallo Horst,

    ich hab jetzt seit gut 2 Wochen da Log-Einträge bei nicht erfolgreichen Tableupdates laufen, mit jeweils sekundengenauer Zeitangabe. Da die Kassenprogramme sowieso Verkaufsabschlüsse (und ein paar andere Dinge) sekundengenau loggen, kann ich ganz gut nachvollziehen, wann was schiefgeht. Fehlerhafte Tableupdates wiederhole ich zudem noch bis zu 2 mal mit größtmöglichem zeitlichen Abstand, die das Gesamthandling noch erlaubt. Spätestens beim 3. Anlauf klappt dann auch das Tableupdate. Betroffen ist immer nur eine Tabelle, die in der Hauptfiliale zudem schon über 1GByte groß ist (was mir zusätzliche Sorge bereitet). Und es ist deutlich, die Fehler passieren wirklich nur dann, wenn parallel von mehreren Stationen grad Tableupdates mit vielen Inserts gemacht werden.

    Eigentlich hab ich das Kassen-Programm schon von Anfang an so ausgelegt, dass ich mit nicht all zu viel Aufwand auch auf SQL-Server umstellen könnte. Aber leider gilt das nicht für das angeschlossene Warenwirtschaftssystem. Und zur Zeit verdiene ich mit dem Produkt nicht mehr genug Geld, um mir eine Umstellung leisten zu können.

    Da ich mich wegen Entwicklungen für WindowsCE-Handheld-Terminals eh in .NET eingearbeitet hab, mache ich Neuentwicklungen, die nichts mehr mit meinen alten Programmen zu tun haben, auch nicht mehr in VFP. Besonders das Entity Framework von .NET hat es mir angetan.

    Gruß,

    Winfried

    Donnerstag, 17. Oktober 2013 14:34