Clanintern Clanintern Clanintern

Forum

Öffentliche Foren
FORUM: Spiele & Computer THEMA: mysql: Aus zwei Abfragen eine machen
AUTOR BEITRAG
marinE

RANG Deckschrubber

#1 - 13.04 12:43

ich möchte XX Einträge aus einer Tabelle verändern, aber nur, wenn bisher noch nix mit dem Wert YY eingetragen wurde.

Momentane Situation: 2 SQL-Abfragen.

code:

$sql = SELECT * FROM table1 WHERE feld2 = 'YY'
$res = mysql_query($sql)

IF(mysql_num_rows($res)== 0) {
  $sql2 = UPDATE table1 SET feld2 = 'YY' LIMIT XX
...


Ich möchte das gerne zusammenführen, weil diese Datei SEHR oft angefragt wird und es daher um Performance geht - PHP ist einfach zu langsam ^^. Mir wäre es lieber, die Abfragelogik komplett an die Datenbank zu übergeben
Ich möchte es (vom Sinn her) so lösen:

code:

UPDATE table1 SET feld2 = 'YY' LIMIT XX IF !(SELECT * FROM table1 WHERE feld2='YY')


Das Problem daran, ich bräuchte ne Art IF-Statement inner SQL-Abfrage.
Gibt es da ne vernünftige Lösung für, sehe ich da den Wald vor lauter Bäumen nicht?

Vielen Dank im Vorraus,
marinE
vaest´ark // patrick *ich bin hier nicht der depp*

RANG Master of Clanintern

#2 - 13.04 15:05

quote:
PHP ist einfach zu langsam
Ja, wenn man nicht programmieren kann ist PHP langsam.
Du lässt dir erstmal schön alle (!) Spalten auf den Stack schieben, nur um dann die Zeilen zu zählen, welche du mit WHERE immerhin schon eingeschränkt hast.
SELECT count(id) AS num FROM tabelle WHERE foo='bar'
wäre da die Abfrage der Wahl, weil die nur einen Ergebnissatz (eine Zeile mit einer Spalte) zurückgibt.
Alternativ könntest du auch
SELECT id FROM tabelle WHERE foo='bar' LIMIT 0, 1
verwenden, weil du ja eh nur höchstens eine Zeile brauchst (sind mehr als 0 Zeilen vorhanden => kein Update).
Welches davon letztendlich schneller ist weiss ich nicht, aber tendenziell würde ich sagen: das letztere.

Das, was du vorhast, geht imho nicht.
marinE

RANG Deckschrubber

#3 - 13.04 15:28

Ooooh verzeihung dass ich aus Schreibfaulheit ein * geschrieben hab.
Wenn es genehm ist werde ich das Nächste mal vorher eine Einführung in mein Projektschema, meine Datenbankstruktur und meine Nomenklaturkriterien geben...
Wie dir sicher aufgefallen ist, würde mein obriges Beispiel eh nicht funktionieren weils mehr oder weniger Pseudocode ist (keine Stringbegrenzer und Semikolons), aber werte ruhig meine Fähigkeiten im Programmieren.

So, soviel dazu. Trotzdem danke für deinen letzten Satz. Mal schaun, ob noch mehr User der Meinung sind, dass es nicht geht, dann leg ich den Gedanken ad acta ^^
horst

RANG Prophet of Clanintern

#4 - 13.04 17:33

Es geht nicht nur um das *. Probier wirklich mal aus, WAS genau vaest da geschrieben hat und miss den Performance Unterschied. Dann wirst du (hoffentlich) merken, dass die einze Abfrage evtl langsamer ist als die andere. Das muss nicht mal was mit PHP zu tun haben Das ganze mit einer geschachtelten If Abfrage zu machen - sofern sowas geht - würde es sicher nicht viel schneller machen, als abzufragen ob es MINDESTENS einen Datensatz gibt (dazu kannst du ja schön das Limit benutzen, was die Abfrage sicher schneller macht als N (zB 1.000.000) auszulesen). Dementsprechend brauchst du den Gedanken nicht ad acta zu legen, sondern kannst ruhig weiter 2 Statements im PHP verwenden. Und fühl dich nicht gleich von vaest angegriffen. Sind durchaus brauchbare Tipps, die er da zum Besten gibt
*al!ve* - Vorbereitung aufs Urlaubssemester

RANG Master of Clanintern

#5 - 13.04 18:23

Ich versteh schon den Grund des Problems nicht. Warum ist denn die Anzahl der zu updatenden Datensätze wichtig? Erst zu zählen wie viele Datensätze ein Update betreffen würde und falls das mehr als keiner sind ein Update durchzuführen dürfte mehr Zeit in Anspruch nehmen, als das Update einfach auszuführen und evtl am Schluss mittels mysql_num_rows() zu erfragen, wie viele Datensätze es getroffen hat.
deluxe *decarboxyliert*

RANG Master of Clanintern

#6 - 14.04 10:04

@Stephan: Er überprüft ja nicht wieviele Datensätze sein Update betrifft, sondern ob er das Update überhaupt durchführen will. Im Sinne von:
Falls ein Datensatz vorhanden ist, der feldA == XX, dann tue nichts, sonst UPDATE in XX Zeilen feldA := XX.

Du kannst IF() in SELECTs benutzen, dass es sie in UPDATE gibt wär mir jetzt nicht geläufig.
quote:
[...]Einführung in mein Projektschema, meine Datenbankstruktur [...]

Könntest du aber gerade mal machen - weil ich kann mir im Moment keine Anwendung für ein solches Problem erdenken. (Zumindest net ad hoc und ich bin auch grad zu faul mich näher damit zu beschäftigen)
Ich vermute jedoch, dass man es ggf. auch anders lösen kann.
El Macho

RANG Deckschrubber

#7 - 14.04 21:14

geht sowas nicht mit mysql?
code:

UPDATE table1 SET feld2 = 'YY' LIMIT XX WHERE NOT EXISTS
    (SELECT * FROM table1 WHERE feld2='YY')
masta // thomas

RANG Prophet of Clanintern

#8 - 14.04 21:35

doch, das geht eigentlich auch in mysql
marinE

RANG Deckschrubber

#9 - 15.04 10:12

Aber um das Beispiel von El Macho durchzuspielen müsste für jeden Eintrag die WHERE-Abfrage gemacht werden, das würde in der Tabelle in der ich arbeite 5.400.000 Abfragen machen, und es greifen gleichzeitig normalerweise ~60 Prozesse mit eben jenem Vorgang auf die Tabelle zu.

Aber ok, ich versuche mal einen Einblick in die Problematik zu geben:

Ich greife auf eine Tabelle zu, wo für diesen Vorgang 5.4 Millionen Tupels eingetragen werden (als Durchschnittswert, der Wert liegt zwischen 150.000 und 20 mil).
Diese Tupels sollen von 16 Clients mit jeweils 4 Prozessen abgearbeitet werden. Damit nicht mehrere Prozesse auf die gleichen Daten zugreifen, blocken die Prozesse eine vorgegebene Anzahl Tupels für sich, in dem sie ein Flag auf ihre ID setzen. Während dieses UPDATEs wird die Tabelle ja geblockt, so dass keine Einträge von mehreren Prozessen angefasst werden.
Nachdem jetzt eine gewisse Anzahl von Einträgen (normalerweise ~400) geblockt wurden, werden sie vom Prozess ausgelesen, verarbeitet und anschließend gelöscht.

So der Normalfall. Aber was ist wenn ein Prozess zwischendurch abschmiert, also in der Tabelle noch Einträge mit seiner ID stehen, die nicht gelöscht wurden?
Falls ein Prozess abschmiert, wird er von nem Masterprozess alle 60sec gekillt und neu gestartet. Und DA befinde ich mich mit meiner Problematik: Dieser neue Prozess soll nicht erneut Tupels blocken, wenn bereits Einträge unter seiner ID eingetragen sind. Ergo ->
1. Sind Einträge mit meiner ID vorhanden?
2. Wenn nein, blocke X Einträge mit meiner ID.

Aber wie schon ein galaktischer Vorredner treffend erwähnte: "Mit Verlaub Mopsgeschwinditschkeit ist grad ein bisserl dämlitsch!".

Die erste Abfrage hat ja nur den Sinn und Zweck, herauszufinden ob bereits Einträge mit dem eigenen Flag geblockt sind. Und dafür dann dieses ganze $str, $res, $obj... und dann die Schleife für wenns geklappt hat etc... das ganze ginge viel einfacher und auch performanter (ja, 60 Abfragen etwa alle 15ms extra sind summa summarum... keine Ahnung aber auf jeden Fall vermeidbare Last. Ganz zu schweigen von dem unnötigen von_sql_an_php_verschieben_dann_verarbeiten_dann_wieder_ans_sql rumgeschiebe)

Alles wäre sehr einfach wenn man eine IF-Abfrage SQL-seitig setzen könnte, die genau das übernimmt: Gibt es in dieser Tabelle bereits ein Feld mit dem Wert X (meine ID), dann veränder nix, ansonsten trag in XX Felder meine ID in den Flag ein.

Ein UPDATE mit einem allgemeinen (nicht auf jeden einzelnen Eintrag berechnenden) WHERE ist das was ich suche, also ein
code:

IF NOT EXISTS (SELECT * FROM table1 WHERE feld2='YY')
THEN UPDATE table1 SET feld2 = 'YY' LIMIT XX
END IF

Leider können IF/ENDIF Konstrukte in mysql keine Rückgabewerte von SQL-Abfragen als Kausalcheck nehmen. Oder liege ich da falsch?
deluxe *decarboxyliert*

RANG Master of Clanintern

#10 - 15.04 10:29

quote:
Falls ein Prozess abschmiert, wird er von nem Masterprozess alle 60sec gekillt und neu gestartet

Der Masterprozess könnte doch dann in der DB auch gleich die Einträge des entsprechendes Prozesses freigeben, bevor er ihn neu startet, oder?
Das würde dir die Problematik erspraren jeden Prozess bei jedem neuen blocken von Einträgen erstmal nen Check auf die Tabelle ausführen zu lassen.
*al!ve* - Vorbereitung aufs Urlaubssemester

RANG Master of Clanintern

#11 - 15.04 12:42

Krieg ich rein interessehalber noch ein paar Informationen, wie ein Prozess angestoßen wird und wie lange er läuft? Hast du pro Client vier Arbeiterprozesse, die mittels set_time_limit beliebig lange arbeiten dürfen und ein bis viele Wächterprozesse, die prüfen, ob die Arbeiterprozesse noch laufen und ggf. mittels fsockopen oder exec neu starten? Oder rufst du grundsätzlich alle paar Sekunden einen neuen Arbeiterprozess auf weil der andere ausläuft? Ein Mischbetrieb wäre auch denkbar, alle paar Sekunden einen neuen Arbeiterprozess starten, der prüft, ob bereits einer läuft und sich dann je nach Situation beendet oder die Arbeit aufnimmt.

Wirklich performant läuft das aber alles nicht.

Aus welchem Grund wird die Anzahl der Datensätze pro Arbeiterprozess derart beschnitten? Liegen die Werte der abzuarbeitenden Datensätze je Arbeiterprozess unterschiedlich, abhängig von der Hardware auf der sie laufen oder nimmt sich jeder Prozess genauso viele Datensätze wie ein anderer?
Oder ist die Einteilung in Blöcke lediglich geschehen, um die Anzahl der SQL-PHP-Kommunikationen zu reduzieren?

Wie kommen die Datensätze in die Tabelle? Kann der eintragende Prozess nicht schon alle Datensätze einem Arbeiterprozess zuordnen? Man könnte pro Abfrage definieren, dass ein abhängiges Feld "Arbeiterprozess" sich automatisch aus "ID mod Anzahl_der_Arbeiterprozesse" errechnet, jeder Arbeiterprozess seine Nummer kennt und diejenigen Datensätze selektiert, die ihn durch diese berechnete Zuordnung betreffen. Dadurch müssten nicht zusätzliche Werte in der Datenbank gespeichert werden weil sich diese Zuordnung unmittelbar ergibt und der eintragende Prozess müsste keine händische Zuordnung erzeugen.
So schon vorgeordnet könnten die Arbeiterprozesse einfach die nächsten x ihnen zugeordneten Datensätze selektieren und bearbeiten.

Wenn diese absolute Zuordnung der Datensätze zu Prozessen aus Leistungsgründen (unterschiedliche Hardware der Arbeiterprozesse) dazu führt, dass einige Prozesse leer laufen, andere dafür nie ans Ende ihrer Warteschlange kommen, würde ich als Wächterprozess auch alle bereits zugeordneten Datensätze für den neu zu startenden Arbeiterprozess freigeben, bevor ich diesen starte. Da das ein wahrscheinlicher Fall ist (fällt eine Hardware für vier Stunden aus, können sich diese Arbeiterprozesse nur an die anderen rantasten, wenn die anderen Hardwaren auch für vier Stunden ausfallen, ...) würde ich das vermutlich so lösen, wie Deluxe das in #10 beschrieben hat.

Was passiert mit bearbeiteten Datensätzen? Verschwinden die aus der Tabelle, werden die als bearbeitet markiert oder durchlaufen die in der selben Tabelle evtl einen erneuten Bearbeitungsschritt, irgend wann?
marinE

RANG Deckschrubber

#12 - 15.04 14:53

@deluxe: Nein, der Masterprozess hat keine entsprechenden Rechte und auch keinen physikalischen Zugriff. Eine Auslagerung der Abfragelogik auf diese Ebene ist daher nicht möglich.
@alive: Die Zahl der Clients ist dynamisch, die Anzahl der für einen Arbeitsvorgang zugelassenen Clients ist unterschiedlich, die Zahl der Prozesse pro Client ist dynamisch und die Blockgröße der zu verarbeitenden Datensätze variiert mit dem errechneten Durchschnitt der bisherigen Arbeitsleistung eines Prozesses. Von daher ist eine Festlegung im Vorhinein nicht möglich.
Mehr möchte ich nicht erzählen, da ich damit zu sehr auf Geschäftsintera eingehen müsste und es auch mit der Problemstellung hier nichts weiter zu tun hat.

Also, noch irgendwelche Vorschläge wie ich eine Art IF-Abfrage meinem SQL-Statement vorlagern könnte? ^^
*al!ve* - Vorbereitung aufs Urlaubssemester

RANG Master of Clanintern

#13 - 15.04 16:20

Man könnte die bedingte Zuteilung dadurch realisieren, dass man in die Liste der freien Datensätze diejenigen, die vom eigenen Prozess schon belegt sind mit einbezieht und nach diesen sortiert.

UPDATE tabelle SET prozess = "meine id" WHERE prozess = "" OR prozess = "meine id" ORDER BY prozess DESC LIMIT "soviel ich halt will"
marinE

RANG Deckschrubber

#14 - 15.04 16:54

Nein geht leider nicht, da nicht klar ist wo sich die Einträge mit meiner ID (wenn es denn welche gibt) befinden. Wenn sich diese Einträge nicht am Anfang befinden, werden ja trotzdem wieder zu viele Einträge geblockt.

--edit--

hm, moment mal. Könnteste Recht haben, durch das Desc werden ja nur die Einträge mit ner 0 (nicht geblockt) und meine angezeigt, meine am Anfang...

*denk*... kann es denn so einfach sein? ... glaub schon ^^

Hey danke! Das ist wohl die Lösung, auf die Weise habe ich garnicht an das Problem gedacht. Dankeschön ^^
*al!ve* - Vorbereitung aufs Urlaubssemester

RANG Master of Clanintern

#15 - 15.04 17:35