mysql如何实现单点登录Token存储_mysql Session表设计

5次阅读

mysql如何实现单点登录Token存储_mysql Session表设计

Token 存储该用 TEXT 还是 VARCHAR

直接说结论:用 VARCHAR(255),别用 TEXT。JWT 或 UUID 类 Token 长度基本固定,VARCHAR 支持索引、比较快、排序稳定;而 TEXT 在多数 mysql 版本里不能建普通索引(除非前缀索引),且 WHERE token = ? 查询可能隐式转成全表扫描。

常见错误现象:select * FROM sessions WHERE token = 'xxx' 执行慢,EXPLAIN 显示 type: ALL —— 很大概率是字段类型用了 TEXT 又没加前缀索引。

  • UUID v4 是 36 字符(含连字符),JWT 通常在 150–300 字节之间,VARCHAR(255) 足够且留有余量
  • 如果用的是 base64url 编码的随机字节(如 32 字节 → 约 44 字符),VARCHAR(64) 就够了
  • 别为了“省事”设成 VARCHAR(1024) 或更大——InnoDB 行长度限制和内存排序开销会悄悄拖慢批量操作

Session 表要不要存 user_id

要,而且必须加 INDEX。单点登录的核心逻辑是“一个用户只能有一个有效 Token”,所有校验、踢出、续期都依赖 user_id 快速定位。

使用场景:用户重新登录时,需立刻失效旧 Token;管理员强制下线某用户时,得按 user_id 删除多条记录。

  • user_id 类型必须和用户主表一致(比如用户表是 BIGint UNSIGNED,这里就不能用 INT
  • 建联合索引 (user_id, expires_at),既支持“查用户当前 Token”,也支持“清理过期 Token”
  • 别只建单列 INDEX(user_id) —— 过期清理语句 delete FROM sessions WHERE user_id = ? AND expires_at 会走不到索引末尾

expires_at 字段为什么必须用 dateTIME 而非 timestamp

因为 TIMESTAMP 会受 MySQL 时区设置影响,而 Token 过期时间必须绝对精确、与服务器本地时区解耦。

常见错误现象:测试环境时区设为 +08:00,生产环境是 UTC,结果 Token 提前 8 小时失效,前端报“登录已过期”但用户刚登 10 分钟。

  • DATETIME 存的是字面值,写入什么就查出什么,不受 time_zone 变更影响
  • 插入时统一用 UTC 时间(如 PHP 的 date('Y-m-d H:i:s', time()) + 手动转 UTC,或 Python 的 datetime.now(timezone.utc)
  • 别依赖 MySQL 的 NOW()CURRENT_TIMESTAMP 默认值——它们默认跟随会话时区

Token 冲突和并发写入怎么防?

靠唯一约束 + INSERT ... ON DUPLICATE KEY UPDATE,而不是先 SELECTINSERT。否则高并发下必然出现双写、覆盖、旧 Token 漏删。

性能影响:唯一索引冲突时,MySQL 会加间隙锁,但比应用层重试+悲观锁轻得多;只要 token 字段有 UNIQUE 约束,就天然防重复。

  • 建表时必须加 UNIQUE KEY uk_token (token)
  • 登录成功写入:用 INSERT INTO sessions (token, user_id, expires_at) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE user_id = VALUES(user_id), expires_at = VALUES(expires_at)
  • 不要用 REPLACE INTO —— 它本质是 DELETE + INSERT,在有外键或触发器时行为不可控

真正容易被忽略的是:Token 生成必须带足够熵(比如 random_bytes(32) 而非 md5(time().rand())),否则再严谨的存储设计也挡不住撞库。

text=ZqhQzanResources