Zur Navigation

Normalisierung von Datenbanktabellen [4]

am Beispiel von Datenbanktabellen für ein Wörterbuch

31 Ranma (Gast)


Sind hier mehrere Übersetzungsmöglichkeiten durch ein ";" getrennt? wenn ja, ist die Tabelle an der Stelle wohl noch nicht ausreichend normalisiert

Ja, davon gibt es einige. Dazu noch solche, in denen ; innerhalb von geschweiften Klammern oder ; innerhalb von runden Klammern vorkommen und etwas anderes voneinander trennen.


OK, dann müsste man ggf. manuell nacharbeiten :)

Das meine ich ja. Aber dafür sind es viel zu viele Datensätze. Mit entsprechenden Funktionen des Wörterbuches, das ich für die Datenbank bauen will, sollten sich die Datensätze nach und nach verbessern lassen. Vielleicht gehen noch ein paar Dinge automatisch, wenn nur noch die drei benötigten Tabellen übrig sind.

Linke Spalte: Hinweis, daß es sich bei der Kombination um eine Übersetzung handelt.

Welche anderen Werte sind in dieser Spalte denn noch möglich?

Vorgesehen sind noch Restriktionen (nur für Japanisch), Cross-References (auch für Japanisch), Verknüpfung zwischen traditionellem und reformiertem Chinesisch. Ich überlegte noch, hier auch Aussprachen zu kombinieren, aber die sollen besser in die Tabelle für Zusatzinformationen. In Letzterer sollen Informationen aus solchen Spalten landen, in denen Datenfelder NULL enthalten können.


Nachteil ist beispielweise, dass du dann einen Eintrag in PHPMyAdmin nicht direkt mehr direkt löschen kannst, weil dieser nicht über eine ID ausgewählt werden kann.


Darüber habe ich mich auch schon geärgert. Mit so einer ID hat Vorteile und ohne so eine hat andere Vorteile. Das führt dazu, daß ich (je nach gerade benötigter Operation) ständig PRIMARY KeYs lösche und neu erstellen lasse.

Aber im Moment nervt mich etwas ganz anderes. Englisch <--> Deutsch - Datensätze wurden aus drei Dateien eingelesen und sind in drei Tabellen gelandet. Eine Stored Procedure wertet gerade eine davon aus und fügt die Kombinationen der IDs der Wörter als Übersetzungen in die Tabelle für die Kombinationen ein. Diese Stored Procedure arbeitet jetzt schon seit vierzig Stunden und es gibt keinen Hinweis darauf, wie lange das noch dauern kann.

Zwischendurch habe ich parallel einige Dateien kopiert und als das Betriebssystem damit fertig war, wußte es nicht, daß es damit fertig war, sondern zeigte das als weiterhin laufende Aufgabe an. Also vielleicht hört auch die Stored Procedure nie wieder auf?

Eigentlich möchte ich nichtmal, daß mein Computer unbeaufsichtigt (bei vierzig Stunden natürlich zwischendurch mal der Fall) läuft, weil dessen Vorgänger explodiert ist (wegen eines unterdimensionierten Netzteiles; damit hatte ich nicht gerechnet, aber später erfahren, daß das bei Computern jener Marke {war ein Packard Bell} üblich ist). Mit meinem jetzigem Computer habe ich die Fehler natürlich nicht wiederholt, aber er klingt so als wäre der Lüfter beschädigt. Läuft bisher trotzdem problemlos (außer dem Geräusch), aber unbeaufsichtigt versetzt mich das in Panik. Das führt mich zu ein paar weiteren Fragen.

Kann ich vorher oder währenddessen herausfinden, wie lange eine Stored Procedure laufen wird? Der Versuch, eine weitere Instanz von phpMyAdmin zu öffnen (um zu sehen, ob die Tabelle mit den neuen Einträgen länger wird), führt jedenfalls nur zu einem endlosem Ladevorgang.

