短链接需设created_at、expires_at、last_accessed_at三个独立时间字段,避免混用导致误判;过期判断应由mysql用now()执行,php层须用datetime对象比对;访问统计须异步解耦以保跳转性能。

短链接的有效期控制和访问统计必须分开设计,不能靠时间戳字段“一拖二”——既当过期判断依据,又当统计时间基准。否则会出现缓存穿透、重复计数、误判过期等实际问题。
MySQL 表结构里为什么需要两个时间字段
短链接表中至少得有 created_at(创建时间)、expires_at(过期时间)和 last_accessed_at(最后访问时间)三个时间字段。只用一个 expires_at 做判断,会导致无法区分“链接已过期但用户仍点进来”和“链接有效且首次访问”这两种场景。
-
expires_at用于 SELECT 查询时 WHERE 过滤,比如WHERE id = ? AND expires_at > NOW() -
last_accessed_at必须在每次成功跳转前用UPDATE ... SET last_accessed_at = NOW() WHERE id = ?单独更新,不能和统计插入混在一起 - 如果还要支持“最多访问 N 次”,就得额外加
access_count和max_accesses字段,且更新逻辑要加事务或乐观锁
PHP 中判断短链接是否过期的正确写法
不要用 time() 和数据库里存的 int 时间戳硬比,尤其当数据库用的是 DATETIME 类型(默认带时区)。直接让 MySQL 自己比最可靠。
- 错误示范:
$now = time(); $row['expires_at'] > $now—— 如果expires_at是'2026-05-20 23:59:59'格式,PHP 的time()和它类型不一致,强转易出错 - 正确做法:查询时就过滤,例如
SELECT * FROM short_links WHERE code = ? AND expires_at > NOW() - 如果必须在 PHP 层判断(比如做缓存预检),统一转成
DateTime对象:$expire = new DateTime($row['expires_at']); $now = new DateTime(); $valid = $expire > $now;
访问统计为什么不能和跳转逻辑写在同一个 SQL 里
短链接跳转要求低延迟、高并发,而统计写入是副作用操作。把 INSERT 统计日志和 UPDATE 短链接访问次数塞进同一个事务,会显著拖慢 302 跳转响应。
- 推荐解耦:跳转路由只做
SELECT+UPDATE last_accessed_at,立刻返回 302;统计记录走异步队列或定时批量写入 - 若坚持同步写,至少把统计表引擎设为
MyISAM(无事务开销)或用INSERT DELAYED(MySQL 5.6+ 已弃用,慎用) - 注意
HTTP_REFERER和HTTP_USER_AGENT可能为空或超长,入库前必须截断,否则触发Data too long for column错误
短链接过期后还能不能统计访问
能,但必须显式允许。默认策略应是“过期链接拒绝跳转,但仍记录一次无效访问”,否则你永远不知道谁在扫过期链接、是不是爬虫在探测。
- 实现上:先查一次
SELECT expires_at, max_accesses, access_count FROM ...,再根据业务逻辑决定是否放行跳转 - 统计表里建议加
is_valid布尔字段,值为 0 表示该次访问因过期/超次/禁用等原因未跳转成功 - 别省事把过期访问全丢进错误日志——它们和 404、500 不同,是真实流量,只是不符合业务规则
真正难的不是怎么写过期判断,而是当短链接被大量转发、缓存、埋在邮件或 App 深链接里时,如何保证 expires_at 的语义不被客户端时间、CDN 缓存、服务端时钟漂移悄悄破坏。这时候,数据库的 NOW() 和应用层的 time() 差几秒,就可能让一批链接提前失效或延后失效。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











