LTRIM是限制Redis列表长度的唯一可靠方式,因其原子性、精准截断和内存即时释放;必须配合LPUSH使用,错误参数会清空列表,高并发下推荐Lua脚本保障原子性。

为什么用 LTRIM 而不是 LPUSH + LPOP 控制列表长度
直接用 LPUSH 加 LPOP 容易漏掉并发写入时的竞态:两个请求几乎同时 LPUSH,都还没来得及 LPOP,结果列表就超长了。而 LTRIM 是原子操作,无论多少并发写入,只要最后执行一次 LTRIM key 0 N-1,就能确保长度严格为 N。
典型场景是记录最近 100 位访客的 user_id,按访问时间倒序(最新在左):
LPUSH recent_users:site1 "u_123" LTRIM recent_users:site1 0 99
注意:LTRIM 的索引是闭区间,0 99 表示保留前 100 个元素(下标 0 到 99)。
LPUSH + LTRIM 组合的实际写法和参数陷阱
必须先 LPUSH 再 LTRIM,顺序不能反;否则新元素进不去。常见错误是误以为 LTRIM 能“自动截断”,其实它只裁剪,不插入。
- 要保留最新 N 条,始终用
LTRIM key 0 N-1(不是N) - 如果想保留最旧 N 条(比如最早登录的用户),改用
LTRIM key -N -1 - 当列表当前长度 LTRIM 无副作用,安全可重复执行
- Redis 7.0+ 支持
LPUSH key value [value ...] COUNT N(带自动截断),但老版本仍需手动LTIRM
如何避免重复用户挤占有效位置
纯 LPUSH + LTRIM 不去重,同一用户反复访问会多次出现。若需“最近 N 个不同用户”,得配合 SET 或 HyperLogLog 做前置判断:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
推荐做法是:先用 SISMEMBER recent_users_set:site1 u_123 检查是否已存在,再决定是否 LPUSH;插入后用 SADD 记录,并设置过期时间同步清理:
LPUSH recent_users:site1 "u_123" LTRIM recent_users:site1 0 99 SADD recent_users_set:site1 "u_123" EXPIRE recent_users_set:site1 86400
注意:SET 和 LIST 的过期需分别设置,Redis 不支持跨数据结构联动过期。
性能与内存取舍:List 真的是最优解吗
当 N 很大(如 10 万)、更新频繁时,LTRIM 的时间复杂度是 O(N),可能成为瓶颈。此时应考虑替代方案:
- 用
ZSET+ 时间戳做 score:ZADD recent_users:site1 1698765432 "u_123",再用ZREVRANGE取 Top N,天然去重且支持范围查询 - 若只需展示、不需精确去重,
LPUSH + LTRIM仍是最快最省内存的方式 - 避免对同一个 key 高频调用
LTRIM(例如每秒上百次),可改用定时任务批量修剪,或用 Lua 脚本合并操作
真正容易被忽略的是:List 的底层编码在长度较小时用 ziplist,变长后转为 linkedlist,内存占用翻倍。所以 N 设为 1000 比 10000 更友好。

