Die Tabelle, die gerade ausgewertet wird, enthält etwas über vierhunderttausend Einträge. Ist mein Computer zu langsam? Wahrscheinlich schon. Es wäre gut, wenn ich meine Stored Procedure so ändern könnte, daß immer nur ein Teil der zu verarbeitenden Datensätze verarbeitet würde, so daß ich meinen Computer zwischendurch auch mal abschalten kann. Aber dann würde es insgesamt natürlich nochmal viel länger dauern. Vielleicht sollte ich versuchen, die Stored Procedure bei einem Webhoster laufen zu lassen?

In dem Fall würde ich wohl sinnvollerweise sämtliche noch zu bearbeitenden Tabellen gleich noch hineinschreiben. Der Server eines Webhosters läuft ja kontinuierlich. Dann könnte das über Tage im Hintergrund laufen. Ich befürchte nur eine große CPU-Auslastung, ohne daß dafür gleich ein Grund ersichtlich sein würde. Das dürfte die meisten Hoster stören. Hindert eine Begrenzung der CPU-Auslastung, die manche Hoster androhen, an der Ausführung einer Stored Procedure? Hilft es, wenn die Stored Procedure zwischendurch (hier wäre es natürlich besonders, gut die Auslastung der CPU abfragen zu können) pausiert? Wie bekomme ich das hin?

Wenn die Verarbeitung nicht bald zu Ende ist, dann muß ich das abbrechen. Dann mache ich mich wohl besser zunächst auf die Suche nach einem Hoster....
Ranma

08.12.2015 01:50

32 Jörg

Kann ich vorher oder währenddessen herausfinden, wie lange eine Stored Procedure laufen wird?

Vorher mit einer begrenzten Anzahl von Datensatzen testen, und dann hochrechnen

Der Versuch, eine weitere Instanz von phpMyAdmin zu öffnen (um zu sehen, ob die Tabelle mit den neuen Einträgen länger wird), führt jedenfalls nur zu einem endlosem Ladevorgang.

Unter Linux kannst du mit dem System-Tool nice einem Prozess eine niedrigere Priorität zuweisen, so dass andere Prozesse nicht abgewürgt werden. In PHP kann mit proc_nice() auf diese Funktionalität zugreifen:

proc_nice(19);

Mit einem Wert von 19 sollte dein Script dann anderen Prozessen den Vortritt lassen. Falls aber nicht PHP sondern nur MySQL am Rödeln ist, wird nice hier wohl nicht helfen.

Der Server eines Webhosters läuft ja kontinuierlich.

Ab und muss aber auch ein Server mal rebootet werden :)

08.12.2015 16:43

33 Ranma (Gast)

Wie oft muß ein Server rebootet werden?

Immerhin habe ich jetzt gelernt, daß es schon leichtsinnig ist, falls man einen Server unter Windows laufen läßt. Da stehen die problemlösenden Funktionen nicht zur Verfügung. Aber in diesem Fall habe ich mein „Skript“, falls das hier der richtige Ausdruck ist, in die SQL-Box von phpMyAdmin eingegeben. Dann auf „OK“ geklickt und seitdem hört es nicht mehr auf.

Ich weiß, daß ich das mit xampp restart abbrechen kann. Wahrscheinlich muß ich das. Aber die ganze Arbeit könnte verloren sein...

Danach werde ich wohl nur mit ein paar Beispieldatensätzen weiterarbeiten können. Besser als garnicht....
Ranma

09.12.2015 03:44

34 Jörg

Wie oft muß ein Server rebootet werden?

Wenn nicht Techniken wie kgraft oder kpatch zum Einsatz kommen, zumindest nach jedem Kernel-Update.

09.12.2015 15:48

35 Ranma (Gast)

Also doch nicht regelmäßig.

Ich habe das Skript abgebrochen. Nur ein paar tausend Einträge wurden hinzugefügt. Also habe ich mal dreißig weitere Einträge (dazu einfach eine LIMIT-Klausel hinzugefügt) eintragen lassen. Das dauerte drei Minuten. Es geht also tatsächlich schrecklich langsam, die Datensätze zu normalisieren.

