Hochpräzise Zeit: Arbeiten mit Sekundenbruchteilen in MySQL und PHP


Als ich dachte, dass ich bei der Arbeit mit der Zeit in Datenbanken fast immer die sekundengenaue Zeit verwende, nur weil ich daran gewöhnt bin und dies die in der Dokumentation und in einer Vielzahl von Beispielen beschriebene Option ist. Jetzt reicht diese Genauigkeit jedoch bei weitem nicht für alle Aufgaben aus. Moderne Systeme sind komplex - sie können aus vielen Teilen bestehen, Millionen von Benutzern interagieren mit ihnen - und in vielen Fällen ist es bequemer, eine höhere Genauigkeit zu verwenden, deren Unterstützung seit langem besteht.


In diesem Artikel werde ich über Möglichkeiten sprechen, Zeit mit Bruchteilen einer Sekunde in MySQL und PHP zu nutzen. Es wurde als Tutorial konzipiert, daher ist das Material für eine breite Palette von Lesern konzipiert und wiederholt an einigen Stellen die Dokumentation. Der Hauptwert sollte sein, dass ich in einem Text alles gesammelt habe, was Sie wissen müssen, um mit dieser Zeit in MySQL, PHP und dem Yii-Framework zu arbeiten, und auch Beschreibungen nicht offensichtlicher Probleme hinzugefügt habe, auf die Sie möglicherweise stoßen.


Ich werde den Begriff "hochpräzise Zeit" verwenden. In der MySQL-Dokumentation sehen Sie den Begriff "Sekundenbruchteil", aber seine wörtliche Übersetzung klingt seltsam, aber ich habe keine andere etablierte Übersetzung gefunden.


Wann sollte eine hochpräzise Zeit verwendet werden?


Für den Anfang zeige ich einen Screenshot des Posteingangs meines Posteingangs, der die Idee gut veranschaulicht:


Zwei Briefe von einem Absender


Briefe sind die Reaktion derselben Person auf ein Ereignis. Ein Mann drückte versehentlich den falschen Knopf, erkannte dies schnell und korrigierte sich. Infolgedessen haben wir ungefähr zur gleichen Zeit zwei Briefe erhalten, die wichtig sind, um richtig zu sortieren. Wenn die Sendezeit gleich ist, besteht die Möglichkeit, dass die Briefe in der falschen Reihenfolge angezeigt werden und der Empfänger in Verlegenheit gebracht wird, da er dann das falsche Ergebnis erhält, für das er zählt.


Ich bin auf folgende Situationen gestoßen, in denen eine Zeit mit hoher Genauigkeit relevant wäre:


  1. Sie möchten die Zeit zwischen einigen Vorgängen messen. Hier ist alles sehr einfach: Je höher die Genauigkeit der Zeitstempel an den Grenzen des Intervalls ist, desto höher ist die Genauigkeit des Ergebnisses. Wenn Sie ganze Sekunden verwenden, können Sie 1 Sekunde lang einen Fehler machen (wenn Sie an die Grenzen von Sekunden fallen). Wenn Sie sechs Dezimalstellen verwenden, ist der Fehler um sechs Größenordnungen niedriger.
  2. Sie haben eine Sammlung, in der es wahrscheinlich mehrere Objekte mit derselben Erstellungszeit gibt. Ein Beispiel ist ein jedem vertrauter Chat, bei dem die Kontaktliste nach dem Zeitpunkt der letzten Nachricht sortiert ist. Wenn eine seitenweise Navigation angezeigt wird, besteht sogar die Gefahr, dass Kontakte an den Seitenrändern verloren gehen. Dieses Problem kann ohne Zeit mit hoher Genauigkeit gelöst werden, da nach einem Feldpaar (Zeit + eindeutige Kennung eines Objekts) sortiert und paginiert wird. Diese Lösung hat jedoch ihre Nachteile (zumindest die Komplexität von SQL-Abfragen, aber nicht nur das). Durch Erhöhen der Zeitgenauigkeit wird die Wahrscheinlichkeit von Problemen verringert und das System nicht kompliziert.
  3. Sie müssen einen Änderungsverlauf eines Objekts führen. Dies ist besonders wichtig in der Servicewelt, in der Änderungen parallel und an völlig unterschiedlichen Orten vorgenommen werden können. Als Beispiel kann ich mit Fotos unserer Benutzer arbeiten, wobei viele verschiedene Vorgänge parallel ausgeführt werden können (der Benutzer kann das Foto privat machen oder löschen, es kann in einem von mehreren Systemen moderiert, zur Verwendung als Foto im Chat zugeschnitten usw.). )

