Datenbank dezentral erweitern [gelöst]

  • Mal eine rein konzeptionelle Frage:


    Ich habe mit AutoIt eine Datenverwaltung geschrieben.

    Die zugehörige, prall mit Inhalten gefüllte SQLite-Datenbank wird später mitgeliefert.


    Bisher arbeitete nur ich selbst mit Programm und Datenbank.

    Zu jedem neuen Eintrag erstellt SQLite bislang einfach eine fortlaufende Datensatz-ID, per Autoincrement.

    Zusätzlich zur automatisch generierten Datensatz-ID habe ich auch ein Feld "Eigene ID". Dort kann z. B. eine Art Bestellnummer eingetragen werden, die natürlich in der Praxis wiederum eindeutig ist und sich niemals wiederholt. Aber sie kann prinzipiell frei vergeben werden.


    Soweit der Ist-Stand (der so nicht unbedingt bleiben muss).


    Problem nun:

    Angenommen, rund um die Welt arbeiten künftig mehrere User mit meinem Programm und besagter Datenbank.

    Jeder User soll natürlich in seiner lokalen Datenbank (ohne Online-Verbindung) neue Datensätze hinzufügen können.

    Jeder spendierfreudige User soll seine Einträge aber auch mit den anderen Usern teilen können.

    - Also ein Export neu angelegter Datensätze, die andere User in ihren eigenen Datenbestand hinzu "mergen" können, ohne dass es dort zu Konflikten mit bereits bestehenden Einträgen kommen darf.


    Idealerweise sollte nach so einer Datensatz-Tausch-Aktion eine jede Datenbank eines jeden einzelnen Users die gleiche Anzahl Datensätze aufweisen (sofern jeder mitmacht).

    Und - das ist der Punkt - alle Datensätze sollten dann überall die gleiche ID haben (so jedenfalls meine hemmungslose Wunschvorstellung).


    Mir fällt als Lösungsansatz nur ein zentraler Server ein, der die Aktivitäten der User koordiniert und von sich aus überall eindeutige Datensatz-IDs vergibt.

    - Das möchte ich aber möglichst nicht! Aus folgenden drei Gründen:

    • Erstens empfinde ich das Verfahren als arg kompliziert (schwacher Grund, ich weiß).
    • Zweitens als nicht hinreichend ausfallsicher. Jeder Server kann schon morgen dicht machen.
    • Drittens mag ich es grundlegend nicht, wenn Software "nach Hause telefoniert".


    Die Frage lautet also, ob es einen irgendwie dezentralen Ansatz für diese Problemstellung gibt?

    Zum Beispiel der Art, dass jeder einzelne User "auf Antrag" einmalig eine (eindeutige) Art Namenskürzel zugeteilt bekommt, welches in die Datensatz-ID mit einfließt?

    Also nix mehr mit simplem Autoincrement in SQL, sondern komplexere IDs.


    Tatsächlich hätte so ein Namenskürzel was. Ich möchte es nämlich (pseudonymisiert) nachvollziehen können, welcher User welchen der untereinander geteilten Datensätze erstellt hat.

    Schon allein deshalb, um Vandalen ausklammern zu können, die den Datenbestand (den ich ja selbst nutze!)mit Murks-Einträgen voll müllen.

    Wirklich sicher vor Vandalen wäre man mit einer derart simplen Methode zwar nicht, aber es geht hier auch nicht um Hochsicherheitstechnik.


    Vorrangiges Ziel ist halt, dass User ihre selbst erstellten Datensätze "spenden" können und dass andere User solche "Spenden" zu ihrem lokalen Datenbestand hinzu "mergen" können, wobei die Datensatz-IDs überall, in jeder einzelnen Datenbank, gleich sein sollen.



    Mein Programm wird Freeware und mehrsprachig, es ist folglich kaum absehbar, wie viele User es weltweit mal geben wird.

    Ein Namenskürzel aus nur zwei Buchstaben/Zeichen könnte daher etwas kurz sein. Aber die Datensatz-ID soll ja auch nicht irre lang werden. Bei mehr als 10.000 Datensätzen ist sie ja jetzt schon fünf Zeichen lang.

    Es würde mich aber auch nerven, mich dauernd darum kümmern zu müssen, "auf Antrag" solche Kürzel an User schicken zu müssen. Zumal ich ja mal 'nen Unfall haben könnte etc. - die User sollen dann nicht im Regen stehen, sondern die Sache soll weiter laufen.


    Ich denke, für die geschilderte Problemstellung müsste es doch schon längst kluge Ansätze geben. Aber ich habe keine gute Idee, wie so ein Ansatz aussehen könnte.

    Wer weiß Rat?

  • Hallo Code-Jack  

    meine naiven Gedanken.


    -Ich kenne keine andere Möglichkeit als einen zentralen Server.

    -Ich würde die Daten in verschiedene Tabellen aufteilen. Lokal hätte jeder Tabellen für die vom Nutzer erstellten Daten und Tabellen für die "synchronisierten" Daten vom gemeinsamen Server. (Im Hintergrund. Mit der Anzeige der Daten hat das ja nichts zu tun).

    -Nach Export und Download der neuen Daten, können dann die User-Tabellen gelehrt werden.

    -Die Namens-ID würde ich von der Datensatz-ID getrennt halten.

    (Wahrscheinlich wäre es am besten, deine Clients würden immer sofort versuchen, alle noch nicht "synchroniesierten" Datensätze mit dem zentralen Server zu tauschen. Wenn er mal nicht erreichbar wäre, würde eben die Liste etwas wachsen. Keine Ahnung, ob das zu deinem Spendengedanken passt).

    -Du könntest die Adresse des zentralen Servers editierbar machen oder Domain Weiterleitung nutzen, um die Möglichkeit zu haben den zentralen Server schnell zu wechseln.


  • Danke, das klingt schon mal nach einem brauchbaren Ansatz.


    Bisher hatte ich dafür ein Feld "Datensatz amtlich - Ja/Nein" vorgesehen, um die "offiziellen" Datensätze von denen zu trennen, die die User selbst erstellt haben.

    Aber ich denke, Dein Vorschlag ist besser: Vom User selbst angelegte Datensätze kriegen nicht bloß so ein "inoffiziell"-Flag verpasst, sondern sie fließen in eine eigene Tabelle ein.

    Dadurch kann ich die offiziellen/inoffiziellen Datensatz-IDs sauber voneinander trennen und weiterhin mit Autoincrement arbeiten.


    Lädt dann ein User seine "Datenspende" auf meinen Server hoch (wird wohl einfach auf mein Forum hinauslaufen, per Dateianhang), dann muss anschließend entweder meine Wenigkeit himself, oder einer meiner Moderatoren, sich darum kümmern, die Plausibilität zu beäugeln und die neuen Datensätze (sofern dem Anschein nach OK) in die offizielle Tabelle einfließen zu lassen, wobei sie dann eine offizielle ID verpasst bekommen.


    Lädt der "Spender" später sporadisch mal wieder die volle Datenbank herunter, dann würde im besten Fall eine Dublettenerkennung seine Tabelle mit selbst erstellten Datensätzen leeren, denn die sind ja nun eh in der offiziellen DB vorhanden.



    Ich spiele ohnehin mit dem Gedanken, Datensätze im Normalfall nicht zu löschen, sondern ein quasi unendliches "Undo/Redo" zu ermöglichen, indem der alte Stand editierter Datensätze, sowie auch gelöschte, lediglich ausgeblendet werden.

    Halt ähnlich wie der Windows-Papierkorb, aus dem man alten Müll wieder hervorkramen kann, bis er explizit geleert wird.

    Bei jedem Editieren eines Datensatzes wird dann eine vollständige Kopie erzeugt, die in diesem Recycling-Müll landet, versehen mit einer Timestamp.


    Wenn ein User Daten "spendet", dann sorgt seine Exportfunktion dafür, dass nur die jeweils aktuelle Version hochgeladen wird.

    Und dass schon einmal exportierte Datensätze dabeiausgeklammert werden.

    - Wobei das auch wieder doof ist, denn schusselige User könnten mehrfach exportieren, editieren, exportieren ... und am Ende doch nur einmal hochladen.

    Aber irgendwie muss ich ja verhindern, dass Heini Schusselmann immer und immer wieder seine Datenspende hochlädt, sich seine Tabelle aber nie leert, weil er niemals die offizielle DB herunter lädt.

    Ich befürchte da einen Fulltime-Job für meine Moderatoren, die die offizielle Datenbank pflegen. :/


    Dummerweise enthält die Datenbank auch nicht nur Text, den man problemlos auf den ersten Blick für plausibel/unplausibel erachten könnte.

    Sondern sie enthält später auch Binärdaten aus Messreihen, mit denen dann ein elektronisches Testgerät gefüttert wird.

    Eben aus diesem Grund will ich ein "unendliches Undo/Redo" ermöglichen,

    Hat sich dann mal jemand seinen funktionstüchtigen Parametersatz zerschossen, entweder per eigenem Edit, oder per Update durch Import von "offiziellen Daten", dann sind die alten Stände der Datensätze noch vorhanden und können im Bedarfsfall reaktiviert werden.


    Vor einigen Jahren habe ich mal an der Entwicklung eines Gerätes mitgearbeitet, zusammen mit einem räumlich getrennten Kollegen. Der hatte dann einen SVN-Server aufgesetzt worüber wir unsere Dateien synchron hielten.

    Etwas unheimlich war mir das aber schon, denn erstens habe ich das Ding nie ganz verstanden, zweitens musste ich dazu auf meinem Rechner einen speziellen Ordner einrichten, der sich dann irgendwie mit dem SVN-Server synchronisierte.

    - Also solche Dinge möchte ich meinen Usern lieber nicht zumuten.

    Ich mag es am liebsten, wenn man auch ganz ohne Online-Zugang alles machen kann und im Bedarfsfall Dateien klassisch altmodisch tauscht, per E-Mail, oder über ein Forum. Nix mit Cloud (die habe ich noch NIE benutzt!) und möglichst nix mit SVN - es sei denn, man überzeugt mich von dessen unverzichtbarem Nutzen.


    Also vorzugsweise klassisch.

    Es soll nur nicht in eine Arbeitsbeschaffungsmaßnahme ausarten, für denjenigen, der die offizielle Datensammlung pflegt.


    Es wird hier aber so sein, dass die Masse der neuen Daten von den Usern kommen wird.

  • Zunächst: Was ist eine ID? Nicht zwangsläufig ein aufsteigendes Nummernfeld. Eine Software, die wir im Haus verwenden, nutzt beispielsweise einen Hash aus UserId, Timestamp und Objektdaten. Da ich gar keine rein numerische ID habe, die sich aufsteigend bildet, kann ich Datensätze beliebig tauschen, ohne Angst vor doppelten IDs haben zu müssen.

  • Yeah, danke Bitnugger!


    Die AutoIt-Hilfe ist dazu ja etwas spärlich, aber hier wurde ich fündig:

    https://msdn.microsoft.com/de-de/library/bb979128.aspx


    Ich hatte schon mit dem Gedanken gespielt, selbst so eine irre lange ID zu erzeugen; wusste gar nicht, dass AutoIt das von sich aus kann.


    Dann würde ich in Zukunft diese ellenlange ID gar nicht mehr anzeigen.

    Beim Autoincrement war es noch naheliegend, die mit höchstens 5 Zeichen angenehm kurze ID in der Maske sichtbar darzustellen. Aber es ist ja viel eleganter, diese lange GUID gar nicht darzustellen, sondern bloß intern zu verwenden.

    Dem User steht ja immer noch seine editierbare "Eigene ID" zur Verfügung.

    Selbst wenn er da mal eine Dublette hinein setzt (und das Program das nicht abfängt), so würde das zumindest programmintern nichts ausmachen.

    - Im Gegenteil: Die Verwendung der GUID "unter der Haube" ermöglicht es dem User dann in für ihn nachvollziehbarer Weise, Kopien weitgehend identischer Datensätze anzulegen. Z. b. mit im Stamm identischer Bestellnummer, wo bloß die letzte Ziffer differiert.


    Im Moment geht es mir um die Verwaltung von Notebook-Mainboards.

    Später kommt noch ein Prüfgerät für Mainboards hinzu, das mit meinem Programm kommunizieren wird (ich repariere beruflich Notebooks und entwickle auch Geräte).

    Da kommt es oft vor, dass ein Mainboard anhand seiner aufgedruckten Mainboard-Bezeichnung nicht eindeutig identifiziert ist.

    Vielmehr gibt es noch unterschiedliche Bestückungsvarianten dieses Mainboards. Und davon wiederum mehrere Revisionsnummern.

    Da können durchaus schon mal 10 leicht unterschiedliche Mainboards existieren, die alle auf die selbe Bezeichnung hören ...


    Trotz identischem Notebook-Modell und identischer Mainboardbezeichnung, hätten die dann logischerweise abweichende elektrische Daten, die mein Testgerät natürlich allesamt im Detail kennen muss.

    Ergo legt man pro MB-Bezeichnung, pro Bestückungsvariante, pro Revisionsnummer, einen eigenen Datensatz an.

    Die können dann alle durchaus eine identische "Bestellnummer" haben, bzw. "Eigene ID", aber die elektrischen Daten weichen halt voneinander ab und da wird mein Testgerät penibel drauf reagieren.


    Es erscheint mir daher spontan sinnvoll, Deinem Vorschlag zu folgen und "unter der Haube" jedem Datensatz so eine GUID zu verpassen.

    - Auch wenn ich im Startposting noch darüber gejammert hatte, dass die ID möglichst nicht zu lang werden soll. Da hatte ich halt noch die "menschenlesbarkeit" im Visier.

    Istja sogar viel besser, die im Gegenteil heftig lang zu machen und auszublenden. Es würde die User eh nur überfordern, wenn jeder Datensatz mit zwei unterschiedlichen Arten von IDs daher käme, von denen eine sich ganz hartnäckig gegen jede Editierung sträubt. ;-)

  • Hmm, interessant, was man so heraus findet.

    Laut SQLite-Doku war mein bisheriges Autoincrement ohnehin gar nicht nötig:

    http://www.sqlitetutorial.net/sqlite-autoincrement/


    Habe es ausprobiert und beim Kreieren der Tabelle das Autoincrement einfach mal weg gelassen.

    Funzt wie eh und je. Jeder neue Eintrag bekommt automatisch eine neue, forlaufende ID, beginnend ab 1.

    SQLite tut tas einfach von sich aus, die Doku rät daher von der Verwendung von Autoincrement ab.


    Gut, habe es also raus genommen.

    Nun kann ich beim Hinzufügen von neuen Datensätzten ins ID-Feld statt "NULL" eine beliebige Zahl einsetzen. Solange die eindeutig ist, funzt das.


    Code
    1. ;Bisher war es so (gekürzte Zeile):
    2. _SQLite_Exec(-1, "INSERT INTO Mainboardverwaltung VALUES(NULL,'16-04-22', ...
    3. ;Neu, jetzt so: Statt NULL eindeutige und selbst vergebene ID (in diesem Beispiel "77"):
    4. _SQLite_Exec(-1, "INSERT INTO Mainboardverwaltung VALUES(" & 77 & ",'16-04-22', ...


    Gut, das war schon der erste Schritt zum Erfolg.

    Nun mit GUID:


    Code
    1. ;Tabelle jetzt so kreieren (gekürzt):
    2. Global $sDBHeader = "CREATE TABLE IF NOT EXISTS [Mainboardverwaltung]" _
    3. & "([ID] TEXT PRIMARY KEY," ...
    4. _SQLite_Exec(-1, "INSERT INTO Mainboardverwaltung VALUES('" & _WinAPI_CreateGUID() & "','16-03-27 16', ...


    Und das klappt tadellos! :-)

    Die Tabelle muss also jetzt zuvor einfach "TEXT" als Primary Key bekommen, statt wie bisher INTEGER und AUTOINCREMENT.

    Denn Integer wäre auch in der 8 Byte Variante (64 Bit) zu kurz, um die GUID aufzunehmen, mit ihren 128 Bit.

    Da _WinAPI_CreateGUID() sowieso einen String liefert, passt das.


    Ich schalte den Thread mal auf "gelöst" und bedanke mich für die guten Anworten!

  • Ja, das ist eine schöne Sache.

    RowIDs sind immer eindeutig aber dennoch veränderlich. Wenn es also nur um Eindeutigkeit geht und nicht um dauerhaft bestehenden Werte - wie das meistens auch der Fall ist - sind sie ausreichend. Das ist jedoch ein Punkt, den man im Blick behalten muss. Manchmal will man das eben doch nicht. (Wäre doof, wenn das bei imdb eine RowID die Film-ID wäre und ein Film gelöscht würde und damit alle nachfolgenden IDs verschoben würden).

  • Irgendwie kann ich Dir nicht folgen, autoiter.

    Wieso würde sich in der IMDB etwas verschieben, wenn dort ein Film gelöscht wird?

    Durch dieses lebenslange GUID-Fingeprinting, eines jeden einzelnen Datensatzes, ist das doch ausgeschlossen!?!


    In meinem eigenen Fall soll - wie ja schon ausgiebig erläutert - jeder Datensatz eine ewig gültige und garantiert eindeutige ID bekommen.

    Da soll sich nie etwas verändern. Und niemals würde es Auswirkungen auf andere Datensätze haben, wenn dazwischen mal einer gelöscht wird.


    Jeder Datensatz erhält bei mir aber auch zusätzlich eine editierbare ID.

    Die ist analog zu einer Bestellnummer zu sehen. Die kann man durchaus ändern, wird es aber nur höchst selten tun.


    Dank der GUID kann ich es nun sogar zulassen, dass mehrere Datensätze die selbe "Bestellnummer" haben, ohne dass Chaos auftritt. Das ist zwar nur selten sinnvoll, aber es kann sinnvoll sein.

    Ich sage nur: "USB-Stick, 8GB, NoName".

    - Da kann man unter der selben Bestellnummer alles verramschen, was man sonstwoher aus China kartonweise eingekauft hat.

    Für die Nachbestellung beim Lieferanten sind es aber eben doch unterschiedliche Sticks (und Datensätze).

    Die GUID regelt das souverän.



    Also ich muss schon sagen: All Eure Antworten hier im Thread waren gut, aber insbesondere der superknappe Hinweis von Bitnugger war echt der Bringer, der mir total die Augen geöffnet hat!


    Inzwischen habe ich mein Programm vollständig auf GUID umgestellt und alles funzt.

    Jetzt kommen noch die Routinen, die die Tauscherei der Userdaten unterstützen, dann kann langsam der Testbetrieb mit einem zweiten Anwender starten.

  • Du solltest deinen Primary Key aber nicht als TEXT definieren, sondern wenn möglich als VARCHAR(n). Zumindest MySQL ist bei einem Index mit VARCHAR wesentlich performanter und deine GUIDs sind ja eh immer gleichlang.


    Habe ich erfolgreich umgesetzt, danke für den Hinweis!

    Einen Geschwindigkeitsunterschied bemerke ich bei 1300 Datensätzen mit 32 Spalten noch nicht. Aber mehr als bloßes Schreiben/Lesen aus/in die Datenbank läuft bei mir eh noch nicht, bezüglich SQL-Kommandos.


    Erstaunlicherweise gibt es bei sqlitetutorial.net kaum Treffer zum Suchstring "VCHAR".

    Die Artikel, die sich dort mit dem Kreieren von Tabellen, mit dem Primary Key und mit den Datentypen befassen, schweigen sich darüber allesamt sogar völlig aus. Und dort hatte ich zuerst geguckt, was so geht.


    Wieder etwas schlauer geworden!