Sollte die Struktur aber nun endlich passen, dann heißt das für mich, daß die Normalisierung am besten auf einem leistungsfähigerem Server fortgesetzt wird, also bei einem Hoster. Das wiederum heißt, daß erst ein Wörterbuchskript fertiggestellt werden muß, das auf die Datenbank zugreift. Eintragen, verbessern und abfragen muß es erlauben, während ein Stored-Procedure-Skript mit der Normalisierung fortfährt. Die Informationen dafür sind alle in weiteren Tabellen derselben Datenbank enthalten. Das bedeutet einen Upload von zweihundertfünfzig Megabyte. Danach fliegen dann die zusätzlichen Tabellen raus.

Übrigens sollte man proc_nice() nach Möglichkeit nicht verwenden. Schon unter der verlinkten Anleitung findet sich der Kommentar, daß das nur die CPU-Last senkt, aber trotzdem nicht weniger Bandbreite verbraucht, so daß man damit einen Vorgang dazu bekommen kann, die zur Verfügung stehende Bandbreite aufzubrauchen, ohne daß dabei sonst etwas geschieht. Zuhause ist das kein Ding, aber für ein gehostetes Programm ist das katastrophal. Außerdem läßt sich die Priorität danach oft nicht mehr zurücksetzen.

Sollte die Struktur wie in #29 angegeben richtig normalisiert sein, abgesehen natürlich von den sowieso noch zur Erledigung vorgesehenen Teilen, dann bleibt hier nur noch das Problem wie doppelte Einträge zu verhindern sind. Beim abarbeiten einer Liste mache ich das über INSERT IGNORE und dann werden keine doppelten Datensätze eingetragen, sofern man dazu einen PRIMARY KEY hat, der keinesfalls mit AUTO INCREMENT versehen sein darf.

Aber im produktiven Betrieb müßte natürlich ein AUTO INCREMENT dabei sein, weil sonst die Wort-IDs nicht erzeugt werden. Aber mit AUTO INCREMENT ist automatisch jeder Datensatz formal von jedem anderem unterschieden, sogar wenn außer dem PRIMARY KEY jedes Datenfeld identisch wäre. Man kann natürlich abfragen, ob das Wort schon enthalten ist, aber schon bei einem danach folgendem INSERT weiß man nicht mehr, ob das Ergebnis der Abfrage überhaupt noch stimmt. Damit sich das ändert, bräuchte es nichtmal zwei parallele Zugriffe. Manchmal sieht man doppelte Einträge in Foren, bei mir zuhause gab es schon doppelte Datenbankeinträge durch eine PHP-Anweisung, die eigentlich nur stur ihre Liste abarbeitete. Hätte theoretisch also garnicht sein dürfen. Trotzdem war es so und ich konnte es nur durch INSERT IGNORE lösen. Wenn die Lösung aber nicht mehr zur Verfügung steht?
Ranma

10.12.2015 01:13

36 Jörg

Ich habe das Skript abgebrochen. Nur ein paar tausend Einträge wurden hinzugefügt. Also habe ich mal dreißig weitere Einträge (dazu einfach eine LIMIT-Klausel hinzugefügt) eintragen lassen. Das dauerte drei Minuten. Es geht also tatsächlich schrecklich langsam, die Datensätze zu normalisieren.

In so einem Fall würde ich erstmal schauen, ob die Queries nicht noch optimiert werden können.

Schon unter der verlinkten Anleitung findet sich der Kommentar, daß das nur die CPU-Last senkt, aber trotzdem nicht weniger Bandbreite verbraucht

Der Kommentar nimmt Bezug auf die "i/o bandwidth", also auf die Festplattenzugriffe. Am besten du testet mal mit top und iotop (letzteres muss ggf. erst installiert werden), was für Prozesse welche Last erzeugen, d.h. was hier überhaupt der Falschenhals ist.

Man kann natürlich abfragen, ob das Wort schon enthalten ist, aber schon bei einem danach folgendem INSERT weiß man nicht mehr, ob das Ergebnis der Abfrage überhaupt noch stimmt.

Wenn ein Wort nur einmal enthalten sein darf, sollte das betreffende Feld UNIQUE sein - ist das der Fall?

