Wochenend-Fred: mysqli Datenbank zwischen zwei Abfragen schließen?

Sorry, noch etwas grün hinter den Ohren...

Wie ich das richtig verstanden habe, sollte eine mysqli Datenbank Verbindung nach Zugriff immer geschlossen werden.

…wenn ich aber jetzt im PHP Skript einen Zahlenwert aus der Datenbank lese, diesen Wert IM PHP Skript inkrementiere und den alten Zahlenwert der Datenbank dann überschreibe - kann ich das machen, ohne zwischendurch die Datenbank zu schließen (mysqli_close($verbindung);) ?

Sind ja zwar ZWEI Abfragen, SELECT und UPDATE, aber OHNE dass der EndbenutzerIn dazwischen funken könnte!

Oder sollte ich die Datenbank zwischendurch trotzdem schließen und für die UPDATE Anfrage neu aufbauen? (Falls beim Aulesen oder Update ein Fehler auftritt (falscher Wert, etc.) wird die Datenbank sowieso auch sofort geschlossen ).

Wie gesagt, grün hinter den Ohren, mit leichtem Kupferstich

Danke, Wochenend-Fred

  1. Lieber Wochenend-Fred,

    Wie ich das richtig verstanden habe, sollte eine mysqli Datenbank Verbindung nach Zugriff immer geschlossen werden.

    das ist so nicht richtig. Es muss „nach dem letzten Zugriff“ lauten.

    Liebe Grüße

    Felix Riesterer

  2. Hallo Fred,

    das ist tatsächlich nicht ganz eindeutig. Aber nur in Fragestellungen zu Durchsatz bei stark belasteten Webservern.

    Ein Trainer hat mir in einer Fortbildung mal gesagt, dass DB Connections wie heiße Kartoffeln zu behandeln sind. Schnappen, verwenden und schnell wieder fallen lassen. Aber wie zu jeder Kartoffel gehört auch zu dieser Aussage eine kräftige Prise Salz.

    Das Kartoffelprinzip gilt nämlich nur, wenn geschlossene Connections gar nicht wirklich geschlossen werden, sondern im Hintergrund bereitgehalten und wiederverwendet werden. Und dieser Pool aus bereitgehaltenen Connections muss für mehr als einen PHP Prozess verfügbar sein, denn ein einzelner PHP Prozess führt zu einem Zeitpunkt immer nur ein Script aus. Sowas geht, dafür gibt's unter Windows und auch Linux den ODBC-Treiber mit Connection Pooling. Hab ich so gelesen. Aber noch nie selbst gemacht.

    Andere Systeme wie ASP.NET machen das ähnlich, da läuft ein asp.net Prozess der die Connections im Pool hält und er macht fleißig Threads auf, um Requests zu verarbeiten. Die können dann auf den Pool zugreifen.

    In diesem Szenario kann das Kartoffelprinzip dazu führen, dass man mit einem Pool aus 100 Connections mehr Requests parallel verarbeiten kann. Wenn jeder Request aus irgendeinem Grund 5 Connections öffnet, dann ist nach dem 20. laufenden Request Schluss. Die übrigen laufen auf einen Timeout oder warten. Wenn aber jeder Request seine Connections immer nur kurz aufmacht, dann können sich die DB Zugriffe ineinander verzahnen und mehr Requests erlauben. Wobei man für 20 parallel laufende Requests schon eine Menge Nüsse knacken muss, um genug Kerne zu haben.

    Der normale FastCGI PHP Prozess hat aber pro Request genau ein PHP laufen, und das behält seine Connections für sich und teilt sie mit niemanden. In diesem Szenario kannst Du deine Connections zu Beginn aufmachen, damit alle Zugriffe durchführen und... sie dann vergessen. Denn PHP schließt sie beim Programmende sowieso.

    Explizit Schließen musst Du eine Connection nur dann, wenn zwischen ihrer letzten Verwendung und dem Script-Ende eine Menge Zeit vergeht (z.B. mehr als eine halbe Sekunde). Andernfalls ist das komplett Wurscht.

    Es gibt auch Fälle, wo man eine Hauptconnection hat und nur für eine einzige Operation eine Zweitconnection braucht. Diese Zweitconnection kann man nach Gebrauch wieder explizit schließen.

    Rolf

    --
    sumpsi - posui - obstruxi
    1. Ein Trainer hat mir in einer Fortbildung mal gesagt, dass DB Connections wie heiße Kartoffeln zu behandeln sind. Schnappen, verwenden und schnell wieder fallen lassen.

      Könnte es sein, dass der „Verbindungen“ zu Datenbanken meinte, welche das exklusive Öffnen einer oder mehrerer Datei(en) erfordern? Also Sqlite, DBase und dergleichen - wo es zwar eine Datenbank aber keinen Datenbankserver gibt?

      1. Hallo Raketenwilli,

        Nein, es ging um Datenbankserver und Runtimeumgebungen mit Connection Pools, in denen viele Requests laufen (konkret SQL Server und .net).

        Rolf

        --
        sumpsi - posui - obstruxi
        1. es ging um Datenbankserver und Runtimeumgebungen mit Connection Pools, in denen viele Requests laufen (konkret SQL Server und .net).

          Hm. Nagut.

          Es ist ein Unterschied, ob Du jetzt viele verschiedene Anwendungen und Anwender hast, die jeweils individuell zugreifen oder ob da ein PHP via Webserver quasi als „Konzentrator“, meinetwegen „Proxy“, wirkt und die Connects verwaltet.

          1. Hallo Raketenwilli,

            eben. Deswegen meinte ich ja auch, dass man das fallweise betrachten muss.

            In Umgebungen wie SQLite (oder dBase), die bei einem Connect (oder USE) erstmal Dateien öffnen müssen, wäre das Kartoffelprinzip sicherlich ineffizient.

            Rolf

            --
            sumpsi - posui - obstruxi
            1. Hallo Rolf,

              In Umgebungen wie SQLite (oder dBase), die bei einem Connect (oder USE) erstmal Dateien öffnen müssen, wäre das Kartoffelprinzip sicherlich ineffizient.

              oder gerade andersrum: Es könnte gerade da effizient sein, weil dann ein anderer Client, ein anderer Prozess sofort wieder auf die Datenbank zugreifen kann.
              Kommt eben sehr auf den konkreten Fall an.

              Einen schönen Tag noch
               Martin

              --
              Es liegt allein an uns, ob wir aus den vielen Steinen, die wir einander in den Weg legen, Mauern oder Brücken bauen. (Ernst Ferstl)
              1. Hallo Martin,

                das Risiko sehe ich nicht, weil sowohl dBase als auch SQLite mit Filesharing umgehen können. Zumindest lokal…

                Sofern es denn grundsätzlich funktioniert, ich habe mir mal sagen lassen, dass man eine SQLite DB nicht auf ein Fileshare im Netz legen soll weil das Locking dann nicht korrekt funktioniert. Keine Ahnung ob das noch aktuell ist.

                Rolf

                --
                sumpsi - posui - obstruxi
                1. Sofern es denn grundsätzlich funktioniert,

                  Ja. Immer auch an die Zukunft und das Wachstum denken.

                  ich habe mir mal sagen lassen, dass man eine SQLite DB nicht auf ein Fileshare im Netz legen soll weil das Locking dann nicht korrekt funktioniert.

                  sqlite (wie auch dessen PHP/PDO-Lib) legt Dateien in dem Verzeichnis an, in welchen die Datenbank-Datei liegt. Wenn das ein Netzshare ist, dann kann man nur beten weil zwischen Nachschauen, ob die Datei schon da ist, und Anlegen derselben einersolchen bzw. dem Schreiben der Daten in die eigentliche Datei halt Zeit vergeht.

                  Generell halte ich sqlite genau dann für eine Lösung, wenn genau eine Anwendung in genau einer Instanz zugreift. Bei dBase war das auch nicht anders...

  3. Oder sollte ich die Datenbank zwischendurch trotzdem schließen und für die UPDATE Anfrage neu aufbauen?

    Beware. Regelmäßig ist das Gegenteil der Fall.

    Du wirst nicht Deine Datenbank abfragen, die Verbidnung schließen und ein paar Mikrosekunden später mit dem ganzen Geraffel

    1. DNS-Auflösung (optional)
    2. TCP-Handshake
    3. SSL-Verschlüsselung aushandeln
    4. Authentifizierung

    wieder anfangen. Das dauert nämlich länger und kostet regelmäßig mehr Ressourcen als die Verbindung offen zu halten.

    Hint: 1. und 2. entfallen bei Verbindung zu 'localhost' (nicht: 127.0.0.1) auf unixoiden Systemen → da wird automatisch der konfigurierte Socket (siehe Konfiguration in /etc/mysql) genutzt. Der ist schneller!

    Hint: Unter Windows ist es aber schlauer, 127.0.0.1 anzugeben, weil seit Windows 7 der localhost nicht mehr in c:\windows\system32\drivers\etc\hosts steht. Es dauert dann eine Weile bis sich ein Nameserver erbarmt. Microsoft halt…

    Statt dessen kennt mysqli::connect sogar einen Trick:

    Prepending host by p: opens a persistent connection. mysqli_change_user() is automatically called on connections opened from the connection pool

    Dem Hostname ein p voranstellen: Öffnet eine dauerhafte Verbindung. mysqli_change_user() wird automatisch bei Verbindungen aufgerufen, die aus dem Verbindungspool geöffnet werden.

    So richtig Sinn macht der Verbindungspool aber (wohl) nur, wenn php selbst als Server (php -S …), FPM-CGI oder als Modul des Apache und nicht als pures cgi läuft:

    Das sieht dann auf meinem Server (verkürzt) so aus:

    ~$ ps -elF | grep php | grep -v grep
    5 S www-data  4709 ... php-fpm: pool www
    5 S www-data 12741 ... php-fpm: pool www
    5 S www-data 18590 ... php-fpm: pool www
    4 S root     26280 ... php-fpm: master process (/etc/php/.../php-fpm.conf)
    

    Der Masterprozess kann hier also alle möglichen Pools offen halten.

    Aber daraus, dass es ihn (den Verbindungspool) gibt und er Vorteile bietet (schnelle Wiederverwendung ohne die Schritte 1-3 der obigen Liste) kann man entnehmen, dass das Beenden der Verbindung weit weniger notwendig erscheint als mache Trainer wohl recht nassforsch in fortgeschrittenen Seminaren behaupten.

    1. Hallo Raketenwilli,

      genau das wusste ich nicht - wie FPM da agiert. Aber bist Du sicher, dass der Master-Prozess die Connections poolt? Ich habe das gestern zu ergoogeln versucht, aber keine Beschreibung gefunden.

      Bei mod_php kann ich es mir noch am ehesten Vorstellen. Sofern das Ding auf Durchsatz ausgelegt ist und die Requests in Threads parallel verarbeitet.

      Den einzige Hinweis auf Connection Pooling fand ich im Zusammenhang mit ODBC.

      Bei PHP -S dürfte das Pooling auch nicht viel bringen, weil der Kamerad ein Highlander ist und die ankommenden Requests sequenziell enthaupten dürfte.

      Das mit localhost unter Windoof in der hosts-Datei muss ich, wenn ich heute mal Zeit für meine Windose finde und mich ins Büro verbinden kann, überprüfen. Zumindest bei mir privat dürfte es anders sein, weil ich dort nämlich den Namen ˋphpwebˋ auf 127.0.0.2 gelegt habe, worauf dann ein separates IIS-Web mit PHP FastCGI gebunden ist. Der normale localhost ist nur für statische Spielereien.

      Rolf

      --
      sumpsi - posui - obstruxi
      1. Hallo Raketenwilli,

        genau das wusste ich nicht - wie FPM da agiert. Aber bist Du sicher, dass der Master-Prozess die Connections poolt? Ich habe das gestern zu ergoogeln versucht, aber keine Beschreibung gefunden.

        Naja. Einer muss es ja tun. Und die Kindprozesse können es definitiv nicht. Zumindest hält der Masterprozess mit der ID 26280 (neben den eigentlich arbeitenden Kindern) auch die entsprechenden Libarys geladen:

        ~$ sudo lsof | grep php | grep mysql „sagt“ (verkürzt):

        php-fpm8. 26280 root  mem … …/mysqli.so
        php-fpm8. 26280 root  mem … …/mysqlnd.so
        

        (Für die die Kindprozesse gilt das auch, diese laden aber teilweise mehr Erweiterungen.)

        Für Nicht-Linuxer: Dateien mit der Endung '.so' sind, grob gesagt, das was unter Windows DLLs sind: dynamisch ladbare Erweiterungen.

        Und falls sich jemand wundert, wieso denn bitte der Vaterprozess eine höhere PID hat als die Kinder (Liste in meinem obigen Beitrag):

        Raspbian GNU/Linux 11 (bullseye) auf 4 Prozessoren ( ARMv7 Processor rev 3 (v7l) ) läuft seit 127 Tagen, 4 Stunden und 53 Minuten.

        ProzessIDs sind endlich (min=0, max=64535) und werden also wiederverwendet (sonst bekämen vor allem Server schnell ein Problem). Da sind frei gewordene ProzessIDs schon ein paar Mal erneut benutzt worden... z.B. nach einem Update und Restart des Apache oder PHP, was in den letzten 4 Monaten sicher schon ein paar Mal vorgekommen ist. Und dann erscheint die Reihenfolge eher als „zufällig“ statt „aufsteigend“.

        Das mit localhost unter Windoof in der hosts-Datei muss ich, wenn ich heute mal Zeit für meine Windose finde und mich ins Büro verbinden kann, überprüfen.

        Ja. Einige Kommentare (im Web) verkünden auch, dass es alternativ Probleme gäbe, wenn unter Windows IPv6 konfiguriert sei und der localhost auch auf ::1 verweise, der MySQL-Server dort aber nicht lausche… Die dort auch empfohlene Deaktivierung des Eintrags in c:\windows\system32\drivers\etc\hosts würde ich (wegen unbekannter aber erwartbarer Nebenwirkungen) nicht empfehlen, sondern dann eben für den Verbindungsaufbau die „127.Klo.1“ angeben wollen.

        1. ~$ sudo lsof | grep php | grep mysql „sagt“ (verkürzt):

          php-fpm8. 26280 root  mem … …/mysqli.so
          php-fpm8. 26280 root  mem … …/mysqlnd.so
          

          Ach so. php-fpm wird vom systemd gestartet. (sudo pstree -cH 26280 würde das vorliegend zeigen) Deshalb ist da für den Apache auch ein Proxy konfiguriert.

          <FilesMatch ".+\.ph(?:ar|p|ps|tml)$">
               SetHandler "proxy:unix:/run/php/php8.2-fpm.sock|fcgi://localhost"
          </FilesMatch>
          
  4. Das tolle an PHP ist, dass du dir darüber (vor allem in kleinen Anwendungen) keine Gedanken machen musst. Du öffnest einfach eine Connection und benutzt sie. Am Ende des Scriptes kommen die "Heinzelmänner" und erledigen den Rest.

    T-Rex erzählt eine Geschichte:
    Vor Äonen von Jahren, als die Inflation nur in Büchern stand und die Ukraine noch ein unbedeutendes Getreide-Land war und nicht der Nabel des Universums, dass langsam sogar für Star Wars Todessterne relevant wird, gab es einen kleinen Dino der eine Ausbildung machte. Diese Ausbildung wurde in der kleinen nicht bekannten Programmiersprache bzw. Programmierumfeld "Prodat" gemacht. Dieses ach so tolle System hatte im Hintergrund Datenbanken. Anders als bei MySQL gab es keine Datenbank -> Tabelle Struktur sondern eben nur "Datenbanken". Es fehlte also eine Gliederungsschicht.
    Das total "Tolle" an dieser f***ing Umgebung war, dass man nur zwei Datenbanken gleichzeitig offen haben konnte. Es gab zwei Datenbankbereiche die man nutzen konnte. Ein Join gab es nicht.
    Und so musste man z.b. einen Kunden via Namen laden. Im zweiten Datenbank Bereich öffnete man die Rechnung des Kunden via id. Man speicherte sich die Rechnungsnummer in eine Programm-variable. Dann schloss man entweder die Kundendatenbank oder die Rechnungsdatenbank (je nachdem was man nicht mehr brauchte) und öffnete die Rechnungs-Positionen. Jetzt suchte man die Position 7 um dort die Menge zu ändern.
    Allein dieses kleine Beispiel war soooo immens kompliziert.

    Als ich auf php umschwenkte, war die connection zur mysql Datenbank ein absoluter Segen. Einfach aufmachen und benutzten. Habe ich anfangs nicht verstanden, da ich dachte, dass auch bei php sowas wie Datenbankbereiche existiert. Aber nein, es ist total einfach.

    Irgendwann hast du soviel Last auf deiner Webseite dass du dir um die Details (welche du hier abfragst) kümmern müsstest. Zu diesem Zeitpunkt kannst du aber auch nach einem Käufer für dein Projekt suchen, der dir mehrere Millionen dafür gibt. Denn dann wird es so groß sein, dass genau das passiert.

    Gruß
    ... und wenn sie nicht gestorben sind, sind die Datenbankverbindungen noch offen...
    T-Rex

    1. Hallo T-Rex,

      "Prodat"

      Ein leichter Müffel von dBase macht sich gerade breit… Da hab ich in den 80/90er Jahren des letzten Jahrhunders Spaß mit gehabt. Da gab's "nur" Tabellen, die man mit USE aufmachen muss. Immerhin waren da mehr als 2 offene Tabellen möglich, solange man die FILES Angabe in der CONFIG.SYS hoch genug drehte. Und man musste sich merken, unter welcher Use-Nummer welche Tabelle offen war.

      Offenbar war das damals State Of The Art Argh.

      SQL😍

      Rolf

      --
      sumpsi - posui - obstruxi
      1. Uff ... dBase sagt mir was. Eventuell war das sogar die Grundlage. Ja genau USE so war es. Am Ende wusste mein Ausbilder selbst nicht, dass man die Anzahl der Tabellen uses hochdrehen hätte können. Würde mich nicht wundern 😂.

        Gruß
        Ex Azubi
        T-Rex