Es muss beachtet werden, dass man den erhaltenen Werten nicht zu 100% vertrauen kann und die tatsächliche Genauigkeit der erhaltenen Werte weniger als sechs Dezimalstellen betragen kann. Dies liegt an der Tatsache, dass wir einen ungenauen Zeitwert erhalten können (insbesondere wenn wir in einem verteilten System arbeiten, das aus vielen Servern besteht), die Zeit sich unerwartet ändern kann (z. B. beim Synchronisieren über NTP oder beim Ändern der Uhr) usw. Ich werde nicht auf all diese Probleme eingehen, aber ich werde ein paar Artikel geben, in denen Sie mehr darüber lesen können:



Arbeiten Sie mit hochpräziser Zeit in MySQL


MySQL unterstützt drei Arten von Spalten, in denen Zeit gespeichert werden kann: TIME , DATETIME und TIMESTAMP . Anfangs konnten sie nur Werte speichern, die ein Vielfaches einer Sekunde waren (z. B. 2019-08-14 19:20:21). In der im Dezember 2011 veröffentlichten Version 5.6.4 wurde es möglich, mit dem Bruchteil einer Sekunde zu arbeiten. Dazu müssen Sie beim Erstellen der Spalte die Anzahl der Dezimalstellen angeben, die im Bruchteil des Zeitstempels gespeichert werden müssen. Die maximale Anzahl unterstützter Zeichen beträgt sechs, sodass Sie die Zeit auf die Mikrosekunde genau speichern können. Wenn Sie versuchen, mehr Zeichen zu verwenden, wird eine Fehlermeldung angezeigt.


