Tämä artikkeli on tällä hetkellä venäjäksi — englanninkielinen käännös on työn alla.
CURRENT_TIMESTAMP — стандартная SQL-функция, которая в PostgreSQL возвращает timestamp with time zone со временем старта текущей транзакции; её ставят в DEFAULT для колонок created_at, в строки аудита и везде, где нужна отметка «когда это произошло». Важно понимать две вещи: какой тип она отдаёт и в какой момент берётся это время — именно здесь чаще всего ошибаются.
PostgreSQL предлагает не одну функцию времени, а целое семейство, и каждая отвечает на свой вопрос. CURRENT_TIMESTAMP и LOCALTIMESTAMP дают время транзакции, statement_timestamp() — время оператора, clock_timestamp() — реальное тикающее «сейчас». Выбор между ними определяет, совпадут ли отметки в пределах одной транзакции и попадёт ли в колонку абсолютный момент или «стенные часы». У CURRENT_TIMESTAMP есть и форма с точностью — CURRENT_TIMESTAMP(0) обрежет дробные секунды, CURRENT_TIMESTAMP(3) оставит миллисекунды; без аргумента берётся полная точность timestamptz. Дальше разберём всё это на конкретных запросах.
Два главных: с зоной и без
CURRENT_TIMESTAMP возвращает timestamp with time zone (он же timestamptz), а LOCALTIMESTAMP — timestamp without time zone. Это не косметика: первый хранит абсолютный момент времени, второй — «стенные часы» без привязки к зоне.
SELECT
CURRENT_TIMESTAMP AS with_zone,
LOCALTIMESTAMP AS without_zone;
Практический вывод простой: для глобального приложения почти всегда нужен timestamptz. Колонка users.created_at должна быть timestamptz, чтобы пользователь из Лиссабона и из Москвы получили один и тот же абсолютный момент, а не два разных «локальных».
CREATE TABLE users (
id bigserial PRIMARY KEY,
email text NOT NULL UNIQUE,
name text,
country text,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
);
Время транзакции, а не оператора
Ключевая «фишка»: CURRENT_TIMESTAMP и LOCALTIMESTAMP возвращают момент старта транзакции и не меняются до её конца. Внутри одной транзакции все вызовы дадут идентичное значение — даже если между ними прошли секунды.
BEGIN;
SELECT CURRENT_TIMESTAMP;
SELECT CURRENT_TIMESTAMP;
COMMIT;
Это очень удобно: если вы одной транзакцией вставляете заказ и строку аудита, обе получат одинаковый created_at — никаких расхождений на доли секунды.
BEGIN;
INSERT INTO orders (user_id, amount, status, created_at)
VALUES (42, 99.90, 'paid', CURRENT_TIMESTAMP);
INSERT INTO order_audit (order_id, event, at)
VALUES (currval('orders_id_seq'), 'created', CURRENT_TIMESTAMP);
COMMIT;
Когда нужно «настоящее сейчас»: clock_timestamp()
Иногда заморозка мешает — например, при профилировании или построчных метках времени в одном запросе. Здесь у PostgreSQL есть градация:
transaction_timestamp() — синоним CURRENT_TIMESTAMP, момент старта транзакции.
statement_timestamp() — момент старта текущего оператора.
clock_timestamp() — реальное «сейчас», меняется при каждом вызове, даже внутри одного SELECT.
SELECT
clock_timestamp() AS row_time
FROM generate_series(1, 3);
Гайдлайн: для аудита и бизнес-данных берите CURRENT_TIMESTAMP (консистентность важнее). Для измерения длительности шагов внутри функции берите clock_timestamp().
Гоча: now() в PostgreSQL — это ровно transaction_timestamp(), то есть тоже заморожено. Если в цикле PL/pgSQL вы ждёте, что now() будет «тикать», — он не будет. Используйте clock_timestamp().
DEFAULT для аудиторских колонок
Самый частый сценарий — автозаполнение колонок создания и обновления. created_at решается через DEFAULT, а updated_at — через триггер, потому что DEFAULT срабатывает только на INSERT.
CREATE TABLE employees (
id bigserial PRIMARY KEY,
name text NOT NULL,
manager_id bigint REFERENCES employees(id),
dept text,
salary numeric(12,2),
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE OR REPLACE FUNCTION touch_updated_at()
RETURNS trigger AS $$
BEGIN
NEW.updated_at := CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_touch
BEFORE UPDATE ON employees
FOR EACH ROW EXECUTE FUNCTION touch_updated_at();
Отличия в MySQL и ClickHouse
- MySQL:
CURRENT_TIMESTAMP возвращает тип DATETIME (без зоны) и, в отличие от PostgreSQL, отражает время оператора, а не транзакции. Зато здесь есть удобный синтаксис created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP и updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP — авто-updated_at без триггера.
- ClickHouse: используйте
now() (тип DateTime) или now64() для миллисекунд; LOCALTIMESTAMP поддерживается как алиас. Заморозки уровня транзакции, как в PostgreSQL, тут нет — модель совсем другая.
Из-за разной семантики переносить выражения с CURRENT_TIMESTAMP между движками вслепую опасно. В PostgreSQL вся транзакция получит одну отметку, а в MySQL та же вставка из нескольких операторов проставит разное время; колонке updated_at в PostgreSQL нужен триггер, а в MySQL хватает ON UPDATE CURRENT_TIMESTAMP. Прежде чем менять движок, проверьте обе ситуации: совпадают ли отметки внутри одной транзакции и обновляется ли updated_at так, как вы ожидаете.
И последнее про измерение длительности: не пытайтесь засечь время шага разницей двух CURRENT_TIMESTAMP — внутри транзакции она всегда будет нулём, потому что значение заморожено. Для таймингов внутри функции или одного SELECT берите clock_timestamp(), а замороженное время транзакции оставьте аудиту и DEFAULT, где как раз ценна его стабильность.
Итог: CURRENT_TIMESTAMP для абсолютного времени с зоной, LOCALTIMESTAMP когда зона не нужна, оба стабильны в рамках транзакции, а clock_timestamp() — ваш инструмент, когда нужно настоящее тикающее время.
CURRENT_TIMESTAMP— стандартная SQL-функция, которая в PostgreSQL возвращаетtimestamp with time zoneсо временем старта текущей транзакции; её ставят вDEFAULTдля колонокcreated_at, в строки аудита и везде, где нужна отметка «когда это произошло». Важно понимать две вещи: какой тип она отдаёт и в какой момент берётся это время — именно здесь чаще всего ошибаются.PostgreSQL предлагает не одну функцию времени, а целое семейство, и каждая отвечает на свой вопрос.
CURRENT_TIMESTAMPиLOCALTIMESTAMPдают время транзакции,statement_timestamp()— время оператора,clock_timestamp()— реальное тикающее «сейчас». Выбор между ними определяет, совпадут ли отметки в пределах одной транзакции и попадёт ли в колонку абсолютный момент или «стенные часы». УCURRENT_TIMESTAMPесть и форма с точностью —CURRENT_TIMESTAMP(0)обрежет дробные секунды,CURRENT_TIMESTAMP(3)оставит миллисекунды; без аргумента берётся полная точностьtimestamptz. Дальше разберём всё это на конкретных запросах.Два главных: с зоной и без
CURRENT_TIMESTAMPвозвращаетtimestamp with time zone(он жеtimestamptz), аLOCALTIMESTAMP—timestamp without time zone. Это не косметика: первый хранит абсолютный момент времени, второй — «стенные часы» без привязки к зоне.SELECT CURRENT_TIMESTAMP AS with_zone, -- timestamptz LOCALTIMESTAMP AS without_zone; -- timestampПрактический вывод простой: для глобального приложения почти всегда нужен
timestamptz. Колонкаusers.created_atдолжна бытьtimestamptz, чтобы пользователь из Лиссабона и из Москвы получили один и тот же абсолютный момент, а не два разных «локальных».CREATE TABLE users ( id bigserial PRIMARY KEY, email text NOT NULL UNIQUE, name text, country text, created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP );Время транзакции, а не оператора
Ключевая «фишка»:
CURRENT_TIMESTAMPиLOCALTIMESTAMPвозвращают момент старта транзакции и не меняются до её конца. Внутри одной транзакции все вызовы дадут идентичное значение — даже если между ними прошли секунды.BEGIN; SELECT CURRENT_TIMESTAMP; -- e.g. 12:00:00.000 -- ... heavy query running for 3 seconds ... SELECT CURRENT_TIMESTAMP; -- STILL 12:00:00.000 COMMIT;Это очень удобно: если вы одной транзакцией вставляете заказ и строку аудита, обе получат одинаковый
created_at— никаких расхождений на доли секунды.BEGIN; INSERT INTO orders (user_id, amount, status, created_at) VALUES (42, 99.90, 'paid', CURRENT_TIMESTAMP); INSERT INTO order_audit (order_id, event, at) VALUES (currval('orders_id_seq'), 'created', CURRENT_TIMESTAMP); COMMIT;Когда нужно «настоящее сейчас»: clock_timestamp()
Иногда заморозка мешает — например, при профилировании или построчных метках времени в одном запросе. Здесь у PostgreSQL есть градация:
transaction_timestamp()— синонимCURRENT_TIMESTAMP, момент старта транзакции.statement_timestamp()— момент старта текущего оператора.clock_timestamp()— реальное «сейчас», меняется при каждом вызове, даже внутри одногоSELECT.SELECT clock_timestamp() AS row_time FROM generate_series(1, 3); -- three DIFFERENT time valuesГайдлайн: для аудита и бизнес-данных берите
CURRENT_TIMESTAMP(консистентность важнее). Для измерения длительности шагов внутри функции беритеclock_timestamp().DEFAULT для аудиторских колонок
Самый частый сценарий — автозаполнение колонок создания и обновления.
created_atрешается черезDEFAULT, аupdated_at— через триггер, потому чтоDEFAULTсрабатывает только наINSERT.CREATE TABLE employees ( id bigserial PRIMARY KEY, name text NOT NULL, manager_id bigint REFERENCES employees(id), dept text, salary numeric(12,2), created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE OR REPLACE FUNCTION touch_updated_at() RETURNS trigger AS $$ BEGIN NEW.updated_at := CURRENT_TIMESTAMP; RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER trg_touch BEFORE UPDATE ON employees FOR EACH ROW EXECUTE FUNCTION touch_updated_at();Отличия в MySQL и ClickHouse
CURRENT_TIMESTAMPвозвращает типDATETIME(без зоны) и, в отличие от PostgreSQL, отражает время оператора, а не транзакции. Зато здесь есть удобный синтаксисcreated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMPиupdated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP— авто-updated_atбез триггера.now()(типDateTime) илиnow64()для миллисекунд;LOCALTIMESTAMPподдерживается как алиас. Заморозки уровня транзакции, как в PostgreSQL, тут нет — модель совсем другая.Из-за разной семантики переносить выражения с
CURRENT_TIMESTAMPмежду движками вслепую опасно. В PostgreSQL вся транзакция получит одну отметку, а в MySQL та же вставка из нескольких операторов проставит разное время; колонкеupdated_atв PostgreSQL нужен триггер, а в MySQL хватаетON UPDATE CURRENT_TIMESTAMP. Прежде чем менять движок, проверьте обе ситуации: совпадают ли отметки внутри одной транзакции и обновляется лиupdated_atтак, как вы ожидаете.И последнее про измерение длительности: не пытайтесь засечь время шага разницей двух
CURRENT_TIMESTAMP— внутри транзакции она всегда будет нулём, потому что значение заморожено. Для таймингов внутри функции или одногоSELECTберитеclock_timestamp(), а замороженное время транзакции оставьте аудиту иDEFAULT, где как раз ценна его стабильность.Итог:
CURRENT_TIMESTAMPдля абсолютного времени с зоной,LOCALTIMESTAMPкогда зона не нужна, оба стабильны в рамках транзакции, аclock_timestamp()— ваш инструмент, когда нужно настоящее тикающее время.