10.12.2015 20:01 | geändert: 10.12.2015 20:02

37 Ranma (Gast)

Die Felder sind natürlich nicht UNIQUE. Das wäre für ein Wörterbuch schließlich fatal! Das englische Wort dick bedeutet etwas völlig anderes als das deutsche Wort dick, das französische Wort bar bedeutet etwas völlig anderes als das deutsche Wort bar, das türkische Wort alt bedeutet etwas anderes als das deutsche Wort alt oder das nochmal andere italienische Wort alt. Diese und noch weitere Beispiele zum Beispiel dort:
https://de.wikipedia.org/wiki/Liste_falscher_Freunde

Das berücksichtige ich durch eine zusätzliche Spalte für die Sprache in der Tabelle für die Wörter. Erst die Kombination aus Sprache und Wort darf sich nicht wiederholen. Aus der Kombination der beiden machte ich einen PRIMARY KEY vor dem Einsatz von INSERT IGNORE. Um danach eine ID für jedes Wort zu bekommen, mußte ich den PRIMARY KEY wieder löschen, schließlich darf es nur einen PRIMARY KEY pro Tabelle geben.

Theoretisch müßte also für weitere Einträge in die Tabelle für die Wörter jedesmal die Spalte mit den ID-Nummern entfernt werden, danach die verbleibenden beiden Spalten in der Kombination als PRIMARY KEY definiert werden, dann wird mittels INSERT IGNORE ein weiterer Datensatz eingefügt, der PRIMARY KEY wieder entfernt und schließlich wieder die auf AUTO INCREMENT stehende Spalte für die ID-Nummern hinzugefügt, was wiederum nur funktioniert, wenn die der PRIMARY KEY ist, sonst könnte der einfach bei der Kombination der beiden anderen Spalten bleiben.

Das Skript, das so langsam die Kombinationen einträgt, besteht aus einer REPEAT-UNTIL-Schleife, die Kombinationen aus einer Tabelle für eine einzelne Sprache (also was ich aus einer Datei in die Datenbank geschaufelt habe) in zwei Cursor packt; für jeden der beiden Cursor wird eine ID aus der Tabelle der Wörter geholt und in jeweils eine Variable gefüllt, schließlich werden beide Variablen mittels eines INSERT in die Tabelle für die Kombinationen übertragen. Das ist die einzige Möglichkeit, die ich gefunden habe, mit der man eine Tabelle vom ersten bis zum letzten Datensatz durchgehen und Datensatz für Datensatz eine weitere Aktion veranlassen kann und wenn es nur die ist, ID-Nummern der Datensätze in eine weitere Tabelle einzutragen. Es dürfte das simpelste Skript sein, das sich überhaupt in SQL realisieren läßt.

top habe ich noch nie verwendet. Ich vermute mal, das gebe ich in das Terminal ein und gleichzeitig muß mein SQL-Skript laufen. Dann werde ich es wieder abbrechen müssen. Kann top mehr als der Systemmonitor? Der konnte nämlich nicht anzeigen, ob es an MySQL oder sonst etwas lag, aber ich habe ja nichts außer phpMyAdmin benutzt. Also kann es sowieso nur an SQL liegen.
Ranma

11.12.2015 02:12

38 Ranma (Gast)

Wie frage ich schließlich die normalisierten Datenbanktabellen überhaupt ab? Die Informationen sind ja dann auf drei Tabellen verteilt. Vielleicht so:


SELECT Sprache, Wort, Art, Info FROM Kombis LEFT JOIN Zusatz WHERE id = ((SELECT anderes_Wort FROM Kombis WHERE Kombi = 'trans' AND ein_Wort = (SELECT id FROM Worte WHERE Wort = ? AND Sprache = ?)) OR (SELECT anderes_Wort FROM Kombis WHERE Kombi = 'trans' AND ein_Wort = (SELECT id FROM Worte WHERE Wort = ? AND Sprache = ?)))

