NOW() в PHP: правда, которую вы не знали о времени
Каждый PHP-разработчик хотя бы раз в жизни писал SQL-запрос вида INSERT INTO logs (created_at) VALUES (NOW()). И это отлично работает. Но что, если я скажу вам, что использование MySQL-функции NOW() в каждом запросе — это не всегда оптимально? А если ваш проект обрабатывает миллионы запросов? Или вам нужно единое время для целого скрипта?
В этой статье мы разберём, как эмулировать функцию NOW() в PHP, как повысить производительность приложений и избежать типичных ошибок, связанных с временем. Вы узнаете, когда PHP-аналог NOW() работает быстрее, чем обращение к базе данных, и какие подводные камни скрывает работа с часовыми поясами.
1. Что такое NOW() и зачем её эмулировать в PHP?
NOW() — это встроенная функция MySQL (и других СУБД), которая возвращает текущую дату и время в формате YYYY-MM-DD HH:MM:SS. Казалось бы, зачем что-то менять? Но есть нюансы:
- Нагрузка на БД: каждый вызов
NOW()— это отдельный запрос к серверу. Если вы вставляете 10 000 строк, вы 10 000 раз отправляете запрос, получаете ответ и только потом вставляете данные. - Разное время: если ваш скрипт работает несколько секунд,
NOW()в разных запросах может вернуть разное время (хотя разница в миллисекундах, но для логов это критично). - Переносимость: при смене СУБД (например, на PostgreSQL или SQLite)
NOW()может не сработать или работать иначе.
Решение: вычислить время на стороне PHP и передать его как параметр. Так вы получаете единое время для всех операций, снижаете нагрузку на БД и делаете код универсальным.
2. Как реализовать NOW() в PHP: три проверенных способа
2.1. Классический date('Y-m-d H:i:s')
Самый простой и быстрый способ — использовать встроенную функцию date():
<?php
$now = date('Y-m-d H:i:s');
echo $now; // 2025-01-15 14:30:00
?>
Плюсы: работает везде, минимальное потребление памяти, не требует загрузки дополнительных классов.
Минусы: зависит от часового пояса, установленного в php.ini (date.timezone).
2.2. Объектно-ориентированный DateTime
Современный стандарт PHP — класс DateTime. Он гибче и удобнее:
<?php
$now = new DateTime();
echo $now->format('Y-m-d H:i:s'); // 2025-01-15 14:30:00
?>
Вы можете явно указать часовой пояс:
<?php
$timezone = new DateTimeZone('Europe/Moscow');
$now = new DateTime('now', $timezone);
echo $now->format('Y-m-d H:i:s'); // 2025-01-15 17:30:00 (MSK)
?>
2.3. Микросекунды с DateTimeImmutable
Для точных замеров или логов с высокой детализацией используйте DateTimeImmutable (не изменяет исходный объект):
<?php
$now = new DateTimeImmutable();
echo $now->format('Y-m-d H:i:s.u'); // 2025-01-15 14:30:00.123456
?>
Совет: если вам нужно только время для вставки в БД, используйте date() — он на 10-15% быстрее, чем DateTime.
3. Оптимизация производительности: тесты и сравнение
Давайте проведём простой бенчмарк. Сравним три способа получения текущего времени в цикле из 100 000 итераций:
<?php
$iterations = 100000;
// Способ 1: date()
$start = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
$now = date('Y-m-d H:i:s');
}
$time1 = microtime(true) - $start;
// Способ 2: DateTime
$start = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
$now = (new DateTime())->format('Y-m-d H:i:s');
}
$time2 = microtime(true) - $start;
// Способ 3: DateTimeImmutable
$start = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
$now = (new DateTimeImmutable())->format('Y-m-d H:i:s');
}
$time3 = microtime(true) - $start;
echo "date(): " . round($time1, 4) . " сек\n";
echo "DateTime: " . round($time2, 4) . " сек\n";
echo "DateTimeImmutable: " . round($time3, 4) . " сек\n";
?>
Результаты (на PHP 8.2):
date()— 0.08 сек (самый быстрый)DateTime— 0.12 сек (на 50% медленнее)DateTimeImmutable— 0.14 сек (на 75% медленнее)
Вывод: для массовых вставок (например, импорт данных) используйте date() или кешируйте время. Для обычных операций разница незаметна.
4. Кеширование NOW() в PHP: единое время для всего скрипта
Самая частая ошибка — вызывать NOW() или date() внутри цикла. Это не только нагружает БД, но и может дать разные значения времени. Правильный подход — вычислить время один раз и сохранить в переменную:
<?php
// Плохо: каждый запрос получает новое время
for ($i = 0; $i < 100; $i++) {
$sql = "INSERT INTO logs (message, created_at) VALUES ('log $i', NOW())";
$db->query($sql);
}
// Хорошо: единое время для всех записей
$now = date('Y-m-d H:i:s');
for ($i = 0; $i < 100; $i++) {
$sql = "INSERT INTO logs (message, created_at) VALUES ('log $i', :now)";
$stmt = $db->prepare($sql);
$stmt->execute([':now' => $now]);
}
?>
Бонус: если вам нужно одно и то же время в разных частях скрипта, используйте синглтон или глобальную переменную