Ein Beispiel:


 Test> CREATE TABLE `ChatContactsList` ( `chat_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY, `title` varchar(255) NOT NULL, `last_message_send_time` timestamp(2) NULL DEFAULT NULL ) ENGINE=InnoDB; Query OK, 0 rows affected (0.02 sec) Test> ALTER TABLE `ChatContactsList` MODIFY last_message_send_time TIMESTAMP(9) NOT NULL; ERROR 1426 (42000): Too-big precision 9 specified for 'last_message_send_time'. Maximum is 6. Test> ALTER TABLE `ChatContactsList` MODIFY last_message_send_time TIMESTAMP(3) NOT NULL; Query OK, 0 rows affected (0.09 sec) Records: 0 Duplicates: 0 Warnings: 0 Test> INSERT INTO ChatContactsList (title, last_message_send_time) VALUES ('Chat #1', NOW()); Query OK, 1 row affected (0.03 sec) Test> SELECT * FROM ChatContactsList; +---------+---------+-------------------------+ | chat_id | title | last_message_send_time | +---------+---------+-------------------------+ | 1 | Chat #1 | 2019-09-22 22:23:15.000 | +---------+---------+-------------------------+ 1 row in set (0.00 sec) 

In diesem Beispiel hat der Zeitstempel des eingefügten Datensatzes einen Nullbruch. Dies geschah, weil der Eingabewert auf die nächste Sekunde genau angezeigt wurde. Um das Problem zu lösen, muss die Genauigkeit des Eingabewerts mit dem Wert in der Datenbank übereinstimmen. Der Rat scheint offensichtlich, ist aber relevant, da in realen Anwendungen ein ähnliches Problem auftreten kann: Wir waren mit einer Situation konfrontiert, in der der Eingabewert drei Dezimalstellen hatte und sechs in der Datenbank gespeichert waren.


Der einfachste Weg, um dieses Problem zu verhindern, besteht darin, die Eingabewerte mit maximaler Genauigkeit (bis zu Mikrosekunden) zu verwenden. In diesem Fall wird beim Schreiben von Daten in die Tabelle die Zeit auf die erforderliche Genauigkeit gerundet. Dies ist eine absolut normale Situation, die keine Warnung (en) hervorruft:


 Test> UPDATE ChatContactsList SET last_message_send_time="2019-09-22 22:23:15.2345" WHERE chat_id=1; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 Test> SELECT * FROM ChatContactsList; +---------+---------+-------------------------+ | chat_id | title | last_message_send_time | +---------+---------+-------------------------+ | 1 | Chat #1 | 2019-09-22 22:23:15.235 | +---------+---------+-------------------------+ 1 row in set (0.00 sec) 

Bei Verwendung der automatischen Initialisierung und automatischen Aktualisierung von TIMESTAMP-Spalten unter Verwendung einer Struktur der Form DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP es wichtig, dass die Werte dieselbe Genauigkeit wie die Spalte selbst haben:


 Test> ALTER TABLE ChatContactsList ADD COLUMN updated TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP; ERROR 1067 (42000): Invalid default value for 'updated' Test> ALTER TABLE ChatContactsList ADD COLUMN updated TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6); ERROR 1067 (42000): Invalid default value for 'updated' Test> ALTER TABLE ChatContactsList ADD COLUMN updated TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3); Query OK, 0 rows affected (0.07 sec) Records: 0 Duplicates: 0 Warnings: 0 Test> UPDATE ChatContactsList SET last_message_send_time='2019-09-22 22:22:22' WHERE chat_id=1; Query OK, 0 rows affected (0.00 sec) Rows matched: 1 Changed: 0 Warnings: 0 Test> SELECT * FROM ChatContactsList; +---------+---------+-------------------------+-------------------------+ | chat_id | title | last_message_send_time | updated | +---------+---------+-------------------------+-------------------------+ | 1 | Chat #1 | 2019-09-22 22:22:22.000 | 2019-09-22 22:26:39.968 | +---------+---------+-------------------------+-------------------------+ 1 row in set (0.00 sec) 

MySQL-Funktionen für das Arbeiten im Laufe der Zeit unterstützen das Arbeiten mit dem Bruchteil der Maßeinheiten. Ich werde sie nicht alle auflisten (ich schlage vor, in der Dokumentation nachzuschauen), aber ich werde einige Beispiele geben:


 Test> SELECT NOW(2), NOW(4), NOW(4) + INTERVAL 7.5 SECOND; +------------------------+--------------------------+------------------------------+ | NOW(2) | NOW(4) | NOW(4) + INTERVAL 7.5 SECOND | +------------------------+--------------------------+------------------------------+ | 2019-09-22 21:12:23.31 | 2019-09-22 21:12:23.3194 | 2019-09-22 21:12:30.8194 | +------------------------+--------------------------+------------------------------+ 1 row in set (0.00 sec) Test> SELECT SUBTIME(CURRENT_TIME(6), CURRENT_TIME(3)), CURRENT_TIME(6), CURRENT_TIME(3); +-------------------------------------------+-----------------+-----------------+ | SUBTIME(CURRENT_TIME(6), CURRENT_TIME(3)) | CURRENT_TIME(6) | CURRENT_TIME(3) | +-------------------------------------------+-----------------+-----------------+ | 00:00:00.000712 | 21:12:50.793712 | 21:12:50.793 | +-------------------------------------------+-----------------+-----------------+ 1 row in set (0.00 sec) 

Das Hauptproblem bei der Verwendung des Bruchteils von Sekunden in SQL-Abfragen ist die Inkonsistenz der Genauigkeit bei Vergleichen ( > , < , BETWEEN ). Sie können darauf stoßen, wenn die Daten in der Datenbank eine Genauigkeit und in den Abfragen eine andere haben. Hier ist ein kleines Beispiel, das dieses Problem veranschaulicht:


 #         Test> INSERT INTO ChatContactsList (title, last_message_send_time) VALUES ('Chat #2', '2019-09-22 21:16:39.123456'); Query OK, 0 row affected (0.00 sec) Test> SELECT chat_id, title, last_message_send_time FROM ChatContactsList WHERE title='Chat #2'; +---------+---------+-------------------------+ | chat_id | title | last_message_send_time | +---------+---------+-------------------------+ | 2 | Chat #2 | 2019-09-22 21:16:39.123 | <-     - ,    +---------+---------+-------------------------+ 1 row in set (0.00 sec) Test> SELECT title, last_message_send_time FROM ChatContactsList WHERE last_message_send_time >= '2019-09-22 21:16:39.123456'; <-    ,    INSERT- +---------+-------------------------+ | title | last_message_send_time | +---------+-------------------------+ | Chat #1 | 2019-09-22 22:22:22.000 | +---------+-------------------------+ 1 row in set (0.00 sec) <- Chat #2   - ,     ,     

In diesem Beispiel ist die Genauigkeit der Werte in der Abfrage höher als die Genauigkeit der Werte in der Datenbank, und das Problem tritt "an der Grenze von oben" auf. In der umgekehrten Situation (wenn der Eingabewert eine Genauigkeit aufweist, die niedriger als der Wert in der Datenbank ist) gibt es kein Problem - MySQL bringt den Wert sowohl in INSERT als auch in SELECT auf die gewünschte Genauigkeit:


 Test> INSERT INTO ChatContactsList (title, last_message_send_time) VALUES ('Chat #3', '2019-09-03 21:20:19.1'); Query OK, 1 row affected (0.00 sec) Test> SELECT title, last_message_send_time FROM ChatContactsList WHERE last_message_send_time <= '2019-09-03 21:20:19.1'; +---------+-------------------------+ | title | last_message_send_time | +---------+-------------------------+ | Chat #3 | 2019-09-03 21:20:19.100 | +---------+-------------------------+ 1 row in set (0.00 sec) 

Die Konsistenz der Genauigkeit von Werten sollte immer beachtet werden, wenn mit hochpräziser Zeit gearbeitet wird. Wenn solche Grenzprobleme für Sie kritisch sind, müssen Sie sicherstellen, dass der Code und die Datenbank mit der gleichen Anzahl von Dezimalstellen funktionieren.


Gedanken zur Auswahl der Genauigkeit in Spalten mit Bruchteilen von Sekunden

Die Menge an Platz, die der Bruchteil einer Zeiteinheit einnimmt, hängt von der Anzahl der Zeichen in der Spalte ab. Es erscheint natürlich, bekannte Bedeutungen zu wählen: drei oder sechs Dezimalstellen. Bei drei Zeichen ist dies jedoch nicht so einfach. Tatsächlich verwendet MySQL ein Byte, um zwei Dezimalstellen zu speichern:


SekundenbruchteilpräzisionSpeicheranforderungen
00 Bytes
1, 21 Byte
3, 42 Bytes
5, 63 Bytes


Speicheranforderungen für Datum und Uhrzeit

Es stellt sich heraus, dass Sie bei Auswahl von drei Dezimalstellen den belegten Platz nicht voll ausnutzen und für denselben Overhead vier Zeichen verwenden können. Im Allgemeinen empfehle ich, dass Sie bei der Ausgabe immer eine gerade Anzahl von Zeichen verwenden und gegebenenfalls unnötige Zeichen „zuschneiden“. Die ideale Option ist, nicht gierig zu sein und sechs Dezimalstellen zu nehmen. Im schlimmsten Fall (mit dem Typ DATETIME) belegt diese Spalte 8 Bytes, dh dieselbe Ganzzahl wie die Ganzzahl in der Spalte BIGINT.


Siehe auch:



Arbeiten Sie mit hochpräziser Zeit in PHP


Es reicht nicht aus, eine hochpräzise Zeit in der Datenbank zu haben - Sie müssen in der Lage sein, im Code Ihrer Programme damit zu arbeiten. In diesem Abschnitt werde ich über drei Hauptpunkte sprechen:


  1. Empfangs- und Formatierungszeit: Ich werde erklären, wie der Zeitstempel abgerufen wird, bevor er in die Datenbank gestellt, von dort abgerufen und manipuliert wird.
  2. Arbeiten mit der Zeit in PDO: Ich zeige Ihnen ein Beispiel dafür, wie PHP die Formatierungszeit in einer Datenbankbibliothek unterstützt.
  3. Arbeiten mit der Zeit in Frameworks: Ich werde über die Verwendung von Zeit in Migrationen sprechen, um die Struktur der Datenbank zu ändern.

Zeit abrufen und formatieren


Wenn Sie mit der Zeit arbeiten, müssen Sie einige grundlegende Vorgänge ausführen können:


  • den aktuellen Zeitpunkt abrufen;
  • einen Moment in der Zeit von einer formatierten Zeichenfolge bekommen;
  • Hinzufügen einer Periode zu einem Zeitpunkt (oder Subtrahieren einer Periode);
  • Abrufen einer formatierten Zeichenfolge für einen bestimmten Zeitpunkt.

In diesem Teil werde ich Ihnen sagen, welche Möglichkeiten zur Ausführung dieser Operationen in PHP bestehen.


Die erste Möglichkeit besteht darin, mit einem Zeitstempel als Zahl zu arbeiten . In diesem Fall arbeiten wir im PHP-Code mit numerischen Variablen, die wir über Funktionen wie time , date und strtotime . Diese Methode kann nicht verwendet werden, um mit hochpräziser Zeit zu arbeiten, da in all diesen Funktionen die Zeitstempel eine Ganzzahl sind (was bedeutet, dass der Bruchteil in ihnen verloren geht).


Hier sind die Unterschriften der wichtigsten derartigen Funktionen aus der offiziellen Dokumentation:


time ( void ) : int
https://www.php.net/manual/ru/function.time.php

strtotime ( string $time [, int $now = time() ] ) : int
http://php.net/manual/ru/function.strtotime.php

date ( string $format [, int $timestamp = time() ] ) : string
https://php.net/manual/ru/function.date.php

strftime ( string $format [, int $timestamp = time() ] ) : string
https://www.php.net/manual/ru/function.strftime.php

Ein interessanter Punkt zur Datumsfunktion

Obwohl es unmöglich ist, den Bruchteil einer Sekunde an die Eingabe dieser Funktionen zu übergeben, können Sie in der Zeile der Formatierungsvorlage, die an die Eingabe der date wird, Zeichen so einstellen, dass Millisekunden und Mikrosekunden angezeigt werden. Bei der Formatierung werden Nullen immer an ihrer Stelle zurückgegeben.


Zeichen im ZeichenfolgenformatBeschreibungBeispiel für einen Rückgabewert
uMikrosekunden (hinzugefügt in PHP 5.2.2). Beachten Sie, dass date () immer 000000 als zurückgibt Es wird ein ganzzahliger Parameter verwendet, während DateTime :: format () Mikrosekunden unterstützt, wenn DateTime mit ihnen erstellt wird.Zum Beispiel: 654321
vMillisekunden (hinzugefügt in PHP 7.0.0). Die Bemerkung ist die gleiche wie für u.Zum Beispiel: 654

Ein Beispiel:


 $now = time(); print date('Ymd H:i:s.u', $now); // 2019-09-11 21:27:18.000000 print date('Ymd H:i:s.v', $now); // 2019-09-11 21:27:18.000 

Zu dieser Methode gehören auch microtime und hrtime , mit denen Sie einen hrtime mit einem Bruchteil für den aktuellen Moment erhalten können. Das Problem ist, dass es keine vorgefertigte Möglichkeit gibt, ein solches Etikett zu formatieren und es aus einer Zeichenfolge eines bestimmten Formats abzurufen. Dies kann durch unabhängige Implementierung dieser Funktionen gelöst werden, aber ich werde eine solche Option nicht in Betracht ziehen.


Wenn Sie nur mit Timern arbeiten müssen, ist die HRTime- Bibliothek eine gute Option, auf die ich aufgrund der Einschränkungen ihrer Verwendung nicht näher eingehen werde. Ich kann nur sagen, dass Sie damit mit der Zeit bis zur Nanosekunde arbeiten können und die Monotonie der Timer garantieren, wodurch einige der Probleme beseitigt werden, die bei der Arbeit mit anderen Bibliotheken auftreten können.

Um mit Bruchteilen einer Sekunde vollständig arbeiten zu können, müssen Sie das DateTime- Modul verwenden. Mit bestimmten Vorbehalten können Sie alle oben aufgeführten Vorgänge ausführen:


 //    : $time = new \DateTimeImmutable(); //      : $time = new \DateTimeImmutable('2019-09-12 21:32:43.908502'); $time = \DateTimeImmutable::createFromFormat('Ymd H:i:s.u', '2019-09-12 21:32:43.9085'); // / : $period = \DateInterval::createFromDateString('5 seconds'); $timeBefore = $time->add($period); $timeAfter = $time->sub($period); //      : print $time->format('Ymd H:i:s.v'); // '2019-09-12 21:32:43.908' print $time->format("Ymd H:i:su"); // '2019-09-12 21:32:43.908502' 

Der nicht offensichtliche Punkt bei der Verwendung von "DateTimeImmutable :: createFromFormat"

Der Buchstabe u in der Formatzeichenfolge bedeutet Mikrosekunden, funktioniert aber auch bei Bruchteilen mit geringerer Genauigkeit korrekt. Darüber hinaus ist dies die einzige Möglichkeit, Bruchteile einer Sekunde in einer Formatzeichenfolge anzugeben. Ein Beispiel:


 $time = \DateTimeImmutable::createFromFormat('Ymd H:i:s.u', '2019-09-12 21:32:43.9085'); // =>   DateTimeImmutable    2019-09-12 21:32:43.908500 $time = \DateTimeImmutable::createFromFormat('Ymd H:i:s.u', '2019-09-12 21:32:43.90'); // =>   DateTimeImmutable    2019-09-12 21:32:43.900000 $time = \DateTimeImmutable::createFromFormat('Ymd H:i:s.u', '2019-09-12 21:32:43'); // =>  false 

Das Hauptproblem dieses Moduls ist die Unannehmlichkeit beim Arbeiten mit Intervallen, die Sekundenbruchteile enthalten (oder sogar die Unmöglichkeit einer solchen Arbeit). Die \DateInterval enthält den Bruchteil einer Sekunde, der auf die gleichen sechs Dezimalstellen genau ist. Sie können diesen Bruchteil jedoch nur über DateTime::diff initialisieren. Der Konstruktor der DateInterval-Klasse und die Factory-Methode \DateInterval::createFromDateString können nur mit ganzen Sekunden arbeiten und erlauben nicht, den Bruchteil anzugeben:


 //     -   $buggyPeriod1 = new \DateInterval('PT7.500S'); //       ,    $buggyPeriod2 = \DateInterval::createFromDateString('2 minutes 7.5 seconds'); print $buggyPeriod2->format('%R%H:%I:%S.%F') . PHP_EOL; //  "+00:02:00.000000" 

Ein weiteres Problem kann auftreten, wenn die Differenz zwischen zwei Zeitpunkten mit der Methode \DateTimeImmutable::diff berechnet wird. In PHP vor Version 7.2.12 gab es einen Fehler, aufgrund dessen die Bruchteile einer Sekunde getrennt von anderen Ziffern existierten und ein eigenes Vorzeichen erhalten konnten:


 $timeBefore = new \DateTimeImmutable('2019-09-12 21:20:19.987654'); $timeAfter = new \DateTimeImmutable('2019-09-14 12:13:14.123456'); $diff = $timeBefore->diff($timeAfter); print $diff->format('%R%a days %H:%I:%S.%F') . PHP_EOL; //  PHP  7.2.12+   "+1 days 14:52:54.135802" //       "+1 days 14:52:55.-864198" 

Im Allgemeinen rate ich Ihnen, beim Arbeiten mit Intervallen vorsichtig zu sein und diesen Code sorgfältig mit Tests abzudecken.


Siehe auch:



Arbeiten Sie mit hoher Präzision in PDO


PDO und mysqli sind die beiden Hauptschnittstellen zum Abfragen von MySQL-Datenbanken aus PHP-Code. Im Zusammenhang mit einem Gespräch über die Zeit sind sie einander ähnlich, daher werde ich nur über einen von ihnen sprechen - PDO.


Bei der Arbeit mit Datenbanken in PDO wird die Zeit an zwei Stellen angezeigt:


  • als Parameter, der an ausgeführte Abfragen übergeben wird;
  • als Wert, der als Antwort auf SELECT-Abfragen kommt.

Es wird empfohlen, Platzhalter zu übergeben, wenn Parameter an die Anforderung übergeben werden. Platzhalter können Werte aus einer sehr kleinen Gruppe von Typen übertragen: Boolesche Werte, Zeichenfolgen und Ganzzahlen. Es gibt keinen geeigneten Typ für Datum und Uhrzeit, daher müssen Sie den Wert von einem Objekt der DateTime / DateTimeImmutable-Klasse manuell in eine Zeichenfolge konvertieren.


 $now = new \DateTimeImmutable(); $db = new \PDO('mysql:...', 'user', 'password', [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]); $stmt = $db->prepare('INSERT INTO Test.ChatContactsList (title, last_message_send_time) VALUES (:title, :date)'); $result = $stmt->execute([':title' => "Test #1", ':date' => $now->format('Ymd H:i:s.u')]); 

Die Verwendung eines solchen Codes ist nicht sehr praktisch, da Sie den übertragenen Wert jedes Mal formatieren müssen. Daher haben wir in der Badoo-Codebasis die Unterstützung für typisierte Platzhalter in unserem Wrapper für die Arbeit mit der Datenbank implementiert. Bei Datumsangaben ist dies sehr praktisch, da Sie damit einen Wert in verschiedenen Formaten übertragen können (ein Objekt, das DateTimeInterface implementiert, eine formatierte Zeichenfolge oder eine Zahl mit einem Zeitstempel) und alle erforderlichen Transformationen und Überprüfungen der Richtigkeit der übertragenen Werte bereits im Inneren durchgeführt werden. Als Bonus erfahren wir beim Übertragen eines falschen Werts sofort von dem Fehler und nicht, nachdem wir beim Ausführen der Abfrage einen Fehler von MySQL erhalten haben.


Das Abrufen von Daten aus Abfrageergebnissen sieht ziemlich einfach aus. Bei der Ausführung dieser Operation gibt PDO die Daten in Form von Zeichenfolgen zurück, und im Code müssen wir die Ergebnisse weiterverarbeiten, wenn wir mit Zeitobjekten arbeiten möchten (und hier benötigen wir die Funktionalität, um die Zeit aus der formatierten Zeichenfolge abzurufen, über die ich im vorherigen Abschnitt gesprochen habe).


 $stmt = $db->prepare('SELECT * FROM Test.ChatContactsList ORDER BY last_message_send_time DESC, chat_id DESC LIMIT 5'); $stmt->execute(); while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { $row['last_message_send_time'] = is_null($row['last_message_send_time']) ? null : new \DateTimeImmutable($row['last_message_send_time']); //  -  } 

Hinweis

Die Tatsache, dass PDO Daten als Zeichenfolgen zurückgibt, ist nicht ganz richtig. Beim Empfang von Werten kann der PDOStatement::bindColumn für die Spalte mithilfe der PDOStatement::bindColumn . Ich habe nicht darüber gesprochen, weil es die gleiche begrenzte Anzahl von Typen gibt, die bei Datumsangaben nicht hilfreich sind.

Leider gibt es ein Problem zu beachten. In PHP vor Version 7.3 gibt es einen Fehler, aufgrund dessen PDO, wenn das Attribut PDO::ATTR_EMULATE_PREPARES den Bruchteil einer Sekunde "abschneidet", wenn es von der Datenbank empfangen wird. Details und ein Beispiel finden Sie in der Fehlerbeschreibung auf php.net . In PHP 7.3 wurde dieser Fehler behoben und gewarnt, dass diese Änderung die Abwärtskompatibilität beeinträchtigt .


Wenn Sie PHP Version 7.2 oder älter verwenden und es nicht aktualisieren oder PDO::ATTR_EMULATE_PREPARES , können Sie diesen Fehler PDO::ATTR_EMULATE_PREPARES , indem Sie SQL-Abfragen korrigieren, die die Zeit mit einem Bruchteil zurückgeben, sodass diese Spalte einen Zeichenfolgentyp hat. Dies kann zum Beispiel folgendermaßen geschehen:


 SELECT *, CAST(last_message_send_time AS CHAR) AS last_message_send_time_fixed FROM ChatContactsList ORDER BY last_message_send_time DESC LIMIT 1; 

Dieses Problem kann auch bei der Arbeit mit dem mysqli Modul auftreten: Wenn Sie vorbereitete Abfragen über einen Aufruf der mysqli::prepare Methode verwenden, wird in PHP vor Version 7.3 der Bruchteil einer Sekunde nicht zurückgegeben. Wie bei PDO können Sie dies beheben, indem Sie PHP aktualisieren oder die Zeitkonvertierung in einen Zeichenfolgentyp umgehen.


Siehe auch:



Arbeiten Sie in Yii 2 mit hoher Präzision


Die meisten modernen Frameworks bieten Migrationsfunktionen, mit denen Sie den Verlauf von Datenbankschemaänderungen im Code speichern und schrittweise ändern können. Wenn Sie Migrationen verwenden und hochpräzise Zeit verwenden möchten, sollte Ihr Framework dies unterstützen. Glücklicherweise funktioniert dies in allen wichtigen Frameworks sofort.


In diesem Abschnitt werde ich zeigen, wie diese Unterstützung in Yii implementiert ist (in den Beispielen, in denen ich Version 2.0.26 verwendet habe). Über Laravel, Symfony und andere werde ich nicht schreiben, um den Artikel nicht endlos zu machen, aber ich würde mich freuen, wenn Sie Details in den Kommentaren oder neuen Artikeln zu diesem Thema hinzufügen.


Bei der Migration schreiben wir Code, der Änderungen am Datenschema beschreibt. Beim Erstellen einer neuen Tabelle beschreiben wir alle Spalten mit speziellen Methoden aus der Klasse \ yii \ db \ Migration (sie werden in der Taskleiste SchemaBuilderTrait deklariert). Die Methoden time , timestamp und datetime , die einen Eingabewert für die Genauigkeit datetime können, sind für die Beschreibung von Spalten verantwortlich, die Datum und Uhrzeit enthalten.


Ein Beispiel für eine Migration, bei der eine neue Tabelle mit einer hochpräzisen Zeitspalte erstellt wird:


 use yii\db\Migration; class m190914_141123_create_news_table extends Migration { public function up() { $this->createTable('news', [ 'id' => $this->primaryKey(), 'title' => $this->string()->notNull(), 'content' => $this->text(), 'published' => $this->timestamp(6), //     ]); } public function down() { $this->dropTable('news'); } } 

Dies ist ein Beispiel für eine Migration, bei der sich die Genauigkeit in einer vorhandenen Spalte ändert:


 class m190916_045702_change_news_time_precision extends Migration { public function up() { $this->alterColumn( 'news', 'published', $this->timestamp(6) ); return true; } public function down() { $this->alterColumn( 'news', 'published', $this->timestamp(3) ); return true; } } 

ActiveRecord - : , DateTime-. , — «» PDO::ATTR_EMULATE_PREPARES . Yii , . , , PDO.


. :



Fazit


, , — , . , , . , !

Source: https://habr.com/ru/post/de469615/


All Articles