Allerdings muß ich dafür die beiden einzugebenden Werte hier je zweimal eintragen und dabei habe ich noch nicht berücksichtigt, daß ich eigentlich keine zwei Spalten gleich benannt habe, um Konflikte zu vermeiden. id in der Tabelle Zusatz heißt darum eigentlich ID. Würde ID AS id als Abhilfe reichen und wo müßte das hin? Mit JOIN habe ich auch noch keine Query gemacht, sitzt das an der richtigen Stelle?

Außerdem fällt mir gerade ein, daß ich den Inhalt der Dateien, aus denen die Tabellen letztendlich generiert wurden, als sicher ansehen konnte. Das heißt, ich habe garnichts wegen der Sicherheit der Tabellen unternommen. Es ist also jedesmal xyz, statt htmlentities(xyz) oder was ich sonst noch hätte machen sollen, eingetragen. Kann das zum Problem werden? (Also die vorhandenen Einträge werden nicht plötzlich böse werden, aber neu dazukommende Einträge werde ich natürlich absichern müssen und dann sind sie völlig unterschiedlich?)
Ranma

11.12.2015 02:51

39 Jörg

Erst die Kombination aus Sprache und Wort darf sich nicht wiederholen.

Dann würde ich auf diese beiden Felder einen kombinierten UNIQUE KEY setzen, in etwa so:

ALTER TABLE woerterbuch ADD UNIQUE KEY wort_index (wort, sprache);

Das Skript, das so langsam die Kombinationen einträgt, besteht aus einer REPEAT-UNTIL-Schleife, die Kombinationen aus einer Tabelle für eine einzelne Sprache (also was ich aus einer Datei in die Datenbank geschaufelt habe) in zwei Cursor packt; für jeden der beiden Cursor wird eine ID aus der Tabelle der Wörter geholt und in jeweils eine Variable gefüllt, schließlich werden beide Variablen mittels eines INSERT in die Tabelle für die Kombinationen übertragen.

Kannst du den Codeabschnitt hier mal posten?

top habe ich noch nie verwendet. Ich vermute mal, das gebe ich in das Terminal ein und gleichzeitig muß mein SQL-Skript laufen.

Ja, ebenso iotop

Kann top mehr als der Systemmonitor?

Meist können die Konsolentools mehr als GUI-Tools, zudem laufen sie auch dort, wo es keine GUI gibt (wie z.B. auf dem Webserver). Desweitern lassen sich die obersten Zeilen bei Bedarf zur weiteren Analyse kopieren (mit der Maus markieren und der mittleren Maustaste woanders einfügen)

Der konnte nämlich nicht anzeigen, ob es an MySQL oder sonst etwas lag, aber ich habe ja nichts außer phpMyAdmin benutzt. Also kann es sowieso nur an SQL liegen.

phpMyAdmin beschäftigt auch noch den PHP-Interpreter und den Webserver. Außerdem geht es hier ja auch um die Ausgabe von iotop, d.h. um deine Befürchtung, dass die Festplattennutzung (i/o bandwidth) kritisch sein könnte

Auf deinen zweiten Beitrag gehe ich später ein...

11.12.2015 14:15 | geändert: 11.12.2015 14:18

40 Jörg

Zu Beitrag 38:

Eine JOIN Abfrage baut nicht auf Subqueries auf. Entscheidend sind die ON Statements, mittels derer zwei Tabellen über einen (Fremd-)Schlüssel verknüpft werden. Beispiel:

SELECT buecher.titel, buecher.isbn, autoren.vorname, autoren.nachname
FROM buecher
INNER JOIN autoren
ON buecher.autor_id = autoren.id
WHERE buecher.jahr = '2014'

buecher.autor_id ist hier der Fremdschlüssel

htmlentities() (oder besser htmlspecialchars()) würde ich immer erst bei der Ausgabe in HTML anwenden, weil dort alle Strings behandelt werden müssen, nicht nur die aus der Datenbank. Zum anderen kann es durchaus passieren, dass man Daten aus der Datenbank auch in Bereichen verwenden möchte, wo ein htmlentities() bzw. htmlspecialchars() wieder rückgängig gemacht werden müsste.

11.12.2015 20:22 | geändert: 11.12.2015 20:36