用floor+unix_timestamp可实现任意分钟粒度分组,核心是将datetime转秒级时间戳→除以粒度秒数→向下取整→乘回对齐时间点;mysql示例:from_unixtime(floor(unix_timestamp(event_time)/900)*900);postgresql推荐date_bin('15 minutes', event_time, '2000-01-01')。

用 FLOOR + UNIX_TIMESTAMP 实现任意分钟粒度分组
直接对 datetime 字段做 GROUP BY 无法按 15 或 30 分钟切片,必须先将时间“对齐”到最近的整粒度起点。核心思路是:转成秒级时间戳 → 除以粒度秒数 → 向下取整 → 再乘回去还原为对齐后的时间点。
比如 15 分钟 = 900 秒,FLOOR(UNIX_TIMESTAMP(log_time) / 900) * 900 就能把 '2024-05-20 14:22:18' 对齐到 '2024-05-20 14:15:00'。
实际写法示例(MySQL):
SELECT FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(event_time) / 900) * 900) AS time_slot, COUNT(*) AS cnt FROM logs WHERE event_time >= '2024-05-20 00:00:00' GROUP BY time_slot ORDER BY time_slot;
- 注意
FROM_UNIXTIME返回的是本地时区时间,若日志存的是 UTC,需先用CONVERT_TZ或确保会话时区一致 -
FLOOR比ROUND更可靠——它保证向下取整,避免跨区间误归并 - PostgreSQL 用户请改用
FLOOR(EXTRACT(EPOCH FROM event_time) / 900),再用TO_TIMESTAMP还原
PostgreSQL 中用 date_bin 更简洁(v14+)
PostgreSQL 14 引入了 date_bin,专为时序分桶设计,语义清晰、时区处理更稳,比手算时间戳更少出错。
15 分钟分组写法:
SELECT
date_bin('15 minutes', event_time, '2000-01-01') AS time_slot,
COUNT(*) AS cnt
FROM logs
WHERE event_time >= '2024-05-20'
GROUP BY time_slot
ORDER BY time_slot;
- 第二个参数是原始时间列,第三个参数是“对齐基准点”,通常用任意能被粒度整除的日期(如
'2000-01-01'),它决定每个 slot 的起始偏移 - 若想让 30 分钟 slot 从整点开始(如 14:00、14:30),基准点用
'2000-01-01 00:00:00'即可;若从 14:15 开始,则用'2000-01-01 00:15:00' - 旧版 PostgreSQL(date_bin,只能退回
generate_series+JOIN或手算EXTRACT,复杂度明显上升
避免常见错误:时区错位与空 slot 缺失
聚合结果里突然某几个 30 分钟段计数为 0?不是数据丢了,而是 GROUP BY 默认只返回有数据的组,且时间字段若没统一时区,date_bin 或 FROM_UNIXTIME 可能跨天错位。
- 检查表中
event_time列类型:用TIMESTAMP WITHOUT TIME ZONE存 UTC 时间,但查询时未声明时区,会导致date_bin按本地时区解释,结果偏移 - 补全空 slot 需要额外生成时间序列。MySQL 没内置函数,得靠递归 CTE 或临时数字表;PostgreSQL 可用
generate_series左连接:
SELECT s.slot, COALESCE(l.cnt, 0) AS cnt
FROM generate_series(
'2024-05-20 00:00:00'::timestamp,
'2024-05-20 23:59:59',
'30 minutes'
) AS s(slot)
LEFT JOIN (
SELECT date_bin('30 minutes', event_time, '2000-01-01') AS slot, COUNT(*) AS cnt
FROM logs WHERE event_time >= '2024-05-20'
GROUP BY 1
) l ON s.slot = l.slot;
性能关键:时间字段必须有索引,且避免函数包裹
如果在 WHERE 条件里写 FROM_UNIXTIME(UNIX_TIMESTAMP(event_time)) > ...,索引完全失效。所有过滤必须作用于原始时间列本身。
- 正确写法:
WHERE event_time >= '2024-05-20 00:00:00' AND event_time - 确保
event_time列上有 B-tree 索引(MySQL/PostgreSQL 均适用) - 若常查固定粒度(如每 15 分钟),可考虑加生成列(MySQL 5.7+/PG 12+)并索引:
ALTER TABLE logs ADD COLUMN slot_15m TIMESTAMP GENERATED ALWAYS AS (FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(event_time)/900)*900)) STORED
对齐计算本身开销极小,瓶颈永远在 I/O 和索引覆盖上;一旦 WHERE 条件触发全表扫描,再快的分组逻辑也救不回来。










