sqlpostgresqldatetimetimestamp

CURRENT_TIMESTAMP vs LOCALTIMESTAMP in SQL

How CURRENT_TIMESTAMP differs from LOCALTIMESTAMP, why both freeze inside a transaction, and when to reach for clock_timestamp().

3 min. skaitymoReferencesql · postgresql · datetime · timestamp · audit
Šis straipsnis šiuo metu yra rusų kalba — vertimas į anglų kalbą rengiamas.

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), а LOCALTIMESTAMPtimestamp 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().

Гоча: 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() — ваш инструмент, когда нужно настоящее тикающее время.

Praktikuokitės su realiomis užduotimis

Spręskite užduotis SQL treniruoklyje su momentiniu vertinimu ir užuominomis.

Atverti treniruoklį